写点什么

深入浅出 Kubernetes 实践篇 (五):二分之一活的微服务

  • 2020-03-30
  • 本文字数:4961 字

    阅读完需:约 16 分钟

深入浅出Kubernetes 实践篇 (五):二分之一活的微服务

简介:Istio is the future!基本上,我相信对云原生技术趋势有些微判断的同学,都会有这个觉悟。其背后的逻辑其实是比较简单的:当容器集群,特别是 Kubernetes 成为事实上的标准之后,应用必然会不断的复杂化,服务治理肯定会成为强需求。


Istio is the future!基本上,我相信对云原生技术趋势有些微判断的同学,都会有这个觉悟。其背后的逻辑其实是比较简单的:当容器集群,特别是 Kubernetes 成为事实上的标准之后,应用必然会不断的复杂化,服务治理肯定会成为强需求。


Istio 的现状是,聊的人很多,用的人其实很少。所以导致我们能看到的文章,讲道理的很多,讲实际踩坑经验的极少。


阿里云售后团队作为一线踩坑团队,分享问题排查经验,我们责无旁贷。这篇文章,我就跟大家聊一个简单 Istio 问题的排查过程,权当抛砖。

二分之一活的微服务

问题是这样的,用户在自己的测试集群里安装了 Istio,并依照官方文档部署 bookinfo 应用来上手 Istio。部署之后,用户执行 kubectl get pods 命令,发现所有的 pods 都只有二分之一个容器是 READY 的。


# kubectl get podsNAME READY STATUS RESTARTS AGEdetails-v1-68868454f5-94hzd 1/2 Running 0 1mproductpage-v1-5cb458d74f-28nlz 1/2 Running 0 1mratings-v1-76f4c9765f-gjjsc 1/2 Running 0 1mreviews-v1-56f6855586-dplsf 1/2 Running 0 1mreviews-v2-65c9df47f8-zdgbw 1/2 Running 0 1mreviews-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 -lnptActive Internet connections (only servers)Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program nametcp        0      0 0.0.0.0:15090           0.0.0.0:*               LISTEN      19/envoytcp        0      0 127.0.0.1:15000         0.0.0.0:*               LISTEN      19/envoytcp        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.confnameserver 172.19.0.10search default.svc.cluster.local svc.cluster.local cluster.local localdomain
复制代码


但是客户删除重建了 kube-dns 服务,且没有指定服务 IP,这导致,实际上集群 dns 的地址改变了,这也是为什么所有的 sidecar 都无法访问 Pilot。


# kubectl get svc -n kube-systemNAME                      TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)                      AGEkube-dns                  ClusterIP      172.19.9.54     <none>          53/UDP,53/TCP                5d
复制代码


最后,通过修改 kube-dns 服务,指定 IP 地址,sidecar 恢复正常。


# kubectl get podsNAME READY STATUS RESTARTS AGEdetails-v1-68868454f5-94hzd 2/2 Running 0 6dnginx-647d5bf6c5-gfvkm 2/2 Running 0 2dnginx-647d5bf6c5-wvfpd 2/2 Running 0 2dproductpage-v1-5cb458d74f-28nlz 2/2 Running 0 6dratings-v1-76f4c9765f-gjjsc 2/2 Running 0 6dreviews-v1-56f6855586-dplsf 2/2 Running 0 6dreviews-v2-65c9df47f8-zdgbw 2/2 Running 0 6dreviews-v3-6cf47594fd-cvrtf 2/2 Running 0 6d
复制代码

结论

这其实是一个比较简单的问题,排查过程其实也就几分钟。但是写这篇文章,有点感觉是在看长安十二时辰,短短几分钟的排查过程,写完整背后的原理,前因后果,却花了几个小时。这是 Istio 文章的第一篇,希望在大家排查问题的时候,有所帮助。


作者简介


罗建龙(花名声东),阿里云技术专家。多年操作系统和图形显卡驱动调试和开发经验。目前专注云原生领域,容器集群和服务网格。


相关阅读


深入浅出Kubernetes 实践篇 (一):节点就绪问题之一


深入浅出Kubernetes 实践篇 (二):节点就绪问题之二


深入浅出Kubernetes 实践篇 (三):命名空间删除问题


深入浅出Kubernetes 实践篇 (四):集群安全组配置管理


2020-03-30 17:141268

评论

发布
暂无评论
发现更多内容

一次搞懂SpringBoot核心原理:自动配置、事件驱动、Condition

热爱java的分享家

Java 程序人生 Spring Boot 编程语言 经验分享

[Pulsar] 消息生命历程(三)——客户端发送消息

Zike Yang

Apache Pulsar 11月日更

清华元宇宙深度报告!理论框架产业真相一文看懂

CECBC

被Chrome坑惨了!

Jackpop

分析23个赛季NBA数据,我发现10条难以置信的信息!

Jackpop

2021RSAC -- 网络韧性

Tom(⊙o⊙)

【死磕Java并发】-----J.U.C之AQS:同步状态的获取与释放

chenssy

11月日更 死磕 Java 死磕 Java 并发

贪心:柠檬水找零、跳跃游戏🍋

空城机

JavaScript 贪心算法 11月日更

Spring中的Controller和Service是线程安全的吗?我有点懵

热爱java的分享家

Java spring 程序人生 编程语言 经验分享

Java 项目中使用 Resilience4j 框架实现客户端 API 调用的限速/节流机制

码语者

Java 限速 节流 Resilience4j RateLimit

一个对标VSCode的IDE---Fleet

IT蜗壳-Tango

11月日更

linux之strings命令

入门小站

Linux

这次不怕撕坏了,区块链电子公告来了

CECBC

Golang Gin 框架之分组路由(五)

liuzhen007

11月日更

模块四作业:设计千万级学生系统的试卷存储方案

Geek_99eefd

模块四 「架构实战营」

华为,告别大陆军时代

脑极体

EF Core如何处理多对多关系

喵叔

11月日更

Windows 11再出杀手锏....

Jackpop

App 端自动化的最佳方案,完全解放双手!

星安果

Python 自动化 爬虫

什么是元宇宙?为何要关注它?

CECBC

🏆【Alibaba中间件技术系列】「RocketMQ技术专题」让我们一起探索一下DefaultMQPullConsumer的实现原理及源码分析

洛神灬殇

阿里巴巴 RocketMQ 消息队列 11月日更 Apache RocketMQ

Spring Bean生命周期你除了会背八股文面试,真的会用了吗?

热爱java的分享家

Java spring 程序人生 编程语言 经验分享

分布式系统的架构演进过程(一)

卢卡多多

分布式, 11月日更

23 K8S之Secret资源配置

穿过生命散发芬芳

k8s 11月日更

【高并发】由InterruptedException异常引发的思考

冰河

Java 并发编程 多线程 高并发 异步编程

2018年世界杯德国竟然输给韩国?终于找到原因了!

Jackpop

Prometheus Exporter (八)kube-state-metrics

耳东@Erdong

Kubernetes Prometheus exporter 11月日更 kube-state-metrics

GitHub霸榜月余的24万字Java面试手册,竟是阿里机密

热爱java的分享家

Java 面试 程序人生 编程语言 经验分享

华为顶级安全专家分享出来这份‘典藏版’Linux开发实战笔记

热爱java的分享家

Java 架构 程序人生 编程语言 经验分享

在线文本行固定长度填充工具

入门小站

工具

CWE 4.6 和 OWASP TOP10(2021)

Tom(⊙o⊙)

深入浅出Kubernetes 实践篇 (五):二分之一活的微服务_文化 & 方法_罗建龙(声东)_InfoQ精选文章