service mesh是什么

A service mesh, like the open source project Istio, is a way to control how different parts of an application share data with one another. Unlike other systems for managing this communication, a service mesh is a dedicated infrastructure layer built right into an app. This visible infrastructure layer can document how well (or not) different parts of an app interact, so it becomes easier to optimize communication and avoid downtime as an app grows.

Each part of an app, called a “service,” relies on other services to give users what they want. If a user of an online retail app wants to buy something, they need to know if the item is in stock. So, the service that communicates with the company’s inventory database needs to communicate with the product webpage, which itself needs to communicate with the user’s online shopping cart. To add business value, this retailer might eventually build a service that gives users in-app product recommendations. This new service will communicate with a database of product tags to make recommendations, but it also needs to communicate with the same inventory database that the product page needed—it’s a lot of reusable, moving parts.

Modern applications are often broken down in this way, as a network of services each performing a specific business function. In order to execute its function, one service might need to request data from several other services. But what if some services get overloaded with requests, like the retailer’s inventory database? This is where a service mesh comes in—it routes requests from one service to the next, optimizing how all the moving parts work together.

提取重点:

A service mesh, is a way to control how different parts of an application share data with one another. Unlike other systems for managing this communication, a service mesh is a dedicated infrastructure layer built right into an app. Service mesh routes requests from one service to the next, optimizing how all the moving parts work together.

翻译一下:

服务网格是一种控制应用程序的不同部分如何共享数据的方法,不同于其他用于管理这类通信的系统,服务网格是直接内置在应用程序中的专用基础结构层。服务网格提供服务间请求的路由功能(负载均衡),从而优化所有服务的协同工作

