转载:使用 Istio 实现基于 Kubernetes 的微服务应用

转载:使用 Istio 实现基于 Kubernetes 的微服务应用

近两年,随着容器、Kubernetes 等技术的兴起,微服务被广泛提及并被大量使用。本文旨在让读者了解 Istio,通过它与 Kubernetes 相结合,大幅降低微服务的复杂度,以便让开发人员更关注于代码本身。

Istio 的架构分析

Istio 介绍

Istio 被称为 Service Mesh 架构,该开源项目由 Google 和 IBM 主导,根据 http://stackalytics.com 网站的统计,该社区代码 Commits 厂商排名如下: 

图 1. Istio 各厂商代码贡献量图示

图 2. Istio 各厂商代码贡献量排名

在 GitHub 上,Istio 项目受关注的程度非常高,您可进一步了解。接下来,我们详细介绍 Istio 的技术架构。

Istio 的架构

Istio 分为两个平面:数据平面和控制平面。

数据平面:

数据平面由一组 Sidecar 的代理(Envoy)组成。这些代理调解和控制微服务之间的所有网络通信,并且与控制平面的 Mixer 通讯,接受调度策略。

控制平面:

控制平面通过管理和配置 Envoy 来管理流量。此外,控制平面配置 Mixers 来实施路由策略并收集检测到的监控数据。 

图 3. Istio 的架构图

在介绍了 Istio 的两个平面以后,我们详细介绍 Istio 的各个组件。

Envoy 是一个用 C ++开发的高性能代理,用于管理 Service Mesh 中所有服务的所有入站和出站流量。 Istio 利用 Envoy 的许多内置功能,例如:

动态服务发现

负载均衡

TLS 终止

HTTP / 2 和 gRPC 代理

断路器

健康检查

流量分割

故障注入

监控指标

我们知道,在 Kubernetes 中集群中,Pod 是最小的计算资源调度单位。一个 Pod 可以包含一个或者多个容器,但通常是一个。而使用 Istio 的时候,需要在 Pod 中主容器旁注入一个 Sidecar,也就是上面提到的代理(Envoy)。

举一个例子,我们查看一个被注入了 Envoy 的 Pod,从下图结果可以看到,这个 Pod 包含两个容器: 

图 4. 查看 Pod 中的 Container

在 Istio 中,每一个 Pod 中都必须要部署一个 Sidecar。

Mixer 是一个独立于平台的组件,负责在整个 Service Mesh 中执行访问控制和使用策略,并从 Envoy 代理和其他服务收集监控到的数据。

Pilot 为 Envoy 提供服务发现;为高级路由(例如,A / B 测试,金丝雀部署等)提供流量管理功能;以及异常控制,如:超时,重试,断路器等。

Citadel 通过内置身份和凭证管理,提供强大的服务到服务和最终用户身份验证。我们可以使用 Citadel 升级 Service Mesh 中的未加密流量。我们可以使用 Istio 的授权功能来控制谁可以访问服务。

Istio 路由规则的实现

在 Istio 中,和路由相关的有四个概念:Virtual Services 、Destination Rules、ServiceEntry、Gateways。

Virtual Services 的作用是:定义了针对 Istio 中的一个微服务的请求的路由规则。Virtual Services 既可以将请求路由到一个应用的不同版本,也可以将请求路由到完全不同的应用。

在如下的示例配置中,发给微服务的请求,将会被路由到 Productpage,端口号为 9080。

route:

   - destination:

       host: productpage

       port:

         number: 9080

清单 1. Virtual Services 规则

在下面的示例配置中,定义了熔断策略。

spec:

 host: productpage

 subsets:

 - labels:

     version: v1

   name: v1

 trafficPolicy:

   connectionPool:

     http:

       http1MaxPendingRequests: 1

       maxRequestsPerConnection: 1

     tcp:

       maxConnections: 1

   tls:

清单 2. Destination 规则

ServiceEntry 用于将 Istio 外部的服务注册到 Istio 的内部服务注册表,以便 Istio 内部的服务可以访问这些外部的服务,如 Istio 外部的 Web API。

在如下的示例配置中,定义了 Istio 外部的 Mongo Cluster 与 Istio 内部的访问规则。

apiVersion: networking.istio.io/v1alpha3

kind: ServiceEntry

metadata:

 name: external-svc-mongocluster

