- Istio is the future -
Istio 作为服务网格的典型实现,某种程度上已经成为网络技术事实上的标准。
本文选自《云原生操作系统Kubernetes》一书,分享一个阿里云线上Istio 的真实案例,并借此和大家讨论一下网格技术背后的逻辑。
1.在线一半的微服务
问题是这样的 :用户在自己的测试集群里安装了 Istio,并依照官方文档部署 bookinfo 应用。部署之后,用户执行 kubectl get pods 命令,发现所有的 Pod都只有1/2个容器是 Ready 的。
# kubectl get pods
NAME READY STATUSRESTARTS AGE
details-v1-68868454f5-94hzd 1/2 Running 0 1m
productpage-v1-5cb458d74f-28nlz 1/2 Running 0 1m
ratings-v1-76f4c9765f-gjjsc 1/2 Running 0 1m
reviews-v1-56f6855586-dplsf 1/2 Running 0 1m
reviews-v2-65c9df47f8-zdgbw 1/2 Running 0 1m
reviews-v3-6cf47594fd-cvrtf 1/2 Running 0 1m
如果从来都没有注意过 Ready 这一列的话,我们大概会有两个疑惑 :
“2”是什么意思?
“1/2”又意味着啥?
简单来讲,这里的 Ready 列,给出的是每个 Pod 内部容器的 readiness,即就绪状态。每个集群节点上的 Kubelet 会根据容器本身 readiness 规则的定义,分别以 tcp、http 或 exec 的方式,来确认对应容器的 readiness 情况。
更具体一点,Kubelet 作为运行在每个节点上的进程,以 tcp/http 的方式(从节点网络命名空间到 Pod 网络命名空间)访问容器定义的接口,或者在容器的命名空间里执行 exec 定义的命令,来确定容器是否就绪,如图1 所示。
图1 Kubelet 健康检查机制
这里的“2”说明这些 Pod 里都有两个容器,“1/2”则表示每个 Pod 里只有一个容器是就绪的,即通过 readiness 测试的。关于“2”这一点,我们一会儿再深入讲,这里我们先看一下,为什么所有的 Pod 里都有一个容器没有就绪。
使用 kubectl 工具拉取第一个 details pod 的编排模板,可以看到这个 Pod里的两个容器只有一个定义了 readiness probe。对于未定义 readiness probe 的容器,Kubelet 认为,只要容器里的进程开始运行,容器就进入就绪状态了。
所以 1/2 个就绪 Pod 意味着,有定义了 readiness probe 的容器没有通过 Kubelet的测试。
没有通过 readiness probe 测试的是 istio-proxy 这个容器。它的 readiness probe 规则定义如下。
readinessProbe:
failureThreshold: 30
httpGet:
path: /healthz/ready
port: 15020
scheme: HTTP
initialDelaySeconds: 1
periodSeconds: 2
successThreshold: 1
timeoutSeconds: 1
我们登录这个 Pod 所在的节点,用 curl 工具来模拟 Kubelet 访问下面的URI,测试 istio-proxy 的就绪状态。
# curl http://172.16.3.43:15020/healthz/ready -v
* About to connect() to 172.16.3.43 port 15020 (#0)
* Trying 172.16.3.43...
* Connected to 172.16.3.43 (172.16.3.43) port 15020 (#0)
> GET /healthz/ready HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 172.16.3.43:15020
> Accept: */*
>
< HTTP/1.1 503 Service Unavailable
< Date: Fri, 30 Aug 2019 16:43:50 GMT
< Content-Length: 0
<
* Connection #0 to host 172.16.3.43 left intact
2.认识服务网格
刚刚我们描述了问题的现象,但是留下一个问题,就是 Pod 里的容器个数为什么是 2?
虽然每个 Pod 本质上至少有两个容器,一个是占位符容器pause,另一个是真正的工作容器,但是我们在使用 kubectl 命令获取 Pod 列表的时候,Ready 列是不包括 pause 容器的。
这里的另外一个容器,其实就是服务网格的核心概念 sidecar。其实把这个容器叫作 sidecar,某种意义上是不能反映这个容器的本质的。从本质上来说,sidecar 容器是反向代理,它本来是一个 Pod 访问其他服务后端 Pod 的负载均衡。
图2 服务网格局部视角
然而,当我们让集群中的每一个 Pod 都“随身”携带一个反向代理的时候,Pod 和反向代理就变成了服务网格,正如图3 这张经典大图所示。
图3 服务网格全局视角
所以 sidecar 模式其实是“自带通信员”模式。有趣的是,在我们把sidecar 和 Pod 绑定在一起的时候,sidecar 在出流量转发时扮演着反向代理的角色,而在入流量接收的时候,可以做一些超过反向代理职责的一些事情。
Istio 在 Kubernetes 基础上实现了服务网格,Isito 使用的 sidecar 容器就是上面我们提到的没有就绪的容器。
所以这个问题其实就是,服务网格内部所有的sidecar 容器都没有就绪。
3.代理与代理的生命周期管理
在上一节中我们看到,Istio 中的每个 Pod,都自带了反向代理 sidecar。我们遇到的问题是,所有的 sidecar 都没有就绪。我们也看到 readiness probe 定义的,判断 sidecar 容器就绪的方式就是访问下面这个接口。
http://:15020/healthz/ready
接下来,我们深入理解一下 Pod,以及其 sidecar 的组成和原理。
在服务网格里,一个 Pod 内部除了本身处理业务的容器之外,还有 istio-proxy 这个sidecar 容器。正常情况下,istio-proxy 会启动两个进程,pilot-agent 和 Envoy。
下图中Envoy 是实际上负责流量管理等功能的代理,从业务容器出、入的数据流,都必须经过 Envoy ;而 pilot-agent 负责维护 Envoy 的静态配置,并且管理 Envoy 的生命周期。这里的动态配置部分,我们在下一节中会展开来讲。
图4 代理与代理生命周期管理
我们可以使用下面的命令进入 Pod 的 istio-proxy 容器做进一步排查。
小技巧:我们可以以用户 1337 身份,使用特权模式进入 istio-proxy容器,如此就可以使用 iptables 等只能在特权模式下运行的命令。
docker exec -ti -u 1337 --privileged bash
这里的用户 1337,其实是 sidecar 镜像里定义的一个同名用户 istio-proxy,默认 sidecar 容器使用这个用户。如果我们在以上命令中不使用用户选项 u,则特权模式实际上是赋予 root 用户的,所以我们在进入容器之后,需切换为 root用户执行特权命令。
进入容器之后,我们使用 netstat 命令查看监听,我们会发现,监听readiness probe 端口 15020 的,其实是 pilot-agent 进程。
istio-proxy@details-v1-68868454f5-94hzd:/$ netstat -lnpt
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:15090 0.0.0.0:* LISTEN 19/envoy
tcp 0 0 127.0.0.1:15000 0.0.0.0:* LISTEN 19/envoy
tcp 0 0 0.0.0.0:9080 0.0.0.0:* LISTEN -
tcp6 0 0 :::15020 :::* LISTEN 1/pilot-agent
我们在 istio-proxy 内部访问 readiness probe 接口,一样会得到“503”的错误。
4.就绪检查的实现
了解了 sidecar 的代理,以及管理代理生命周期的 pilot-agent 进程,我们可以稍微思考一下 pilot-agent 应该怎么去实现 healthz/ready 这个接口。显然,如果这个接口返回 OK 的话,那不仅意味着 pilot-agent 是就绪的,而且必须确保代理工作正常。
实际上 pilot-agent 就绪检查接口的实现正是如此。
图5 代理就绪检查机制的实现
这个接口在收到请求之后,会去调用代理 Envoy 的 server_info 接口。调用所使用的IP 地址是 localhost。这非常好理解,因为这是同一个 Pod 内部进程通信。使用的端口是 Envoy 的 proxyAdminPort,即 15000。
有了以上的知识准备之后,我们来看一下 istio-proxy 这个容器的日志。
实际上,在容器日志里,一直在重复输出一个报错。
这个报错分为两部分:
其中 Envoy proxy is NOT ready 部分是 pilot agent 在响应 healthz/ready 接口的时候输出的信息,即 Envoy 代理没有就绪 ;
而剩下的 config not received from Pilot (is Pilot running?): cds updates: 0 successful, 0 rejected; lds updates: 0 successful, 0 rejected 这部分,是 pilot-agent 通过 proxyAdminPort 访问 server_info 的时候带回的信息,看来 Envoy 没有办法从 Pilot 获取配置。
Envoy proxy is NOT ready: config not received from Pilot (is Pilot running?): cds updates: 0 successful, 0 rejected; lds updates: 0 successful, 0 rejected.
到这里,建议大家回头看下上文的图 4,之前我们选择性忽略了从 Pilot 到 Envoy 这条虚线,即动态配置。这里的报错,实际上是 Envoy 从控制面 Pilot 获取动态配置失败。
5.控制面和数据面
到目前为止,这个问题其实已经很清楚了。在进一步分析问题之前,我们简单聊一下对控制面和数据面的理解。控制面和数据面模式,可以说无处不在,我们举两个极端的例子。
● 第一个例子,是 DHCP 服务器。
我们都知道,在局域网中的电脑,可以通过配置 DHCP 来获取 IP 地址,在这个例子中,DHCP 服务器统一管理,动态分配 IP 地址给网络中的电脑,这里的 DHCP 服务器就是控制面,而每个动态获取 IP 的电脑就是数据面。
● 第二个例子,是电影剧本和电影的演出。
剧本可以认为是控制面,而电影的演出,包括演员的每一句对白、电影场景布置等,都可以看作数据面。
之所以认为这是两个极端,是因为在第一个例子中,控制面仅仅影响了电脑的一个属性,而在第二个例子中,控制面几乎是数据面的一个完整的抽象和拷贝,影响数据面的方方面面。Istio 服务网格的控制面是比较接近第二个例子的情况,如下。
图6 控制面和数据面
Istio 的控制面 Pilot 使用 gRPC 协议对外暴露接口 istio-pilot.istiosystem:15010,而 Envoy 无法从 Pilot 处获取动态配置的原因是,在所有的 Pod 中,集群 DNS 都无法使用。
6.简单的原因
这个问题的原因其实比较简单,在 sidecar 容器 istio-proxy 里,Envoy 不能访问 Pilot 的原因是集群 DNS 无法解析 istio-pilot.istio-system 这个服务名字。在容器里看到 resolv.conf 配置的 DNS 服务器地址是 172.19.0.10,这个是集群默认的 kube-dns 服务地址。
istio-proxy@details-v1-68868454f5-94hzd:/$ cat /etc/resolv.conf
nameserver 172.19.0.10
search default.svc.cluster.local svc.cluster.local cluster.local localdomain
但是客户删除、重建了 kube-dns 服务,且没有指定服务 IP 地址,这实际上导致集群 DNS 的地址改变了,这也是所有的 sidecar 都无法访问 Pilot 的原因。
# kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 172.19.9.54 53/UDP,53/TCP 5d
最后,通过修改 kube-dns 服务,指定 IP 地址,sidecar 恢复正常。
# kubectl get pods
NAME READY STATUS RESTARTS AGE
details-v1-68868454f5-94hzd 2/2 Running 0 6d
nginx-647d5bf6c5-gfvkm 2/2 Running 0 2d
nginx-647d5bf6c5-wvfpd 2/2 Running 0 2d
productpage-v1-5cb458d74f-28nlz 2/2 Running 0 6d
ratings-v1-76f4c9765f-gjjsc 2/2 Running 0 6d
reviews-v1-56f6855586-dplsf 2/2 Running 0 6d
reviews-v2-65c9df47f8-zdgbw 2/2 Running 0 6d
reviews-v3-6cf47594fd-cvrtf 2/2 Running 0 6d
7.阿里云服务网格(ASM)介绍
从以上案例可以看出,Istio 在提供了强大的服务治理等功能的同时,对工程师的运维开发带来了一定程度的挑战。
阿里云服务网格(Alibaba Cloud Service Mesh,简称 ASM)提供了一个全托管式的服务网格平台,极大地减轻了开发与运维人员的工作负担。
如图 7 所示,在阿里云 ASM 中,Istio 控制平面的组件全部托管,降低您使用的复杂度,您只需要专注于业务应用的开发部署。同时,保持与 Istio社区的兼容,支持声明式的方式定义灵活的路由规则,支持网格内服务之间的统一流量管理。
图7 阿里云服务网格
从能力上来看,一个托管了控制平面的 ASM 实例可以支持来自多个Kubernetes 集群的应用服务或者运行于 ECI Pod 上的应用服务。也可以把一些非 Kubernetes 服务(例如运行于虚拟机或物理裸机中的服务)集成到同一个服务网格中。
8.总结
这个案例的结论是比较简单的。基于对 Istio 的深入理解,问题排查的耗时也并不是很久,但整理这章内容,却有一种看《长安十二时辰》的感觉 :排查过程虽短,写完背后的原理和前因后果却花了好几个小时。
总之,希望本文的案例分析对大家理解服务网格技术有所帮助,同时希望阿里云服务网格可以帮助大家使服务网格类产品快速落地。
(完)
图书推荐
▊《阿里云数字新基建系列:云原生操作系统Kubernetes》
罗建龙 刘中巍 张城 黄珂 苏夏 高相林 盛训杰 著
本书是阿里云容器服务产品线上实践的技术沉淀,主要包括理论篇和实践篇两部分内容。理论篇注重理论介绍,核心是Kubernetes on Cloud,即着重介绍Kubernetes和阿里云产品的结合。实践篇是疑难问题的诊断案例,希望通过案例来和读者分享Kubernetes深度问题诊断经验。