需要了解的词

  • K8S(k8s):容器化管理服务的事实标准(K8s RoadMap
  • microservices:微服务-也称为微服务架构-是一种架构样式,可将应用程序构造为一组服务;在微服务架构下我们可以快速,频繁且可靠地交付大型,复杂的应用程序
  • 集群:一般意义上K8S的部属单位,也指一组受管理的物理机器
  • 灰度发布:给予一部分用户体验应用新版本内容的发布方式
  • PVC:本文中泛指K8S中的可灵活弹性伸缩(Persistent Volumes, Static Provisioning, awsElasticBlockStore

Reference

[1] https://K8S.io/docs/concepts/services-networking/ingress

[2] https://www.cncf.io/wp-content/uploads/2020/12/CNCF_Survey_Report_2020.pdf

[3] https://www.envoyproxy.io/docs/envoy/latest/intro/what_is_envoy

[4] https://K8S.io/docs/concepts/services-networking/ingress-controllers

[5] https://istio.io/latest/docs/reference/config/security

[6] https://github.com/envoyproxy/ratelimit

[7] https://istio.io/latest/docs/tasks/policy-enforcement/rate-limit

[8] https://istio.io/latest/docs/tasks/traffic-management/locality-load-balancing

原生K8S面临的挑战

云原生阶段,容器资源的粒度更细,利用率高,启动/销毁速度达到秒级,且大部分应用都配置了PVC;网络管理环境也发生了变更,出现Service的概念,一个微服务往往是由Deployment部署的一组弹性伸缩、动态调度的容器(Pod)承载,PodIP地址动态变化,这一组Pod一般以Service对外提供访问,流量管理是以Service为单位,且K8s支持集群内的服务注册与服务发现,以及基本的负载均衡能力

服务化拆分业务模块使得构建应用更容易,加上容器环境良好的弹性伸缩能力,DevOps理念得以很好的实施,微服务的迭代步伐加快,经常需要滚动更新。此时的入口流量管理面临如下新挑战:

  • 需要与K8s集成,支持转发流量到指定Pod
  • 更新迭代速度加快,对服务新版本灰度发布的诉求更加强烈
  • 出现集群概念,集群之间的服务发现是隔离的,接入层需支持跨集群的服务发现(即接入层可选择backend为多个集群的Pod);这区别于传统物理机 / 虚拟机阶段,没有集群隔离,只需保证网络联通性,即可配置接入层后端为任意对应服务的IP地址
  • 传统阶段到云原生阶段的迁移过程中,出现VM、容器环境混布的情况

基于上述挑战,出现了以下容器环境的接入层流量管理解决方案:

  1. K8S自带的 Ingress API:老牌网络代理(e.g. Nginx)或云厂商的负载均衡产品(e.g. AWS Elastic Load Balancer,腾讯云 CLB)都实现了各自的 Ingress Controller,作为单个集群的入口流量管理解决方案。灰度发布、鉴权限流等能力,视Ingress Controller的能力,可通过Annotation扩展,部分 Ingress Controller 还设计了自己的流量管理模型和语法
  2. Service Mesh Ingress:服务网格的服务发现和管理界限大于集群纬度,以Istio Ingress Gatewa为例,基于Istio跨集群的服务发现能力,backend可以来自不同集群的服务,同时还支持注册在网格内运行在虚拟机上的服务。Istio 也设计了自己的管理模型和语法,声明式支持配置一致的任何流量管理
  3. 沿用原有 VM 上部署的网络代理,转发流量至 VM 服务或 K8S 集群的服务

云原生接入层流量管理场景与解决方案

场景一:基础流量管理

要求接入层具有基于流量内容路由的能力(最基础的服务发现能力)

方案一:Load Balancer + NodePort

在容器化的早期阶段,应用同时部署在虚拟机和 K8S 集群上,很多用户会使用原有负载均衡(e.g. Nginx, 腾讯云 CLB)将请求分别转发到虚拟机和容器,同时受限于容器网络方案,原有负载均衡不能直接访问 Pod IP,因此需要通过 NodePort 暴露集群内的服务

但是该方案存在以下问题:

  • NodePort 端口数量有限(默认 30000-32767)
  • 随着集群规模的扩大,Nginx 配置文件越来越复杂,很难管理
  • 用户将应用发布到 K8S 集群后,需要再单独修改 Nginx 配置,体验很差

方案二:K8S Ingress

前面提到过其实这个能力K8s自身就有,K8S 提供了 Ingress API 用于暴露集群内的 HTTP 服务,Ingress 支持基于 Host 和 Path 将请求路由到不同 Service。为了让 Ingress 工作,集群必须有一个正在运行的 Ingress 控制器(e.g. Nginx Ingress Controller)。原生 Ingress 语法提供简单的基于 Host,Path 路由,以及配置 TLS 的能力

但是另一方面原生 Ingress 的功能十分有限,不能满足很多复杂场景的需求。许多第三方的 Ingress Controller [4] 通过 annotation 或新的配置模型和语法扩展了原生 Ingress 的功能,但仍然受限于集群间服务发现隔离的问题,只能作为单集群入口流量管理方案

场景二:灰度发布

服务可暴露给外部访问后,还需要考虑如何做版本发布,做平滑、无风险地迭代。常见的两种做法是按权重或流量内容切部分流量至新版本验证稳定性,无问题后逐渐过渡至新版本,也就是灰度发布以及AB test

在这方面K8S Ingress API 原生并没有灰度发布的功能,Nginx ingress controller 通过 annotation 的方式扩展了原生 Ingress API 的功能,实现了灰度发布,但这种方式并不能很好地支撑控制应用流量的发布策略,相比之下,Istio CRD 配置更灵活易用,下面介绍如何使用 Istio VirtualService 配置灰度发布路由规则

方案一:基于权重

这种方案是配置成本最低的一个方案,很简单,基于k8s的service进行路由流量:

Istio 可通过 Virtual Service 配置基于权重的灰度发布,以下是配置来自 {namespace}/{gateway} 的入口流量 95% 路由到 {service}current 版本,5% 路由到 canary 版本的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: ...
kind: VirtualService
metadata:
name: canary-weight
spec:
hosts:
- '*'
gateways:
- {namespace}/{gateway}
http:
- route:
- destination:
host: {service}
subset: current
weight: 95
- destination:
host: {service}
subset: canary
weight: 5

方案二:基于请求内容

既然能基于流量分流,那自然也应该支持基于内容的路由:

VirtualService 也支持配置基于内容的灰度发布路由规则,以下是配置来自 {namespace}/{gateway} 的入口流量 header cookie "version=stable" 时路由到 {service}current 版本,"version=canary" 时路由到 {service}canary 版本的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: ...
kind: VirtualService
metadata:
name: canary-content
spec:
hosts:
- '*'
gateways:
- {namespace}/{gateway}
http:
- match:
- headers:
cookie:
exact: version=stable
route:
- destination:
host: {service}
subset: current
- match:
- headers:
cookie:
exact: version=canary
route:
- destination:
host: {service}
subset: canary

场景三:应用流量鉴权与限流

鉴权与限流,是保证南北流量的安全性与健壮性的两个重要能力

接入层是访问后端服务的统一入口,保证接入层的安全是接入层流量管理的一个重要场景,一般在入口处需要配置认证与授权规则,传统架构下认证授权功能一般通过代码逻辑实现,比如Django的一些开源鉴权中间件

Istio自 1.5 之后提供了 AuthorizationPolicyRequestAuthentication CRD [5],可灵活配置入口层的认证和授权规则

方案一:请求身份认证(JWT)

入口处认证请求携带的 Json Web Token,放通携带合法令牌的请求,拒绝携带非法令牌的请求。

以下是使用 Istio RequestAuthentication 配置 Ingress Gateway 放通携带合法 JWT 请求的配置示例:

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: ..
kind: RequestAuthentication
metadata:
name: jwt-example
namespace: istio-system
spec:
selector:
matchLabels:
istio: ingressgateway
jwtRules:
- issuer: {issuer that issued the JWT}
jwksUri: {URL of the provider’s public key set to validate signature of the JWT}

方案二:授权

在入口处配置授权策略,根据流量内容特征,允许/拒绝流量访问,例如在入口处配置 IP 黑/白名单;或有外部鉴权服务,希望入口组件可对接外部鉴权服务,按照其返回的鉴权结果放通/拒绝流量

以下是使用 Istio AuthorizationPolicyIngress Gateway 配置 IP block 白名单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: ...
kind: AuthorizationPolicy
metadata:
name: white-list
namespace: istio-system
spec:
selector:
matchLabels:
app: istio-ingressgateway
action: ALLOW
rules:
- from:
- source:
ipBlocks: {single IP or CIDR}

Istio 1.9 增强了对 AuthorizationPolicy 对于对接外部鉴权系统的支持,可配置 Ingress Gateway 按照外部鉴权系统返回的结果放通或拒绝流量。

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: ...
kind: AuthorizationPolicy
metadata:
name: ext-authz
namespace: istio-system
spec:
selector:
matchLabels:
app: istio-ingressgateway
action: CUSTOM
provider:
name: "my-ext-authz-service"
rules: ...

方案三:限流

限流这一手段在业务规模较大时非常常见,后端服务提供给众多租户使用时,需要在入口处控制请求的速率(不然再扩容老板要鲨人了),例如限制每个 User ID 每分钟只能请求 /product 接口 100 次;为了使用Istio Ingress Gateway 的限流功能,首先需要安装 Ratelimit service,可以自行实现或直接使用社区的 ratelimit [6],然后使用 Envoyfilter 配置限流规则,具体配置方法可参考官方文档[7]

场景四:多集群异构场景入口流量管理

随着业务规模的增加,或对容灾、数据合规性、业务之间隔离要求的提升,业务会考虑与实施部署多个 K8S 集群,甚至会出现容器化环境与非容器化环境异构混布的情况,给入口流量管理又带来了一系列新的挑战

K8S 集群一般是基于容灾和业务隔离两方面的考虑:

容灾

K8S 集群有地域属性,根据应用交付提供服务的访问时效和容灾诉求,同一应用可能分布在多个不同的地理区域。多(公有)云、混合云(IDC + 公有云)架构的容灾,也需部署多个集群。跨地域多集群容灾与就近接入可通过 DNS 解析提供,但 DNS 有缓存,故障转移实际生效时间可能较长,并且无法视服务健康程度切部分流量到备份地域,只能全部切换

Istio 基于以下能力:1. 多集群服务发现能力;2. 地域感知、故障感知、容灾流量容量规划,可以实现:① 当所有集群的服务都健康时,按照请求来源地就近路由至对应服务;② 某个集群的服务出现部分故障时,视服务的健康程度转移一定比例的流量到其他集群的备份服务

业务隔离

据 CNCF 2020 云原生调查报告显示 [2],用多个集群做应用隔离是仅次于用 namespace 隔离的使用方式,使用率从 2019 年的 47% 上升到了2020年的 50%。多个业务仍共用一个流量入口时,接入层需具备多集群服务发现的能力,将流量按指定策略路由至指定集群的服务

方案一:Service Mesh Ingress

K8S Ingress Controller 遇到的一个挑战是,K8S 集群隔离了集群间的服务发现,Ingress Controller 只能作为集群级别的流量入口。而 Service Mesh 技术借助于控制面服务发现的能力,可发现或注册多个集群的服务甚至异构服务,打通集群间的服务发现壁垒,不受应用部署平台限制,天然提供一致的接入流量转发管理能力

Istio 作为最受欢迎的 Service Mesh 开源项目,它的接入层 Istio Ingress Gateway 同样提供了对 Ingress API 的支持,但是不建议使用 Ingress 去配置 Ingress Gateway,这大大削弱了 Istio 的能力。Istio 对流量管理模型提供了更高程度的抽象,可以直接使用 Istio API 实现更灵活的流量管理能力,实现灰度发布,跨集群路由,地域感知等高级特性

Istio Ingress Gateway 基于 Envoy [3] 实现,Envoy 最初由 Lyft 创建,是一款为云原生场景设计的高性能服务代理软件,后由 Lyft 捐献到了 CNCF 社区,并已从 CNCF 毕业

多 K8S 集群服务管理

Istiod 可以通过网格内所有集群的 API Server 来获取 endpoints 信息,聚合多个集群的信息后,将最终生成的配置推送到 Ingress Gateway,Ingress Gateway 可以将请求按需转发至网格内所有 Pod

地域感知负载均衡

在服务网格中,一个 Pod 的地理信息包括以下 3 个部分 [8]:

  • Region(地域):通常代表一个较大的地理区域(e.g 北京 上海),在 K8S 中,节点的地域由标签 topology.K8S.io/region 决定
  • Zone(可用区):一个地域通常包含多个可用区(e.g. 北京一区 北京二区),在 K8S 中,节点的可用区由标签 topology.K8S.io/zone 决定
  • Sub-zone :允许对可用区做进一步划分实现更细粒度的控制,例如可以按照 rack(机架)划分,在 K8S 中不存在 sub-zone 的概念,Istio 使用节点的 topology.istio.io/subzone 标签来定义 sub-zone

如果使用云厂商托管的 K8S 服务,节点的 Region 和 Zone 标签已由云厂商配置,例如在 TKE 集群中,上海二区的节点会有以下标签:

  • topology.K8S.io/region: sh
  • topology.K8S.io/zone: "200002"

网格内的集群可能分布在不同地域不同可用区,大多数情况下,我们希望尽量减少跨地域/跨可用区的请求调用,因为这会增加请求时延。因此接入层需具备感知 endpoints 地理信息的能力,并支持根据地理信息配置负载均衡及故障转移策略

地域故障转移

在开启地域负载均衡的情况下,Istio 会告知 Ingress Gateway 将请求就近转发。当所有实例都正常时,请求将保持在同一地点,当实例异常时,流量会分发到下一优先地域的实例

例如,位于 bj.bj-01 的 Ingress Gateway 转发请求的优先级如下:

优先级地理位置
1bj.bj-01Region Zone 完全匹配
2bj.bj-02Region 匹配 Zone 不匹配
3sh.sh-01/sh-02Region Zone 都不匹配
地域加权负载均衡

地域加权负载均衡可以将用户定义的一定百分比的流量分发到某些地域,例如我们可以使用如下配置分发流量:

1
2
3
4
5
6
7
8
9
global:
localityLbSetting:
enabled: true
distribute:
- from: bj/bj-01/*
to:
"bj/bj-01/*": 70
"bj/bj-02/*": 20
"sh/sh-01/*": 10

img

异构服务入口流量管理

除了多集群,用户在云原生改造的过程中,常常会面临部分服务已经做了容器化改造,运行在 K8S 集群,部分不便改造的服务仍在虚拟机的情况,甚至会有部分使用的是云厂商 serverless 云函数服务(e.g. AWS lambda)。接入层需具备异构服务注册/发现的能力,以管理异构部署服务的南北向流量

可以通过 Istio 提供的 WorkloadGroup 和 WorkloadEntry 将虚拟机上的服务注册到网格内,同一个服务可以同时运行在 K8S 集群和虚拟机上

总结

Istio Ingress Gateway 在入口灰度发布、安全、多集群异构流量管理等场景提供了多集群服务发现、地域感知、流量容量规划,以及更强大灵活的流量管理 API 的支持

但与此同时,用户也不得不面对 Istio 的复杂性;需要投入资源和人力成本运维 Istiod 和 Istio Ingress Gateway,集成 metric,trace,log 等可观测性及证书管理周边系统成本较高,还需要正确配置各种 CRD(Gateway VirtualService DestinationRule 等);但在一两年前,这也是K8S为人所诟病的地方,相信开发者们很快就能熟悉Istio的运维方式