istio 路由实例解析

f93d238615115655af729ad7cf5437954a6.jpg

根据链路追踪图如上:

资料:https://istio.io/docs/guides/bookinfo/

8147ff67b90e99a4882ad5c2d4d0ef8c8a3.jpg

流程解析:

1. 访问地址: http://IP:31380/productpage

 kubectl get svc --all-namespaces  -o wide 

istio-system   istio-ingressgateway       NodePort    10.96.192.172    <none>        80:31380/TCP,443:31390/TCP,31400:31400/TCP                            15h       istio=ingressgateway

IP为k8sMasterIP , 31380 通过nodePort

第一步请求先到  istio-ingressgateway 80端口。 istio-ingressgateway根据路由表再转发到productpage。配置如下

[root@es-cluster-16-203 istio-0.8.0]# cat samples/bookinfo/routing/bookinfo-gateway.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: bookinfo-gateway
spec:
  selector:
    istio: ingressgateway # use istio default controller
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: bookinfo
spec:
  hosts:
  - "*"
  gateways:
  - bookinfo-gateway
  http:
  - match:
    - uri:
        exact: /productpage
    - uri:
        exact: /login
    - uri:
        exact: /logout
    - uri:
        prefix: /api/v1/products
    route:
    - destination:
        host: productpage
        port:
          number: 9080

根据该配置访问/productpage路由到productpage服务

productpage对应SVC,SVC可以根据kubeDNS访问路由,也可也envoy层做路由

default        details                    ClusterIP   10.106.166.91    <none>        9080/TCP                                                              18h       app=details
default        kubernetes                 ClusterIP   10.96.0.1        <none>        443/TCP                                                               23h       <none>
default        productpage                ClusterIP   10.100.230.25    <none>        9080/TCP                                                              18h       app=productpage
default        ratings                    ClusterIP   10.111.207.150   <none>        9080/TCP                                                              18h       app=ratings
default        reviews                    ClusterIP   10.96.83.234     <none>        9080/TCP                                                              18h       app=reviews
istio-system   grafana                    NodePort    10.99.157.41     <none>        3000:31076/TCP                                                        15h       app=grafana

 

2.productpage服务调用了details服务

3.productpage服务调用了2次reviews服务

具体源码如下:

#!/usr/bin/python
#
# Copyright 2017 Istio Authors
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.


from flask import Flask, request, render_template, redirect, url_for
import simplejson as json
import requests
import sys
from json2html import *
import logging
import requests

# These two lines enable debugging at httplib level (requests->urllib3->http.client)
# You will see the REQUEST, including HEADERS and DATA, and RESPONSE with HEADERS but without DATA.
# The only thing missing will be the response.body which is not logged.
try:
    import http.client as http_client
except ImportError:
    # Python 2
    import httplib as http_client
http_client.HTTPConnection.debuglevel = 1

app = Flask(__name__)
logging.basicConfig(filename='microservice.log',filemode='w',level=logging.DEBUG)
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True
app.logger.addHandler(logging.StreamHandler(sys.stdout))
app.logger.setLevel(logging.DEBUG)

from flask_bootstrap import Bootstrap
Bootstrap(app)

details = {
    "name" : "http://details:9080",
    "endpoint" : "details",
    "children" : []
}

ratings = {
    "name" : "http://ratings:9080",
    "endpoint" : "ratings",
    "children" : []
}

reviews = {
    "name" : "http://reviews:9080",
    "endpoint" : "reviews",
    "children" : [ratings]
}

productpage = {
    "name" : "http://productpage:9080",
    "endpoint" : "details",
    "children" : [details, reviews]
}

service_dict = {
    "productpage" : productpage,
    "details" : details,
    "reviews" : reviews,
}

def getForwardHeaders(request):
    headers = {}

    user_cookie = request.cookies.get("user")
    if user_cookie:
        headers['Cookie'] = 'user=' + user_cookie

    incoming_headers = [ 'x-request-id',
                         'x-b3-traceid',
                         'x-b3-spanid',
                         'x-b3-parentspanid',
                         'x-b3-sampled',
                         'x-b3-flags',
                         'x-ot-span-context'
    ]

    for ihdr in incoming_headers:
        val = request.headers.get(ihdr)
        if val is not None:
            headers[ihdr] = val
            #print "incoming: "+ihdr+":"+val

    return headers


# The UI:
@app.route('/')
@app.route('/index.html')
def index():
    """ Display productpage with normal user and test user buttons"""
    global productpage

    table = json2html.convert(json=json.dumps(productpage),
                              table_attributes="class=\"table table-condensed table-bordered table-hover\"")

    return render_template('index.html', serviceTable=table)


@app.route('/health')
def health():
    return 'Product page is healthy'


@app.route('/login', methods=['POST'])
def login():
    user = request.values.get('username')
    response = app.make_response(redirect(request.referrer))
    response.set_cookie('user', user)
    return response


@app.route('/logout', methods=['GET'])
def logout():
    response = app.make_response(redirect(request.referrer))
    response.set_cookie('user', '', expires=0)
    return response


@app.route('/productpage')
def front():
    product_id = 0 # TODO: replace default value
    headers = getForwardHeaders(request)
    user = request.cookies.get("user", "")
    product = getProduct(product_id)
    detailsStatus, details = getProductDetails(product_id, headers)
    reviewsStatus, reviews = getProductReviews(product_id, headers)
    return render_template(
        'productpage.html',
        detailsStatus=detailsStatus,
        reviewsStatus=reviewsStatus,
        product=product,
        details=details,
        reviews=reviews,
        user=user)


# The API:
@app.route('/api/v1/products')
def productsRoute():
    return json.dumps(getProducts()), 200, {'Content-Type': 'application/json'}


