《实战 Istio入门与实战》—2.2.2 应用详细说明
2.2.2 应用详细说明
1. service-js服务
service-js服务分别使用Vue和React各实现一套Web界面,主要用于服务路由中的A/B测试,可以让不同的终端用户看到不同的前端Web界面。service-js服务主要负责根据service-python服务的响应数据,使用ECharts图表库在浏览器上展示出后端服务的具体调用关系和各个服务的调用耗时,具体的代码在实验源码根目录的service/js目录下。
v1版本使用React框架实现,源码目录如下:
.
├── Dockerfile
├── package.json
├── package-lock.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
├── README.md
└── src
├── App.css
├── App.js
├── App.test.js
├── index.css
├── index.js
├── logo.svg
└── registerServiceWorker.js
v2版本使用Vue框架实现,源码目录如下:
.
├── build
│ ├── build.js
│ ├── check-versions.js
│ ├── logo.png
│ ├── utils.js
│ ├── vue-loader.conf.js
│ ├── webpack.base.conf.js
│ ├── webpack.dev.conf.js
│ └── webpack.prod.conf.js
├── config
│ ├── dev.env.js
│ ├── index.js
│ └── prod.env.js
├── Dockerfile
├── index.html
├── package.json
├── package-lock.json
├── README.md
├── src
│ ├── App.vue
│ ├── assets
│ │ └── logo.png
│ ├── components
│ │ └── HelloWorld.vue
│ └── main.js
└── static
用于容器化的Dockerfile文件如下所示:
FROM node:8-alpine
LABEL maintainer="will835559313@163.com"
COPY . /app
WORKDIR /app
RUN npm i && npm run build \
&& rm -rf ./node_modules \
&& npm install -g serve
EXPOSE 80
CMD ["serve", "-s", "build", "-p", "80"]
2. service-python服务
service-python服务是一个用Python编写的API服务,负责接收前端的API请求,调用整合后端其他服务的响应数据,返回给前端使用。service-python服务分别使用Python2和Python3实现了两个版本的服务,具体代码在实验源码根目录的service/python目录下。service-python服务使用Flask框架实现,具体源码如下:
1 import time
2 import requests
3 from functools import partial
4 from flask import Flask, jsonify, g, request
5 from multiprocessing.dummy import Pool as ThreadPool
6
7
8 app = Flask(__name__)
9
10
11 def getForwardHeaders(request):
12 headers = {}
13 incoming_headers = [
14 'x-request-id',
15 'x-b3-traceid',
16 'x-b3-spanid',
17 'x-b3-parentspanid',
18 'x-b3-sampled',
19 'x-b3-flags',
20 'x-ot-span-context'
21 ]
22
23 for ihdr in incoming_headers:
24 val = request.headers.get(ihdr)
25 if val is not None:
26 headers[ihdr] = val
27
28 return headers
29
30
31 @app.before_request
32 def before_request():
33 g.forwardHeaders = getForwardHeaders(request)
34
35
36 def get_url_response(url, headers={}):
37 try:
38 start = time.time()
39 resp = requests.get(url, headers=headers, timeout=20)
40 response_time = round(time.time() - start, 2)
41 data = resp.json()
42 data['response_time'] = response_time
43 except Exception as e:
44 print(e)
45 data = None
46 return data
47
48
49 @app.route("/env")
50 def env():
51 service_lua_url = 'http://' + 'service-lua' + '/env'
52 service_node_url = 'http://' + 'service-node' + '/env'
53
54 services_url = [service_lua_url, service_node_url]
55 pool = ThreadPool(2)
56 wrap_get_url_response = partial(get_url_response, headers=g.forwardHeaders)
57 results = pool.map(wrap_get_url_response, services_url)
58 upstream = [r for r in results if r]
59
60 return jsonify({
61 "message": 'python v1',
62 "upstream": upstream
63 })
64
65
66 @app.route("/status")
67 def status():
68 return "ok"
69
70
71 if __name__ == '__main__':
72 app.run(host='0.0.0.0', port=80)
第11~28行定义的getForwardHeaders函数是为了从请求中提取出用于Istio调用链追踪的头信息,用于传递给service-python要调用的其他后端服务。
第31~33行表示每个请求在被处理前,调用getForwardHeaders函数,从请求中提取出Istio调用链追踪的头信息,并保存到全局对象g的forwardHeaders变量中。
第36~46行定义了用于获取后端服务响应数据的get_url_response函数。
第49~63行定义了真正的业务路由,使用线程池的方式并发请求后端服务,并把后端服务的响应数据组合处理后返回给调用方。
第66~68行定义了用于服务健康检查的路由。
第71~72行表示服务启动在0.0.0.0地址的80端口。
v1版本和v2版本源码只有第61行有略微差别,在v2版本中"python v1"修改为"python v2"。
用于容器化的Dockerfile文件如下所示:
FROM python:2-alpine
LABEL maintainer="will835559313@163.com"
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
CMD [ "python", "main.py" ]
v1版本和v2版本的Dockerfile只有使用的基础镜像版本不同,其他保持一致。
3. service-lua服务
service-lua服务使用OpenResty的不同版本用Lua语言分别实现了两个版本的服务。具体的代码在实验源码根目录的service/lua目录下。源代码如下:
1 worker_processes 4;
2 error_log logs/error.log;
3 events {
4 worker_connections 10240;
5 }
6 http {
7 server {
8 listen 80;
9 location / {
10 default_type text/html;
11 content_by_lua '
12 ngx.say("hello, world")
13 ';
14 }
15
16 location = /status {
17 default_type text/html;
18 content_by_lua '
19 ngx.say("ok")
20 ';
21 }
22
23 location = /env {
24 charset utf-8;
25 charset_types application/json;
26 default_type application/json;
27 content_by_lua '
28 json = require "cjson"
29 ngx.status = ngx.HTTP_OK
30 version = "lua v1"
31 data = {
32 message = version
33 }
34 ngx.say(json.encode(data))
35 return ngx.exit(ngx.HTTP_OK)
36 ';
37 }
38 }
39 }
第8行表示服务启动在0.0.0.0地址的80端口。
第9~14行定义了访问服务的/链接时返回"hello world"。
第16~21行定义了用于服务健康检查的/status链接。
第23~37行定义了真正的业务逻辑,当访问/env链接时,响应服务的版本信息。
v1版本和v2版本源码只有第30行有略微差别,在v2版本中"lua v1"修改为"lua v2"。
用于容器化的Dockerfile文件如下所示:
FROM openresty/openresty:1.11.2.5-alpine
LABEL maintainer="will835559313@163.com"
COPY . /app
WORKDIR /app
EXPOSE 80
ENTRYPOINT ["/usr/local/openresty/bin/openresty", "-c", "/app/nginx.conf", "-g", "daemon off;"]
v1版本和v2版本的Dockerfile只有使用的基础镜像版本不同,其他保持一致。
4. service-node服务
service-node服务使用Node的不同版本分别实现了两个版本的服务。具体的代码在实验源码根目录的service/node目录下。源代码如下:
1 const Koa = require('koa');
2 const Router = require('koa-router');
3 const axios = require('axios')
4 const app = new Koa();
5 const router = new Router();
6
7
8 function getForwardHeaders(request) {
9 headers = {}
10 incoming_headers = [
11 'x-request-id',
12 'x-b3-traceid',
13 'x-b3-spanid',
14 'x-b3-parentspanid',
15 'x-b3-sampled',
16 'x-b3-flags',
17 'x-ot-span-context'
18 ]
19
20 for (idx in incoming_headers) {
21 ihdr = incoming_headers[idx]
22 val = request.headers[ihdr]
23 if (val !== undefined && val !== '') {
24 headers[ihdr] = val
25 }
26 }
27 return headers
28 }
29
30
31
32 router.get('/status', async (ctx, next) => {
33 ctx.body = 'ok';
34 })
35
36 router.get('/env', async (ctx, next) => {
37 forwardHeaders = getForwardHeaders(ctx.request)
38 service_go_url = 'http://' + 'service-go' + '/env'
39 upstream_ret = null
40 try {
41 let start = Date.now()
42 const response = await axios.get(service_go_url, {
43 headers: forwardHeaders,
44 timeout: 20000
45 });
46 response_time = ((Date.now() - start) / 1000).toFixed(2)
47 upstream_ret = response.data
48 upstream_ret.response_time = response_time
49 } catch (error) {
50 console.error('error');
51 }
52 if (upstream_ret) {
53 ctx.body = {
54 'message': 'node v1',
55 'upstream': [upstream_ret]
56 };
57 } else {
58 ctx.body = {
59 'message': 'node v1',
60 'upstream': []
61 }
62 }
63 })
64
65 app.use(router.routes()).use(router.allowedMethods());
66 app.listen(80);
第8~28行定义的getForwardHeaders函数是为了从请求中提取出用于Istio调用链追踪的头信息,用于传递给service-node要调用的其他后端服务。
第32~34行定义了用于服务简单健康检查的路由。
第36~63行定义了真正的业务路由,请求后端服务,并把后端服务的响应数据组合处理后返回给调用方。
第65~66行表示服务启动在0.0.0.0地址的80端口。
v1版本和v2版本源码只有第54和59行有略微差别,在v2版本中"node v1"修改"node v2"。
用于容器化的Dockerfile文件如下:
FROM node:8-alpine
LABEL maintainer="will835559313@163.com"
COPY . /app
WORKDIR /app
RUN npm i
EXPOSE 80
CMD ["node", "main.js"]
v1版本和v2版本的Dockerfile只有使用的基础镜像版本不同,其他保持一致。
5. service-go服务
service-go服务使用Go语言的不同版本分别实现了两个版本的服务,具体的代码在实验源码根目录的service/go目录下。源代码如下:
1 package main
2
3 import (
4 "github.com/gin-gonic/gin"
5 )
6
7 func main() {
8 r := gin.Default()
9
10 r.GET("/env", func(c *gin.Context) {
11 c.JSON(200, gin.H{
12 "message": "go v1",
13 })
14 })
15
16 r.GET("/status", func(c *gin.Context) {
17 c.String(200, "ok")
18 })
19
20 r.Run(":80")
21 }
第10~14行定义了真正的业务路由,以及返回服务的版本信息。
第16~18行定义了用于服务健康检查的路由。
第20行表示服务启动在0.0.0.0地址的80端口。
v1版本和v2版本源码只有第12行有略微差别,在v2版本中"go v1"修改为"go v2"。
用于容器化的Dockerfile文件如下:
FROM golang:1.10-alpine as builder
LABEL maintainer="will835559313@163.com"
COPY . /app
WORKDIR /app
RUN apk update && apk add git \
&& go get github.com/gin-gonic/gin \
&& go build
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/app .
EXPOSE 80
CMD ["./app"]
你可能已经注意到,上面的Dockerfile代码使用了两次FROM关键词和一个不太一样的COPY用法,这体现了Docker镜像的多阶段构建功能,可以在一个镜像中编译代码,然后复制编译后的产物到另一个镜像中,这样可以非常有效地减小应用的Docker镜像大小,特别是Go、Java这类编译型静态语言,因为这类语言编译之后就不再需要原来编译时的依赖库,可以把编译后的产物直接放在一个极小运行环境中启动运行。由于Go语言可以编译为在操作系统上直接运行的二进制文件,所以可以把编译后的文件直接复制到alpine这类极简的操作系统镜像中,这种优化使得service-go服务编译构建后的镜像体积可缩小到10M级别,进而使服务镜像的分发效率大幅度提升。
v1版本和v2版本的Dockerfile只有使用的基础镜像版本不同,其他保持一致。
6. service-redis服务
service-redis服务是使用Go语言实现的服务,用于从Redis服务器获取信息,具体的代码在实验源码根目录的service/redis目录下。源代码如下:
1 package main
2
3 import (
4 "log"
5
6 "github.com/gin-gonic/gin"
7 "github.com/go-redis/redis"
8 )
9
10 // NewClient new redis client
11 func NewClient() *redis.Client {
12 client := redis.NewClient(&redis.Options{
13 Addr: "redis:6379",
14 Password: "",
15 DB: 0,
16 })
17 return client
18 }
19
20 func main() {
21 r := gin.Default()
22 client := NewClient()
23 r.GET("/env", func(c *gin.Context) {
24 val, err := client.Info().Result()
25 if err != nil {
26 log.Print(err)
27 }
28 c.String(200, val)
29 })
30 r.GET("/status", func(c *gin.Context) {
31 c.String(200, "ok")
32 })
33 r.Run(":80")
34 }
第23~29行定义了真正的业务路由,以及返回Redis服务器的信息。
第30~32行定义了用于服务健康检查的路由。
第33行表示服务启动在0.0.0.0地址的80端口。
用于容器化的Dockerfile文件如下:
FROM golang:1.11-alpine as builder
LABEL maintainer="will835559313@163.com"
COPY . /app
WORKDIR /app
RUN apk update && apk add git \
&& go get github.com/gin-gonic/gin \
&& go build
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/app .
EXPOSE 80
CMD ["./app"]
7. httpbin服务
httpbin服务是一个用于HTTP测试的开源服务。它既提供了在线的测试服务,也可以通过源码或者使用Docker镜像在本地部署运行,这两种使用方式在后续章节的实验中都有涉及。
- 点赞
- 收藏
- 关注作者
评论(0)