spec:

 hosts:

 - mymongodb.somedomain # not used

 addresses:

 - 192.192.192.192/24 # VIPs

 ports:

 - number: 27018

   name: mongodb

   protocol: MONGO

 location: MESH_INTERNAL

 resolution: STATIC

 endpoints:

 - address: 2.2.2.2

 - address: 3.3.3.3

清单 3. ServiceEntry 规则

Gateway:定义了 Istio 边缘的负载均衡器。所谓边缘,就是 Istio 的入口和出口。这个负载均衡器用于接收传入或传出 Istio 的 HTTP / TCP 连接。在 Istio 中会有 Ingress Gateway 和 Egress Gateway,前者负责入口流量,后者负责出口流量。

在如下的示例配置中,定义了 Istio 的入口流量。

spec:

 selector:

   istio: ingressgateway

 servers:

 - hosts:

   - '*'

   port:

     name: http

     number: 80

     protocol: HTTP

清单 4. Gateway 规则

Istio 的技术实现

基于 Kubernetes 部署的 Istio

在本文中,我们基于 Kubernetes 1.11 部署 Istio 1.04。由于篇幅有限,具体的部署步骤可以参考 Quick Start with Kubernetes(https://istio.io/docs/setup/Kubernetes/quick-start/)。

查看 Kubernetes 版本: 

图 5. 查看 Kubernetes 集群的版本

查看 Kubernetes 集群: 

图 6. 查看 Kubernetes 集群的节点

查看部署好的 Istio。

Istio 以一个项目的形式部署到 Kubernetes 集群中。我们可以看到,部署好的 Pod 中,除了有 istio-citadel、istio-egressgateway、istio-ingressgateway、istio-pilot 等 Istio 本身的功能组件,还集成了微服务相关的监控工具,如:Grafana、jaeger-agent、Kiali、Prometheus。正是这些功能丰富且强大的监控工具,帮助 Istio 实现了微服务的可视化管理。


图 7. 查看 Kubernetes 中的 Istio

查看 istio 版本:1.0.4: 

图 8. 查看 Istio 版本

接下来,我们将会对 Isito 集成的工具进行介绍。本文最后的实验展现环节,我们将会使用这些工具进行微服务的监控和管理。

Istio 的工具集:Grafana

Grafana 是一个非常著名的开源项目。它是一个 Web 应用,可以提供丰富的监控仪表盘。它的后端支持 Graphite、 InfluxDB 或 OpenTSDB。

通过浏览器访问 Istio 中部署好的 Grafana。

登录 Grafana 后,首页面如下: 

图 9. Grafana 首页面

查看已有的 Dashboard: 

图 10. Grafana 上的 Istio Dashboard

我们查看 Pilot Dashboard,可以看到丰富的资源统计。 

图 11. Grafana 上的 Istio Dashboard 查看

Istio 的工具集:Prometheus

Prometheus 是一个开源监控系统。它具有维度数据模型;具备灵活的查询语言、高效的时间序列数据库,并提供灵活的警报方法。

在 Istio 中,Prometheus 收到的数据,会被汇总到 Grafana 进行统一展现。

访问 Istio 中部署好的 Prometheus: 

图 12. Prometheus 的 UI

我们可以看到有多达上百个监测点: 

图 13. Prometheus 的监测点

例如我们选择 Containermemorycache,点击 Execute。

图 14. 执行监测点

然后可以生成图形化界面展示,并且我们也可以调整时间间隔(图中是 60 分钟)。 

图 15. 监控图

Istio 的工具集:Kiali

Kiali 作为一个开源项目,可以为 Istio 提供可视化服务网格拓扑、断路器或请求率等功能。此外 Kiali 还包括 Jaeger Tracing,可以提供开箱即用的分布式跟踪功能。

我们看一下 Istio 中部署的 Kiali: 

图 16. Kiali 的 UI 首页

它可以查看在 Istio 上部署的微服务的拓扑结构: 

图 17. Kiali 查看微服务的拓扑

Istio 的工具集:Jaeger

Jaeger 是一个开源项目,用于微服务的分布式跟踪。它实现的功能有:

分布式事务监控

服务调用问题根因分析

服务依赖性分析

性能/延迟优化

Jaeger 工具已经集成到 Istio 中,部署以后可以通过浏览器访问。

下图是 Jeager 追踪 Productpage 这个服务在过去三个小时的所有调用: 

图 18. Jaeger 查看 API 调用

我们可以展开看详细的调用层级: 

图 19. Jaeger 查看 API 详细调用

Istio 管理微服务的实验展现

在本小节中,我们将在 Istio 上部署一个名为 bookinfo 的微服务应用。为了方便读者理解,我们先分析这个应用的源代码。然后展示 Istio 如何管理这套微服务。

bookinfo 微服务源码分析

bookinfo 应用程序显示的有关书籍的信息,类似于在线书店的单个商品。应用页面上显示的是书籍的描述、书籍详细信息(ISBN,页数等)以及书评。

bookinfo 应用一共包含四个微服务:Productpage、Details、Reviews、Ratings。

Productpage 使用 Python 开发,负责展示书籍的名称和书籍的简介。

Details 使用 Ruby 开发,负责展示书籍的详细信息。

Reviews 使用 Java 开发,负责显示书评。

Ratings 使用 Node.js 开发,负责显示书籍的评星。

其拓扑关系见下图。 

图 20. bookinfo 应用拓扑架构

我们看一下 bookinfo 微服务部署完毕的展示效果: 

图 21. bookinfo 应用页面展示效果

Productpage 微服务包含两部分内容:

书籍的名称:"The Comedy of Errors",翻译成中文是《错误的喜剧》。

书籍的简介:Summary。翻译成中文是:《错误的喜剧》是威廉·莎士比亚早期剧作之一。这是他最短的、也是他最喜欢的喜剧之一,除了双关语和文字游戏之外,幽默的主要部分来自于打闹和错误的身份。

Details 微服务包含的内容是书籍的详细信息,内容如下:

Type:

paperback

Pages:

200

Publisher:

PublisherA

Language:

English

ISBN-10:

1234567890

ISBN-13:

123-1234567890

清单 5. Details 微服务显示内容

Reviews 微服务包含的信息是书评内容:

An extremely entertaining play by Shakespeare. The slapstick humour is refreshing!

Absolutely fun and entertaining. The play lacks thematic depth when compared to other plays by Shakespeare.

清单 6. 书评内容

Ratings 微服务包含的内容将展示为评星部分(下图黑框中的部分)。

图 22. 评星图

接下来,我们访问 GitHub 上查看 bookinfo 源码,通过源码分析,理解业务的实现逻辑。

首先查看 Productpage(https://github.com/istio/istio/tree/master/samples/bookinfo/src/productpage)的源码(部分内容):

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.'

           }

清单 7. Productpage 源码

我们可以很明显看出,以上代码就是 bookinfo 页面显示的书籍的名称和简介。

查看 Productpage 的另外一部分源码。

details = {

"name" : "http://details{0}:9080".format(servicesDomain),

"endpoint" : "details",

"children" : []

}

ratings = {

"name" : "http://ratings{0}:9080".format(servicesDomain),

"endpoint" : "ratings",

"children" : []

}

reviews = {

"name" : "http://reviews{0}:9080".format(servicesDomain),

"endpoint" : "reviews",

"children" : [ratings]

}

productpage = {

"name" : "http://details{0}:9080".format(servicesDomain),

"endpoint" : "details",

"children" : [details, reviews]

}

service_dict = {

"productpage" : productpage,

"details" : details,

"reviews" : reviews,

}

清单 8. Productpage 源码

上面代码定义了四个微服务的 name、endpoint、children。endpoint 代表这个微服务后端的 Kubernetes 集群中 service 名称、children 代表本微服务调用的 Kubernetes 集群中的微服务 service 名称。

以 Productpage 举例,它的 endpoint 是 details、children 是 details 和 reviews。所以,被发送到 Productpage 请求,将会调用 details、reviews 这两个服务。

接下来,查看 reviews 微服务的源码,代码使用 Java 编写的。

private String getJsonResponse (String productId, int starsReviewer1, int starsReviewer2) {

   String result = "{";

   result += "\"id\": \"" + productId + "\",";

   result += "\"reviews\": [";

   // reviewer 1:

   result += "{";

   result += "  \"reviewer\": \"Reviewer1\",";

   result += "  \"text\": \"An extremely entertaining play by Shakespeare. The slapstick humour is refreshing!\"";

 if (ratings_enabled) {

   if (starsReviewer1 != -1) {

     result += ", \"rating\": {\"stars\": " + starsReviewer1 + ", \"color\": \"" + star_color + "\"}";

   }

   else {

     result += ", \"rating\": {\"error\": \"Ratings service is currently unavailable\"}";

   }

 }

   result += "},";

   // reviewer 2:

   result += "{";

   result += "  \"reviewer\": \"Reviewer2\",";

   result += "  \"text\": \"Absolutely fun and entertaining. The play lacks thematic depth when compared to other plays by Shakespeare.\"";

 if (ratings_enabled) {

   if (starsReviewer2 != -1) {

     result += ", \"rating\": {\"stars\": " + starsReviewer2 + ", \"color\": \"" + star_color + "\"}";

   }

   else {

     result += ", \"rating\": {\"error\": \"Ratings service is currently unavailable\"}";

   }

 }

   result += "}";

   result += "]";

   result += "}";

   return result;

}

清单 9. reviews 服务源码

上面的这段代码,定义的是两个 Reviewer,以及书评的内容。书评的内容正是 bookinfo 页面展示的内容。

在上面的代码中,我们注意到有两个重要的变量 starcolor 和 ratingsenabled。

star_color 表示评星的颜色(黑色和红色)。

ratings_enabled 表示是否启用评星。

查看 reviews 微服务的源码的另外一部分内容:

private final static String star_color = System.getenv("STAR_COLOR") == null ? "black" : System.getenv("STAR_COLOR");

清单 10. reviews 服务源码

上面代码显示:在应用构建时:

如果不指定 STARCOLOR 变量且 ratingsenabled 为 true,那么评星默认为黑色。

如果指定 STARCOLOR 变量且 ratingsenabled 为 true,那么评星颜色为传入的颜色。

如果不指定 ratings_enabled 为 true,那么将不会显示评星。

那么,STAR_COLOR 这个变量,在应用构建时,有没有传入呢?我们查看:build-services.sh

#java build the app.

docker run --rm -v "$(pwd)":/home/gradle/project -w /home/gradle/project gradle:4.8.1 gradle clean build

pushd reviews-wlpcfg

 #plain build -- no ratings

 docker build -t "istio/examples-bookinfo-reviews-v1:${VERSION}" -t istio/examples-bookinfo-reviews-v1:latest --build-arg service_version=v1 .

 #with ratings black stars

 docker build -t "istio/examples-bookinfo-reviews-v2:${VERSION}" -t istio/examples-bookinfo-reviews-v2:latest --build-arg service_version=v2 \

    --build-arg enable_ratings=true .

 #with ratings red stars

 docker build -t "istio/examples-bookinfo-reviews-v3:${VERSION}" -t istio/examples-bookinfo-reviews-v3:latest --build-arg service_version=v3 \

    --build-arg enable_ratings=true --build-arg star_color=red .

清单 11. build-services.sh 内容

上面代码显示,运行该脚本是,将会构建三个版本 Reviews 的 docker image:

V1:没有评星(未指定 enable_ratings=true)。

V2:评星为黑色(指定 enableratings=true;未指定 starcolor 的变量,代码中默认的颜色为黑色)。

V3:评星为红色(指定 enableratings=true;指定 starcolor 的变量为 red)。

在 bookinfo 的源代码中,还有两个数据库的定义 MongoDB 和 MySQL。

接下来,我们看这个应用中两个数据库的内容。

先看 MongoDB 的 script.sh,内容如下:

#!/bin/sh

   set -e

   mongoimport --host localhost --db test --collection ratings --drop --file /app/data/ratings_data.json

清单 12. MongoDB 的 script.sh

也就是说,MongoDB 数据库在初始化时,会将 ratings_data.json 文件中的信息导入到数据库中。

再看 ratings_data.json:

{rating: 5}

{rating: 4}

清单 13. ratings_data.json 文件

也就是说,当应用部署完毕后,MongoDB 将包含五星和四星。

查看 MySQL 的初始化文件:mysqldb-init.sql。

# Initialize a mysql db with a 'test' db and be able test productpage with it.

# mysql -h 127.0.0.1 -ppassword < mysqldb-init.sql

CREATE DATABASE test;

USE test;

CREATE TABLE `ratings` (

 `ReviewID` INT NOT NULL,

 `Rating` INT,

 PRIMARY KEY (`ReviewID`)

);

INSERT INTO ratings (ReviewID, Rating) VALUES (1, 5);

INSERT INTO ratings (ReviewID, Rating) VALUES (2, 4);

清单 14. mysqldb-init.sql 内容

我们可以看出,上面的初始化脚本是创建一个名为 Ratings 的数据库表,插入的数据效果如下:

ReviewIDRating

15

24

表 1. 数据库表示意

查看 Ratings 的源代码,该代码使用 Node.JS 书写。

* We default to using mongodb, if DB_TYPE is not set to mysql.

*/

if (process.env.SERVICE_VERSION === 'v2') {

 if (process.env.DB_TYPE === 'mysql') {

   var mysql = require('mysql')

   var hostName = process.env.MYSQL_DB_HOST

   var portNumber = process.env.MYSQL_DB_PORT

   var username = process.env.MYSQL_DB_USER

   var password = process.env.MYSQL_DB_PASSWORD

 } else {

   var MongoClient = require('mongodb').MongoClient

   var url = process.env.MONGO_DB_URL

 }

}

dispatcher.onGet(/^\/ratings\/[0-9]*/, function (req, res) {

 var productIdStr = req.url.split('/').pop()

 var productId = parseInt(productIdStr)

 if (Number.isNaN(productId)) {

   res.writeHead(400, {'Content-type': 'application/json'})

   res.end(JSON.stringify({error: 'please provide numeric product ID'}))

 } else if (process.env.SERVICE_VERSION === 'v2') {

   var firstRating = 0

   var secondRating = 0

   if (process.env.DB_TYPE === 'mysql') {

     var connection = mysql.createConnection({

       host: hostName,

       port: portNumber,

       user: username,

       password: password,

       database: 'test'

     })

     connection.connect()

     connection.query('SELECT Rating FROM ratings', function (err, results, fields) {

       if (err) {

         res.writeHead(500, {'Content-type': 'application/json'})

         res.end(JSON.stringify({error: 'could not connect to ratings database'}))

       } else {

         if (results[0]) {

           firstRating = results[0].Rating

         }

         if (results[1]) {

           secondRating = results[1].Rating

         }

         var result = {

           id: productId,

           ratings: {

             Reviewer1: firstRating,

             Reviewer2: secondRating

           }

         }

         res.writeHead(200, {'Content-type': 'application/json'})

         res.end(JSON.stringify(result))

       }

     })

清单 15. Ratings 的源代码

以上代码主要实现:如果不指定 DB_TYPE 的变量,将默认使用 MongoDB 数据库。

当微服务 Reviews 的版本是 V2 时,将连接数据库 MySQL 或 MongoDB(根据环境变量传入的 DB_TYPE)。当 Reviews 的版本是 V3 时,访问 MongoDB 数据库。

但从上面的数据库分析我们可以知道,无论 Reviews 连接哪个数据库,得到的数据都是第一个评论者五星、第二个评论者四星。也就是说,只要是 Reviews 的 V2 和 V3 版本,访问数据库得到的评星结果是一样的;只不过 Reviews 为 V2 时评星为黑色、Reviews 为 V3 时评星为红色。

微服务的部署

我们在 Kubernetes 集群中部署 bookinfo 应用。

图 23. 在 Kubernetes 集群部署 bookinfo

Pod 创建成功: 

图 24. 查看 bookinfo 的 Pod

接下来,通过浏览器对 bookinfo 发起多次访问,页面呈现三种显示。

第一种:访问 bookinfo 时(Productpage 调用的 Review V1),页面没有评星;

第二种:访问 bookinfo 时(Productpage 调用的 Review V2),页面是黑色的评星;

第三种:访问 bookinfo 时(Productpage 调用的 Review V3),页面是红色的评星。

图 25. bookinfo 第一种展现

图 26. bookinfo 第二种展现

图 27. bookinfo 第三种展现

过了几秒后:Kiali 收集到之前几次对 bookinfo 的访问流量,并进行动态展示。我们可以看到,Productpage 随机访问 Reviews V1、V2、V3。

图 28. Kiali 展示微服务流量

Productpage 轮询访问 Review V1 和 V2 的原因是:我们没有设置针对 Reviews 的特定策略,而 Productpage 的源码中,指定了 Product 服务调用 Reviews 服务的业务逻辑,但并未指定版本。因此,Product 服务会随机访问 Reviews 的三个版本。 

图 29. 查看 virtualservice

接下来,我们查看 Virtualservice 的配置文件。

图30. 查看 Virtualservice 配置文件列表

查看 virtual-service-reviews-v3.yaml 内容。该文件定义发向 Reviews 的请求,全部到 V3 版本。

[root@master networking]# cat virtual-service-reviews-v3.yaml

apiVersion: networking.istio.io/v1alpha3

kind: VirtualService

metadata:

 name: reviews

spec:

 hosts:

   - reviews

 http:

 - route:

   - destination:

       host: reviews

       subset: v3      version: v3

清单 16. 查看 virtual-service-reviews-v3.yaml 内容

接下来,应用 Virtualservice 配置。

图 31. 应用配置

查看生效的 Virtualservice,reviews 的配置生效。

图 32. 查看生效的 Virtualservice

接下来,我们再次对 bookinfo 发起多次访问,可以看到,页面的评星均为红色。 

图 33. 访问 bookinfo 应用

通过 Kiali 查看流量,可以看到,Productpage 的流量全部访问 Review V3。

图 34. Kiali 查看 bookinfo 应用访问流量

接下来,我们继续调整策略,让 Productpage 对 Reviews 的访问,以 V1 和 V2 按照 8:2 比率进行:

[root@master networking]# cat virtual-service-reviews-80-20.yaml

apiVersion: networking.istio.io/v1alpha3

kind: VirtualService

metadata:

 name: reviews

spec:

 hosts:

   - reviews

 http:

 - route:

   - destination:

       host: reviews

       subset: v1

     weight: 80

   - destination:

       host: reviews

       subset: v2

     weight: 20

清单 17. 查看 virtual-service-reviews-80-20.yaml 内容

图 35. 替换之前的 Virtual Services 策略

图 36. Kiali 查看 bookinfo 应用访问流量

Istio 的限速

在了解了 Istio 微服务的路由策略后,接下来我们对微服务组件进行限速的设置。

在默认情况下,Productpage 随机访问 reviews 三个版本,没有流量限制。我们编写两个配置文件,限制 Productpage 到 reviews v3 的访问速度。

speedrule.yaml 限制了从 Productpage 到 reviews v3 的访问速度,最多每秒一个请求。

[root@master ~]# cat speedrule.yaml

apiVersion: "config.istio.io/v1alpha2"

kind: memquota

metadata:

 name: handler

 namespace: myproject

spec:

 quotas:

 - name: requestcount.quota.myproject

   # default rate limit is 5000qps

   maxAmount: 5000

   validDuration: 1s

   # The first matching override is applied.

   # A requestcount instance is checked against override dimensions.

   overrides:

   - dimensions:

       destination: reviews

       source: productpage

       destinationVersion: v3

     maxAmount: 1

     validDuration: 1s

recommendation_rate_limit_handler.yml 文件声明了 requestcount quota。

清单 18. 查看 speedrule.yaml

[root@master ~]# cat recommendation_rate_limit_handler.yml

apiVersion: "config.istio.io/v1alpha2"

kind: quota

metadata:

 name: requestcount

 namespace: myproject

spec:

 dimensions:

   source: source.labels["app"] | source.service | "unknown"

   sourceVersion: source.labels["version"] | "unknown"

   destination: destination.labels["app"] | destination.service | "unknown"

   destinationVersion: destination.labels["version"] | "unknown"

---

apiVersion: "config.istio.io/v1alpha2"

kind: rule

metadata:

 name: quota

 namespace: myproject

spec:

 actions:

 - handler: handler.memquota

   instances:

   - requestcount.quota

---

apiVersion: config.istio.io/v1alpha2

kind: QuotaSpec

metadata:

 creationTimestamp: null

 name: request-count

 namespace: myproject

spec:

 rules:

 - quotas:

   - charge: 1

     quota: RequestCount

---

apiVersion: config.istio.io/v1alpha2

kind: QuotaSpecBinding

metadata:

 creationTimestamp: null

 name: request-count

 namespace: myproject

spec:

 quotaSpecs:

 - name: request-count

   namespace: myproject

 services:

 - name: productpage

   namespace: myproject

 - name: details

   namespace: myproject

 - name: reviews

   namespace: myproject

清单 19. 查看 recommendation_rate_limit_handler.yml

应用两个配置:


图 37. 应用配置

图 38. 应用配置

然后,对 bookinfo 发起高频度请求,访问请求频率为 10 次/秒。

while true; do curl http://istio-ingressgateway-istio-system.apps.example.com/productpage; sleep .1; done

清单 20. 发起高频度访问请求

通过 Kiali,可以看到: 

图 39. Kiali 显示微服务流量

我们查看 Grafana,可以看到这段时间内,reviews v3 的流量,远低于同期 reviews v1 和 v2 的流量。

图 40. Grafana 显示微服务流量统计

Istio 的熔断

熔断技术,是为了避免"雪崩效应"的产生而出现的。我们都知道"雪球越滚越大"的现象。应用中的雪崩指的由于应用第一个组件的出现问题,造成调用这个组件的第二个组件有无无法调用第一个组件,无法实现业务逻辑,也出现问题;而调用第二个组件第三个组件因此也出现问题,问题迅速传播,从而造成整个应用的瘫痪,我们称之为应用的雪崩效应。

在单体应用中,多个业务的功能模块放在一个应用中,且由于各个功能模块之前是紧耦合,因此不容易出现雪崩情况。但由于微服务松耦合、各个组件调用关系复杂的特点,雪崩现象就较为容易出现。为了避免雪崩情况的发生,就需要有熔断机制,采用断路模式。熔断机制相当于为每个微服务前面加一个"保险丝"。当电流负载过大的时候(如服务访出现故障问超时,并且超过设定的重试次数),保险丝烧断,中断客户端对该应用的访问,而不影响客户端访问其他正常运行的组件。

Spring Cloud 中熔断的实现,需要调用 Hystrix。而 Istio 本身自带熔断的功能。下面,我们进行实现展现。

在初始情况下,未配置熔断。 

图 41. 配置熔断之前正常访问应用流量

接下来,我们在 Productpage 的 destination rule 中配置熔断策略(trafficPolicy):每个链接最多的请求数量是一个;最多 pending 的 request 是一个、最多的连接数是一个。

spec:

 host: productpage

 subsets:

 - labels:

     version: v1

   name: v1

 trafficPolicy:

   connectionPool:

     http:

       http1MaxPendingRequests: 1

       maxRequestsPerConnection: 1

     tcp:

       maxConnections: 1

   tls:

     mode: ISTIO_MUTUAL

清单 21. 在 destination rule 中配置熔断(open.yaml 配置文件部分内容)

接下来,我们先删除旧的配置,应用熔断的配置。 

图 42. 应用新配置

图 43. 发起大并发流量后的熔断

过一会,当问题熔断器打开后,业务恢复正常: 

图 44. 熔断后的应用访问页面

Istio 的访问控制

Istio 中的访问控制有白名单和黑名单。白名单是允许从哪个服务到哪个服务的访问;黑名单是不允许从哪个服务到哪个服务之间的访问。两种实现的效果展现是一样的,由于篇幅有限,本小节展示黑名单。

我们将在 details 服务上创建一个黑名单,从 Productpage 发往 details 请求将会返回 403 错误码。

[root@master ~]# cat acl-blacklist.yml

apiVersion: "config.istio.io/v1alpha2"

kind: denier

metadata:

 name: denycustomerhandler

spec:

 status:

   code: 7

   message: Not allowed

---

apiVersion: "config.istio.io/v1alpha2"

kind: checknothing

metadata:

 name: denycustomerrequests

spec:

---

apiVersion: "config.istio.io/v1alpha2"

kind: rule

metadata:

 name: denycustomer

spec:

 match: destination.labels["app"] == "details" && source.labels["app"]=="productpage"

 actions:

 - handler: denycustomerhandler.denier

instances: [ denycustomerrequests.checknothing ]

清单 22. 黑名单配置文件

图 45. 应用黑名单

接下来,对 Producepage 发起流量访问。从下图可以看到,从 Productpage 到 details 之间的访问是被拒绝的。 

图 46. Kali 显示应用流量访问

此时,通过浏览器访问 bookinfo,界面无法显示产品的详细信息,但其他微服务显示正常。 

图 47. 访问 bookinfo 应用

我们删除黑名单,再访问 bookinfo,对 details 微服务的访问马上正常。

图 48. 删除黑名单策略

图 49. Kali 显示应用流量访问

图 50. 访问 bookinfo 应用

总结

通过本文,相信读者对微服务的概念和 Istio 的架构有了一定程度的理解。在微服务领域,正是由于 Istio 强大的功能、丰富的界面、可视化的监控,Istio 的使用将会越来越广泛。

原文链接:https://www.ibm.com/developerworks/cn/cloud/library/cl-lo-implementing-kubernetes-microservice-using-istio/index.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容