@app.route('/api/v1/products/<product_id>')
def productRoute(product_id):
    headers = getForwardHeaders(request)
    status, details = getProductDetails(product_id, headers)
    return json.dumps(details), status, {'Content-Type': 'application/json'}


@app.route('/api/v1/products/<product_id>/reviews')
def reviewsRoute(product_id):
    headers = getForwardHeaders(request)
    status, reviews = getProductReviews(product_id, headers)
    return json.dumps(reviews), status, {'Content-Type': 'application/json'}


@app.route('/api/v1/products/<product_id>/ratings')
def ratingsRoute(product_id):
    headers = getForwardHeaders(request)
    status, ratings = getProductRatings(product_id, headers)
    return json.dumps(ratings), status, {'Content-Type': 'application/json'}



# Data providers:
def getProducts():
    return [
        {
            'id': 0,
            'title': 'The Comedy of Errors',
            'descriptionHtml': '<a href="https://en.wikipedia.org/wiki/The_Comedy_of_Errors">Wikipedia Summary</a>: The Comedy of Errors is one of <b>William Shakespeare\'s</b> early plays. It is his shortest and one of his most farcical comedies, with a major part of the humour coming from slapstick and mistaken identity, in addition to puns and word play.'
        }
    ]


def getProduct(product_id):
    products = getProducts()
    if product_id + 1 > len(products):
        return None
    else:
        return products[product_id]


def getProductDetails(product_id, headers):
    try:
        url = details['name'] + "/" + details['endpoint'] + "/" + str(product_id)
        res = requests.get(url, headers=headers, timeout=3.0)
    except:
        res = None
    if res and res.status_code == 200:
        return 200, res.json()
    else:
        status = res.status_code if res is not None and res.status_code else 500
        return status, {'error': 'Sorry, product details are currently unavailable for this book.'}


def getProductReviews(product_id, headers):
    ## Do not remove. Bug introduced explicitly for illustration in fault injection task
    ## TODO: Figure out how to achieve the same effect using Envoy retries/timeouts
    for _ in range(2):
        try:
            url = reviews['name'] + "/" + reviews['endpoint'] + "/" + str(product_id)
            res = requests.get(url, headers=headers, timeout=3.0)
        except:
            res = None
        if res and res.status_code == 200:
            return 200, res.json()
    status = res.status_code if res is not None and res.status_code else 500
    return status, {'error': 'Sorry, product reviews are currently unavailable for this book.'}


def getProductRatings(product_id, headers):
    try:
        url = ratings['name'] + "/" + ratings['endpoint'] + "/" + str(product_id)
        res = requests.get(url, headers=headers, timeout=3.0)
    except:
        res = None
    if res and res.status_code == 200:
        return 200, res.json()
    else:
        status = res.status_code if res is not None and res.status_code else 500
        return status, {'error': 'Sorry, product ratings are currently unavailable for this book.'}

class Writer(object):
    def __init__(self, filename):
        self.file = open(filename,'w')

    def write(self, data):
        self.file.write(data)
        self.file.flush()

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print "usage: %s port" % (sys.argv[0])
        sys.exit(-1)

    p = int(sys.argv[1])
    sys.stderr = Writer('stderr.log')
    sys.stdout = Writer('stdout.log')
    print "start at port %s" % (p)
    app.run(host='0.0.0.0', port=p, debug=True, threaded=True)

https://github.com/istio/istio/blob/master/samples/bookinfo/src/productpage/productpage.py

 

根据源码可以看出,在productpage内访问其他服务调用方式先关链接是:

details = {
    "name" : "http://details:9080",
    "endpoint" : "details",
    "children" : []
}

ratings = {
    "name" : "http://ratings:9080",
    "endpoint" : "ratings",
    "children" : []
}

reviews = {
    "name" : "http://reviews:9080",
    "endpoint" : "reviews",
    "children" : [ratings]
}

productpage = {
    "name" : "http://productpage:9080",
    "endpoint" : "details",
    "children" : [details, reviews]
}

如访问:http://reviews:9080

reviews:svc名称

[root@es-cluster-16-203 istio-0.8.0]# docker ps |grep productpage
a1da946eab11        a43f156372a7                                                                                                                         "/usr/local/bin/pilot"   18 hours ago        Up 18 hours                             k8s_istio-proxy_productpage-v1-5b76d7dfb7-k4rz5_default_f21e410c-8999-11e8-96aa-00505698cae1_0
152cd7b6277d        istio/examples-bookinfo-productpage-v1@sha256:0c775e97bbe1e76507d71e2149b5c288d8cf6d5c999057ae4d7649aa9209d570                       "/bin/sh -c 'python p"   18 hours ago        Up 18 hours                             k8s_productpage_productpage-v1-5b76d7dfb7-k4rz5_default_f21e410c-8999-11e8-96aa-00505698cae1_0
7a9822c07d13        k8s.gcr.io/pause:3.1                                                                                                                 "/pause"                 18 hours ago        Up 18 hours                             k8s_POD_productpage-v1-5b76d7dfb7-k4rz5_default_f21e410c-8999-11e8-96aa-00505698cae1_0

istio-proxy(envoy)会拦截该请求http://reviews:9080

这里可能会出现2中路由方式:

1.envoy会拦截该请求http://reviews:9080做转发,该路由走的kubeNDS的路由

2.nvoy会拦截该请求http://reviews:9080.根据host地址reviews查找路由表,然后在做一次代理转发。

 

应该是第2种。

 

总流程如下:

http > ingress > productpage-envoy > productpage >

(调用其他服务) productpage-envoy > reviews-envoy >  reviews >  reviews-envoy > productpage-envoy

> productpage

 

 

参考资料:https://istio.io/docs/tasks/traffic-management/request-routing/

转载于:https://my.oschina.net/xiaominmin/blog/1858640

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值