❝作者:delphisfang,目前就职于腾讯音乐/社区产品部/公共框架团队,从事微服务、服务网格等云原生架构的开发与落地。 本文约6000字,预计阅读完毕需要15分钟。
1. 前言
本文从 Service Mesh 开发者的角度,阐述 istio 进阶的个人最佳实践,希望帮助正在入门 istio 的同学节省一点时间,少走一点弯路。
1.1 本文出发点
学会 istio[1] 或 envoy[2] 需要多久?是一周、两周,还是一个月?笔者无法给出准确的答案,那取决于每个人对“学会” 这个标准的定义。网上有不少介绍 istio 的入门文章,如果只是想快速地完成一篇「Service Mesh 综述」的话,看看过往的文章应该已经足够。不过按照笔者的经验,看完之后大概会达到一种浅尝辄止的状态:能说上那么两句,但是仅此而已,不能立即投入线上开发,也无法调试定位具体的问题。如果想要更深入一步,则需要去啃那些「大部头书籍」,花费的时间会成倍增长。
因此,笔者撰写本文的出发点是,尝试找到入门文章与大部头书籍之间的一个平衡,以一篇文章的篇幅,帮助初学者达到「玩转istio」的阶段。也可以说是完成对istio的祛魅,为 istio 的初学者提供一把打开 Service Mesh 进阶之路的钥匙。希望它既不是一篇「入门介绍」,也不是一本巨细靡遗、主次不分的「参考手册」,而是一篇只讲重点、只讲笔者自身经验的最佳实践。
1.2 何为「最佳实践」?
“最佳实践” 这个名词起源于管理领域,但是已经有一些被滥用了。我们可以在网上搜到各式各样的「某某最佳实践」。然而事实上,“最佳实践” 无疑是一个伪命题。这个世界上永远不可能有什么工程实践是最佳的,除非你是 Jeff Dean(冷笑话)。实事求是地说,本文介绍的只是一种还算不错的、可以快速上手的实践。
1.3 何为「进阶」?
我们经常看到有人在简历里写 “熟练掌握某某技术”、“精通某某技术”。那么,到底什么程度才能算精通?精通的那根标准线在哪里?「进阶开发」的英文是 Advanced Programming。Advanced 既有高级的意思,也有先进的含义。高级是指,掌握了这个事物的一些不为初学者所知的、精微的技术细节,能让你完成别人完成不了的工作,或者比别人更快地完成。先进则是指,这个事物本身带来了一些更新颖的、更现代化的想法或理念,比如:Google 在2006年发布的三驾马车论文、Docker 带来的容器思想、Kubernetes 引入的声明式API。
2. 最佳实践的一点方法论
在厘清本文的定位以及标题关键词的含义之后,就可以开始正文部分了。
在面对一个庞大的系统时,我们该如何去掌握它?像 Kubernetes[3] 这种活跃度超级高的开源项目,其代码行数通常是以百万计的,没有人能够清楚地记得所有的细节,也没有这个必要。本文并不会 case by case 地去讲解 istio/envoy 的每一个功能或特性,而是尝试介绍一种通用的工程实践方法,让开发者在需要研究某一项特性时,知道如何快速切入、纵向深挖与横向发散,并快速得出结论、验证原型,最终应用于生产环境。
2.1 三重证据学习法
历史学领域有一个著名的研究方法:三重证据法,即:「传世文献 · 出土文字 · 出土文物」三方相互印证,构成严密的证据链,从而推断出某个历史的真相。在这里我们提出:可以使用类似的思路学习和掌握复杂的技术系统,即:「官方文档 · 源码 · 原型实验」 三位一体,构成一个完整的学习闭环。
2.2 去除不必要的流程或组件
该说法出自Elon Musk 的视频采访 Five Step Improvement Process[4] 中的第2步:Remove unnecessary process,它更广为人知的名字是:奥卡姆剃刀原则。早在2012年,微信之父张小龙在《微信背后的产品观》的讲座中,就不断提到类似的理念:保持简单。我们也认为,在学习乃至设计一个东西的过程中,需要不断地追问:
- 哪些东西是必不可少的,哪些只是锦上添花?
- 哪些东西是新颖的,哪些是业界已有的套路?
这个步骤可以大幅降低系统的复杂度和理解成本,为我们节省出大量的时间。拨开表面的那些枝叶,我们才能看清一个东西的骨架,即 “原型”。
2.3 关键词定位
Talk is cheap, Dive into the code. 与启发式的AI神经网络算法相比,大多数工程类的项目都有一个优点:它们的一切奥秘都在源码之中,其行为以及背后驱动的代码都是确定无疑、可以被解释的。这意味着:你一定能在源码中定位到它、读懂它,乃至修改它。如果某个项目的源码在可读性方面做得还还不赖的话,你就可以用关键词定位法快速地锁定代码段,大大节省你的时间。
以 istio 和 envoy 为例,两者均已发展成为数十万行代码的项目。但是得益于高度模块化、可扩展的设计,两者的代码在 可读性 与 内聚度 两方面都达到了很高的水平,这使得关键词检索法能够发挥最大的威力。你甚至不需要一个解析代码的IDE,仅靠grep大法也可以锁定某个特性背后的核心代码。
综合上述几点方法论,可以总结出一套进阶最佳实践:
1. 预设问题:带着问题出发,逐个寻找答案,在有限时间内收敛闭环,避免无限发散。
2. 文档调研:了解目标对象是什么、不是什么、边界在哪里、竞品有哪些,它在更大的Landscape中的位置。
3. 原型搭建:搭建目标系统的架构原型,设计实验,验证其行为是否符合自己预期。
4. 深入源码:修改它、重编它、运行它、调试它,让它按照你所指挥的那样去工作。
为了避免本文演变成虚无的、形而上的坐而论道,方法论部分点到为止。
3. istio进阶最佳实践案例
下面以「istio 就近地域路由」这个特性为例,展示这套最佳实践的具体操作过程。「就近地域路由」是一个粒度适中的特性,既不大也不小,很适合用于展示 istio进阶 的最佳实践。
3.1 预设问题
下面的问题是逐层递进的。有些只需要做文档调研就能回答;有些则需要深入源码,甚至运行实验才能得出确切的结论。尽管不同人提出的问题可能会非常不同,但是笔者认为问题列表应该存在一个最低标准,即:将这些问题紧密串联起来,应该要能回答整个架构的每一环是如何工作的,即:每一个环节的输入是什么、输出是什么。
Q1:何为Locality?
Q2:希望就近地域路由如何表现?
Q3:如何驱动envoy的就近地域路由?需要如何配置?
Q4:如何驱动istio的就近地域路由?需要如何配置?
Q5:istio如何获知某个envoy的locality?
Q6:就近地域路由的核心算法是怎么样的?在istio层还是envoy层实现?
3.2 文档调研
3.2.1 何为Locality?
在云计算领域,Locality 通常指二元组 <Region, Availability Zone>,即:<地域, 可用区>。此概念起源于云计算大厂 Amazon[5]。【地域】的划分,是为了尽量靠近当地的用户,使用户获得较低的接入时延;不同地域之间只能通过公网互通。【可用区】的划分,是为了冗余和容灾;可用区之间的网络是互通的,距离在100KM以内,且采用高速光纤相连。这意味着,用户连到A区 或 连到B区,在时延上的差别几乎可以忽略。通常一个可用区就是一个数据中心(即一个机房),规模较大的可用区也可以由多个距离靠近的数据中心组成,形成庞大的服务器集群。
Kubernetes 沿袭了云厂商的提法,支持 Region、AZ 两个级别[6]。而在 istio 中,Locality 则是一个三元组:<Region, Zone, Sub-zone>,即:<地域,可用区,子区>。按照 Istio 官方 [6] 的说法,Sub-zone是为了做更精细的划分,比如:划分到 “机架” 的粒度。
3.2.2 istio就近地域路由策略:Locality failover
很明显,对于【就近路由】这个特性,最直接的诉求就是:istio应该总是尝试将流量转发给距离主调方最近的目标服务实例。这是一个不断做降级 (Failover) 的过程:
- 如果有与主调方处在同一个 region/zone/sub-zone 的被调实例,则 istio 优先路由给这些实例;否则转到第 2 步。
- 如果有与主调方处在同一个 region/zone 的被调实例,则 istio 优先路由给这些实例;否则转到第 3 步。
- 如果有与主调方处在同一个 region 的被调实例,则 istio 优先路由给这些实例;否则转到第 4 步。
- 将流量路由给其他实例。
在 istio 中,该策略被称为 Locality failover,具体的例子可以参考文献[8]。
3.3 istio原型搭建
3.3.1 理解 istio 架构原型
本节将向读者展示 istio架构[9] 真正必不可少的部件(即原型)是什么,以及在自助学习进阶istio的阶段,我们可以暂时不去关注哪些内容,只聚焦在最核心的部件上。本质上,只有 envoy 与 pilot 两个组件是必不可少的。当然,如果您有充足的时间,建议您还是部署一套 Kubernetes 集群,并在其上部署一套完整的istio系统。
一个完整的 istio service mesh 架构[10] 如上图所示,包含几个要点:
- 整套 istio 必须部署运行于 Kubernetes 之上。
- 主调方与被调方均需部署 istio proxy,分别接管 outbound流量 和 inbound流量。
- istio 的控制面 istiod 包含 Pilot、Citadel、Galley 三个主要的子模块,分别用于下发xDS配置给envoy、管理证书、为其他控制面组件生成配置。
简化理解istio架构:
- 如今minikube、kind等工具已经使得部署一个 Kubernetes 集群变得非常简单,然而要想在K8s上调试一个istio 集群,您仍然需要具备操纵 K8s Deployment、Pod、ConfigMap 等资源的前置能力。当您需要验证某个 istio 特性时,K8s 这一层的存在,将使你所花费的时间成倍地增长。
- istio 默认要求在主、被调双方都部署 proxy (即envoy),以接管一切出、入流量。然而如果仔细分析,除了全局限流 (global ratelimit)、服务间调用鉴权 (service authentication) 等几个特性之外,大多数流量治理能力都可以只由 主调方proxy 承担。即是说,架构可以简化为只部署主调方的 proxy。
- 在istio控制面的三个组件中,pilot是必不可少的,我们需要利用它给数据面的envoy下发xDS配置,驱动envoy按照我们指挥的那样去工作。
- 综上三点,我们可以得出一套 简化的istio service mesh架构,便于学习-调试-验证阶段的快速切入,如下图所示。
3.3.2 理解envoy层的地域配置:locality endpoint
由第2节的调研结果可知:envoy并不会直接去计算每个目标实例与自己的距离远近,而是由 istio 告知 envoy 每个实例的优先级 (priority);envoy只需要将流量优先转发给 priority值最小的那些实例。
在 envoy 层面,使用 locality endpoint 表示一批拥有相同locality的实例;而locality 和 priority 都是 locality endpoint 的属性,分别用于指明这批实例的 地理位置 和 优先级。
Envoy locality endpoint 配置示例如下:
代码语言:javascript复制- name: helloworld
connect_timeout: 0.1s
type: static
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: helloworld
endpoints:
- priority: // 第1个实例,位于华南-深圳-01,优先级为0 (最高)
locality:
region: south-china
zone: shenzhen
sub_zone: 01
lb_endpoints:
- endpoint:
address:
socket_address:
address: 1.1.1.1
port_value: 12345
- priority: // 第2个实例,位于华南-深圳-02,优先级为1
locality:
region: south-china
zone: shenzhen
sub_zone: 02
lb_endpoints:
- endpoint:
address:
socket_address:
address: 2.2.2.2
port_value: 12345
- priority: // 第3个实例,位于华南-广州-01,优先级为2
locality:
region: south-china
zone: guangzhou
sub_zone:
lb_endpoints:
- endpoint:
address:
socket_address:
address: 3.3.3.3
port_value:
- priority: // 第4个实例,位于华东-上海-01,优先级为3 (最低)
locality:
region: east-china
zone: shanghai
sub_zone:
lb_endpoints:
- endpoint:
address:
socket_address:
address: 4.4.4.4
port_value:
3.3.3 理解istio层的地域配置:DestinationRule
istio 层面的【地域路由】称作 Locality failover,需要使用 DestinationRule [19] 进行配置才能启用。如下面的yaml配置所示,该 DestinationRule 描述了 helloworld 服务的3个核心策略,分别是:connectionPool (连接参数)、loadBalancer (负载均衡策略)、outlierDetection(熔断踢除策略)。其中与【就近地域路由】相关的配置就是第2项:localityLbSetting。需要注意的是:必须同时有 outlierDetection 配置项,locality failover 才能生效。
istio DestinationRule locality failover 配置示例如下:
代码语言:javascript复制apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: helloworld
namespace: demo-namespace
spec:
host: helloworld.svc.cluster.local
trafficPolicy:
connectionPool:
http:
maxRequestsPerConnection: 100
loadBalancer:
simple: ROUND_ROUBIN
localityLbSetting: // 启用 locality failover 策略
enabled: true
failover: []
outlierDetection: // 必须有该配置项,locality策略才能生效
consecutive5xxErrors:
interval: 5s
baseEjectionTime: 1m
3.4 深入istio源码
3.4.1 读懂istio地域路由的核心算法
源码路径:https://github.com/istio/istio/blob/master/pilot/pkg/networking/core/v1alpha3/loadbalancer/loadbalancer.go#L158
在 istio 源码中,尝试检索关键词 Locality 和 Failover,可以看到:确实有一个名为 applyLocalityFailover 的函数(如上述源码所示)。阅读该函数的注释,不难发现:该函数正是 Locality Failover 的核心算法实现。该函数通过计算 endpoint locality 与 envoy locality 的匹配程度,得出 endpoint 的 priority。当然,这个priority值仅仅只是针对该envoy来说的。换成另一个envoy,计算出来的endpoint priority就可能完全不同。
核心算法如下:
- 若 region/zone/sub-zone 三层均匹配,则将实例的priority设置为0。
- 若仅 region/zone 两层匹配,则将实例的priority设置为1。
- 若仅 region 匹配,则将实例的priority设置为2。
- 若均不匹配,则将实例的priority设置为3。
3.4.2 istio 如何获知每个服务实例的 Locality?
我们知道,每一个 istio Service Entry [17] 都用于唯一表示一个服务,包括隶属于该服务的所有实例 (endpoint)。而 Locality 又是 endpoint 的一个属性。因此,实例的Locality一定存在于 ServiceEntry 对象中,如下面的yaml配置所示。istio 通过解析 ServiceEntry 对象,即可获知每个实例的Locality。
Service Entry locality 配置示例如下:
代码语言:javascript复制apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
name: helloworld
namespace: demo-namespace
spec:
hosts:
- helloworld.svc.cluster.local
ports:
- number:
name: helloworld
protocol: HTTP
resolution: static
endpoints:
- address: 1.1.1.1
locality: south-china/shenzhen/01
- address: 2.2.2.2
locality: south-china/shenzhen/02
- address: 3.3.3.3
locality: south-china/guangzhou/01
- address: 4.4.4.4
locality: east-china/shanghai/01
3.4.3 istio 如何获知每个 envoy 的 Locality?
源码路径:https://github.com/istio/istio/blob/master/pilot/pkg/model/context.go
如上面的 istio 源码所示:在 istio 中,每一个连接到它的 envoy 实例都由一个Proxy对象唯一表示,其中就包含Locality属性。而 Locality 的取值就存放在该envoy实例的 bootstrap 启动配置文件中。
4. istio进阶知识点
1. envoy 并不依赖 容器 与 Kubernetes,可以部署在虚拟机、物理机乃至裸金属环境上。
2. envoy 也不依赖 istio,而是反过来,istio 选择了 envoy 作为数据面组件。
3. 在 envoy 层面,服务、实例、路由配置、监听器 等所有资源都被定义为 xDS API [14],可以动态获取。当然,这些资源也可以静态配置在 envoy bootstrap 配置文件 [15] 中。在调试验证阶段,采用静态配置的方式显然会方便很多。
4. 在 istio 层面,沿袭了 K8s 的设计,使用 CRD (Custom Resource Definition [16]) 表示各种资源。其中,最核心的CRD有3个:
- istio Service Entry [17] 用于定义一个服务及其下的实例列表。
- istio Virtual Service [18] 用于定义一个服务的流量路由策略 (traffic routing)。
- istio Destination Rule [19] 用于定义一个服务的关键参数,包括:连接参数、负载均衡策略、故障踢除策略。
5. 虽然 istio 控制面包含有若干个子模块:pilot、citadel、galley,但在大多数应用场景下,只有 pilot 是必不可少的。一套最简单的 Istio Service Mesh 可以只部署 pilot 和 envoy,让 envoy 连接到 pilot 动态拉取资源。更极致地,你可以只部署 envoy,将实验所需的资源都写在 envoy bootstrap config 中。
6. 虽然 envoy 的代码量已经超过了 70w 行,istio 的代码量超过了 30w 行,但是不必担心,也不要企图搞清楚每一处细节。你甚至不需要一款全功能的IDE,有需要时可以直接搜索关键词。
7. 虽然本文的标题是「istio进阶最佳实践」,不过本实践路径并不局限于学习 istio,也适用于其他开源技术 (尤其是云原生技术) 的学习与进阶。
5. istio 进阶技术文档
[1] The istio service mesh.
https://istio.io/latest/about/service-mesh/
[2] Envoy proxy.
https://www.envoyproxy.io/
[3] Kubernetes Github.
https://github.com/kubernetes/kubernetes
[4] Elon Musk Five Step Improvement Process.
https://www.youtube.com/watch?v=Jgw-_hlFQk4
[5] AWS Regions & Zones.
https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html
[6] K8s Topology Aware Routing.
https://kubernetes.io/docs/concepts/services-networking/topology-aware-routing/
[7] Istio Locality Load Balancing.
https://istio.io/latest/docs/tasks/traffic-management/locality-load-balancing/
[8] Istio Locality failover.
https://istio.io/latest/docs/tasks/traffic-management/locality-load-balancing/failover/
[9] Istio architecture.
https://istio.io/latest/zh/docs/ops/deployment/architecture/
[10] Istio Locality weighted distribution.
https://istio.io/latest/docs/tasks/traffic-management/locality-load-balancing/distribute/
[11] Envoy Locality weighted load balancing.
https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/load_balancing/locality_weight
[12] Envoy Priority Levels.
https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/load_balancing/priority#arch-overview-load-balancing-priority-levels
[13] Envoy Advanced.
https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/advanced
[14] Envoy xDS API.
https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/dynamic_configuration
[15] Envoy bootstrap config example.
https://www.envoyproxy.io/docs/envoy/latest/configuration/overview/examples
[16] Kubernetes CRD.
https://kubernetes.io/zh-cn/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/
[17] Istio Service Entry.
https://istio.io/latest/docs/reference/config/networking/service-entry/
[18] Istio Virtual Service.
https://istio.io/latest/docs/reference/config/networking/virtual-service/
[19] Istio Destination Rule.
https://istio.io/latest/docs/reference/config/networking/destination-rule/