Istio从懵圈到熟练:二分之一活的微服务 Posted 2021-04-13 分布式实验室
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Istio从懵圈到熟练:二分之一活的微服务相关的知识,希望对你有一定的参考价值。
Istio is the future!基本上,我相信对云原生技术趋势有些微判断的同学,都会有这个觉悟。其背后的逻辑其实是比较简单的:当容器集群,特别是Kubernetes成为事实上的标准之后,应用必然会不断的复杂化,服务治理肯定会成为强需求。
Istio的现状是,聊的人很多,用的人其实很少。所以导致我们能看到的文章,讲道理的很多,讲实际踩坑经验的极少。
阿里云售后团队作为一线踩坑团队,分享问题排查经验,我们责无旁贷。这篇文章,我就跟大家聊一个简单Istio问题的排查过程,权当抛砖。
问题是这样的,用户在自己的测试集群里安装了Istio,并依照官方文档部署bookinfo应用来上手Istio。部署之后,用户执行kubectl get pods命令,发现所有的Pod都只有二分之一个容器是READY的。
# kubectl get pods
NAME READY STATUS RESTARTS 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网络命名空间)访问容器定义的接口,或者在容器的namespace里执行exec定义的命令,来确定容器是否就绪。
这里的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
上一节我们描述了问题现象,但是留下一个问题,就是Pod里的容器个数为什么是2。虽然每个Pod本质上至少有两个容器,一个是占位符容器pause,另一个是真正的工作容器,但是我们在使用kubectl命令获取Pod列表的时候,READY列是不包括pause容器的。
这里的另外一个容器,其实就是服务网格的核心概念sidercar。其实把这个容器叫做sidecar,某种意义上是不能反映这个容器的本质的。Sidecar容器本质上是反向代理,它本来是一个Pod访问其他服务后端Pod的负载均衡。
然而,当我们为集群中的每一个Pod,都“随身”携带一个反向代理的时候,Pod和反向代理就变成了服务网格。正如下边这张经典大图所示。这张图实在有点难画,所以只能借用,绕不过去。
所以sidecar模式,其实是“自带通信员”模式。这里比较有趣的是,在我们把sidecar和Pod绑定在一块的时候,sidecar在出流量转发时扮演着反向代理的角色,而在入流量接收的时候,可以做超过反向代理职责的一些事情。这点我们会在其他文章里讨论。
Istio在Kubernetes基础上实现了服务网格,Isito使用的sidecar容器就是第一节提到的,没有就绪的容器。所以这个问题,其实就是服务网格内部,所有的sidecar容器都没有就绪。
上一节我们看到,Istio中的每个Pod,都自带了反向代理sidecar。我们遇到的问题是,所有的sidecar都没有就绪。我们也看到readiness probe定义的,判断sidecar容器就绪的方式就是访问下边这个接口:
http : //<pod ip>:15020/healthz/ready
接下来,我们深入看下Pod,以及其sidecar的组成及原理。在服务网格里,一个Pod内部除了本身处理业务的容器之外,还有istio-proxy这个sidecar容器。正常情况下,istio-proxy会启动两个进程,pilot-agent和Envoy。
如下图,Envoy是实际上负责流量管理等功能的代理,从业务容器出、入的数据流,都必须要经过Envoy;而pilot-agent负责维护Envoy的静态配置,以及管理Envoy的生命周期。这里的动态配置部分,我们在下一节会展开来讲。
我们可以使用下边的命令进入Pod的istio-proxy容器做进一步排查。这里的一个小技巧,是我们可以以用户1337,使用特权模式进入istio-proxy容器,如此就可以使用iptables等只能在特权模式下运行的命令。
docker exec - ti - u 1337 -- privileged < istio - proxy container id > 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的错误。
了解了sidecar的代理,以及管理代理生命周期的pilot-agent进程,我们可以稍微思考一下pilot-agent应该怎么去实现healthz/ready这个接口。显然,如果这个接口返回OK的话,那不仅意味着pilot-agent是就绪的,而必须确保代理是工作的。
实际上pilot-agent就绪检查接口的实现正是如此。这个接口在收到请求之后,会去调用代理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 .
到这里,建议大家回退看下上一节的插图,在上一节我们选择性的忽略是Pilot到Envoy这条虚线,即动态配置。这里的报错,实际上是Envoy从控制面Pilot获取动态配置失败。
目前为止,这个问题其实已经很清楚了。在进一步分析问题之前,我聊一下我对控制面和数据面的理解。控制面数据面模式,可以说无处不在。我们这里举两个极端的例子。
第一个例子,是DHCP服务器。我们都知道,在局域网中的电脑,可以通过配置DHCP来获取IP地址,这个例子中,DHCP服务器统一管理,动态分配IP地址给网络中的电脑,这里的DHCP服务器就是控制面,而每个动态获取IP的电脑就是数据面。
第二个例子,是电影剧本,和电影的演出。剧本可以认为是控制面,而电影的演出,包括演员的每一句对白,电影场景布置等,都可以看做是数据面。
我之所以认为这是两个极端,是因为在第一个例子中,控制面仅仅影响了电脑的一个属性,而第二个例子,控制面几乎是数据面的一个完整的抽象和拷贝,影响数据面的方方面面。Istio服务网格的控制面是比较靠近第二个例子的情况,如下图:
Istio的控制面Pilot使用gRPC协议对外暴露接口istio-pilot.istio-system:15010,而Envoy无法从Pilot处获取动态配置的原因,是在所有的Pod中,集群DNS都无法使用。
这个问题的原因其实比较简单,在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 <none> 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
这其实是一个比较简单的问题,排查过程其实也就几分钟。但是写这篇文章,有点感觉是在看长安十二时辰,短短几分钟的排查过程,写完整背后的原理,前因后果,却花了几个小时。这是Istio文章的第一篇,希望在大家排查问题的时候,有所帮助。
原文链接:https://yq.aliyun.com/articles/717283
基于Kubernetes的DevOps战培训将于2019年10月11日在上海开课,3天时间带你系统掌握Kubernetes,学习效果不好可以继续学习 。本次培训包括:容器特性、镜像、网络;Kubernetes架构、核心组件、基本功能;Kubernetes设计理念、架构设计、基本功能、常用对象、设计原则;Kubernetes的数据库、运行时、网络、插件已经落地经验;微服务架构、组件、监控方案等,点击下方图片或者阅读原文链接查看详情。
以上是关于Istio从懵圈到熟练:二分之一活的微服务的主要内容,如果未能解决你的问题,请参考以下文章
K8s 从懵圈到熟练-集群伸缩原理
K8S 从懵圈到熟练:读懂此文,集群节点不下线!
K8s 从懵圈到熟练 – 镜像拉取这件小事
istio 三日谈之一 环境准备
基于GogRPC和Protobuf的微服务的Istio可观察性
5G下的微服务架构:拥抱NFV,Istio 1.1将支持多网络平面