2019独角兽企业重金招聘Python工程师标准>>>
根据链路追踪图如上:
资料:https://istio.io/docs/guides/bookinfo/
流程解析:
1. 访问地址: http://IP:31380/productpage
kubectl get svc --all-namespaces -o wide
istio-system istio-ingressgateway NodePort 10.96.192.172
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 9080/TCP 18h app=details
default kubernetes ClusterIP 10.96.0.1 443/TCP 23h
default productpage ClusterIP 10.100.230.25 9080/TCP 18h app=productpage
default ratings ClusterIP 10.111.207.150 9080/TCP 18h app=ratings
default reviews ClusterIP 10.96.83.234 9080/TCP 18h app=reviews
istio-system grafana NodePort 10.99.157.41 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/')
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//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//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': 'Wikipedia Summary: The Comedy of Errors is one of William Shakespeare\'s 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/