《实战 Istio入门与实战》—2.2.2 应用详细说明

举报
华章计算机 发表于 2019/06/20 15:36:14 2019/06/20
【摘要】 本节书摘来自华章计算机《实战 Istio入门与实战》一文中的第2章,第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镜像在本地部署运行,这两种使用方式在后续章节的实验中都有涉及。


【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。