Kubernetes 节点故障自救指南
技术
作者:Daniele Polencic
译者:夏天
2018-06-06 04:11

kube-proxy 怎么工作?

当你在 Kubernetes 中部署应用程序时,你的代码最终会在一个或多个工作节点上运行。这个节点可能是物理机,也可能是像 AWS EC2、Google Compute Engine 这样的虚拟机。

无论是哪个只要你有节点,就意味着你已经具备了跨实例高效运行和扩展应用程序的能力。如果你有一个由三个节点组成的集群,并决定把应用程序扩展为四个副本, 那 Kubernetes 会把这些副本均匀地分布在节点上,如图所示:

一旦出现故障,这个架构就会有特别好的表现。即使第一个节点不可用,另外两个节点仍然可以为应用程序提供服务。同时,Kubernetes 有足够的时间将第四个副本重新安排到另一个节点上。

更棒的是,即便所有的节点都被隔离,他们仍然可以提供流量。接下来我们将应用程序缩减为两个副本:

每个节点都可以为应用程序提供服务,那第三个节点是怎么知道它没有运行应用程序,并且必须将流量路由到其他节点的呢?

因为 Kubernetes 有一个叫做 kube-proxy  的二进制文件,它运行在每个节点上,负责将流量路由到特定的 Pod。你可以把 kube-proxy 当作一个接待员。它拦截所有指向这个节点的流量,然后再把流量路由到正确的 Pod 中去。

那么问题来了,kube-proxy 是怎么知道所有 Pod 在哪里的呢?

答案是:它不知道

但是 Master 节点知道一切,并负责创建包括在所有路由规则的列表。kube-proxy 负责检查和执行路由列表上的规则。在上述的简单情况下,路由列表如下

  • 应用程序实例 1 在节点 1 上可用
  • 应用程序实例 2 在节点 2 上可用

流量来自哪个节点并不重要。通过查看路由列表,kube-proxy 自然知道应该将流量转发到哪里。

kube-proxy 崩溃了怎么办?

那接下来你要问了:

  • 如果 kube-proxy 崩溃时会发生什么呢?
  • 如果路由列表规则丢失怎么办呢?
  • 如果没有规则转发流量时会发生什么呢?

Manabu Sakai(一名日本 SRE 工程师) 就恰好遇到这些问题。所以他决定找出答案 [1]。

首先假设你在 GCP(Google Compute Platform)上有一个两节点的集群:

通过以下命令你将 Manabu 的应用程序部署到 GCP 上:

这个应用程序很简单。它会显示网页中当前 Pod 的 hostname:

你可以用下面这个方式将部署扩展到 10 个副本: 

这 10 个副本均匀分布在两个节点上:


创建一个 Service 来负载均衡 10 个副本中的请求:

这个 Service 使用 NodePort 方式将 30000 端口暴露在外。换句话说,每个节点的 30000 端口都向公网开放,并且可以接受传入的流量。

但是,流量是如何从 30000 端口发送到我的 Pod 上来的呢?

Kube-proxy 负责制定规则,将从 30000 端口进入的流量路由到 10 个 Pod 中的一个上。你可以尝试请求 30000 端口上的节点:

请注意,你可以通过 kubectl get nodes -o wide 命令检索节点的 IP。

应用程序以 Hello World!和容器的 hostname 回复。在前面的命令中,应该有一条 Hello world! via <hostname> 命令迎接你。

如果你一直请求相同的 URL,你可能会注意到有时你会收到相同的响应,有时却会发生变化。kube-proxy 一直充当负载均衡器的角色,一直查看路由列表并持续将流量分配到 10 个 Pod 中去。

更有趣的是,你请求哪个节点并不重要。响应可能来自任何 Pod,甚至这个响应都不在你请求的同一节点上托管的 Pod 上。

要完成设置,你需要有一个将流量路由到 30000 端口的节点上的外部负载均衡器。

负载均衡器将来自公网的流量路由到两个节点之一。

如果你对我们到底有多少类似负载均衡器这件事感到困惑,那么让我们来快速回顾一下:

  • 来自公网的流量被路由到主负载均衡器上;
  • 主负载均衡器将流量转发到 30000 端口上的两个节点之一;
  • kube-proxy 设置的规则将流量从节点路由 Pod;
  • 流量到达 Pod。

哎!这个过程太漫长了!

打破一切

现在你知道事情是如何连接在一起的,让我们回到原来的问题。如果篡改路由规则会发生什么呢?

集群是否仍然有效?

Pod 是否仍然提供请求?

接下来删除路由规则。在单独的 shell 中,你可以监视应用程序的时间并删除请求。写一个每秒打印一次程序循环请求应用:

在这种情况下,你可以在第一栏中看到时间,而在另一栏中看到来自 Pod 的相应:

第一个请求是在 10:14:41 对 k8s-hello-world-55f48f8c94-vrkr9 pod 进行的。

第二次请求是在10:14:43 对 k8s-hello-world-55f48f8c94-tjg4n pod 进行的。

接下来我们从节点中删除路由规则。

kube-proxy 可以在三种模式下运行:userspace,iptables 和 ipvs。因为 Kubernetes 1.2 默认 iptables 模式。因此在 iptables 模式下,kube-proxy 使用 iptables 规则将路由规则列表写入节点。你可以登录到其中一个节点服务器并使用 iptables -F 删除 iptables 规则。

请注意,这 iptables -F 可能会干扰 SSH 连接。如果一切按计划进行,你应该有如下体验:

正如你看到的那样,从你删除 iptables 规则到下一个响应时间,从 10:14:43 到 10:15:10 需要大约 27 秒。

问题又来了:这 27 秒发生了什么?

27 秒后为什么一切恢复正常?

只是个巧合吗?让我们再刷新规则试试:

从 11:29:56 到 11:30:25 有 29 秒的时间差,但集群恢复正常。为什么需要大约 30 秒才能恢复?没有路由表,节点是否能接收流量?

也许你可以调查一下在这 30 秒内节点发生了什么。
在另一个终端中,你可以编写一个程序,每秒向应用程序发出请求。但是这次,你可以试试请求节点而不是负载均衡器:

让我们删除 iptables 规则。下面是之前的命令日志:

当删除 iptables 规则后,连接到节点的时间就会超时,这并不奇怪。更有趣的是,curl 等待 10 秒后就放弃了。在前面的例子中,如果负载均衡器正在等待连接建立呢?

这可以解释那 30 秒延迟。但是它并没有说明为什么当你等待足够长时,节点就准备好接受连接。那么为什么 30 秒后流量恢复了呢?是谁把 iptables 规则放回去了?在删除 iptables 规则之前,你可以通过以下方式来做检查:

删除规则后,你可以继续执行 iptables -F ,然后你就能发现几秒钟之后规则自己回来了!这是你的 kube-proxy 吗?是的。

当我们深度挖掘 kube-proxy 的官方文档[2]时,可以发现两个有趣的问题:

  • --iptables-sync-period 刷新 iptables 规则的最大时间间隔(例如 5s,1m,2h22m)必须大于 0。(默认 30 秒)
  • --iptables-min-sync-period 当端点和 Service 改变时(例如 5s,1m,2h22m),iptables 规则刷新的最小间隔可以被刷新。(默认10秒)

Kube-proxy 每 10 到 30 秒就刷新一次 iptables 规则。如果我们删除 iptables 规则,30 秒后 kube-proxy 才能实现并恢复。这就解释了为什么需要 30 秒节点才能恢复!

这也解释了路由表是如何从 master 节点传播到 worker 节点的。kube-proxy 负责定期同步它们。换句话说,每次添加或删除一个 Pod 时,master 节点都会重新计算路由列表。在规则的间隔内,kube-proxy 将规则同步到当前节点中。

让我们回顾一下 Kubernetes 和 kube-proxy 是如何从篡改节点上的 iptables 规则恢复的:

  • 一个请求被转发到负载均衡器并路由到该节点;
  • 节点不接受传入的请求,所以负载均衡器等待;
  • 30 秒后 kube-proxy 恢复 iptables;
  • 该节点可以再次提供流量。iptables 规则将来自负载均衡器的请求转发给 Pod;
  • 该容器以 30 秒的延迟回复负载均衡器;

对于你的应用程序来说,等待 30 秒可能是不可接受的。也许你会对调整 kube-proxy 的默认刷新间隔感兴趣 。那么究竟应该在哪里设置,又该如何改变它们呢?接下来再说说这个问题。
节点上有一个代理 - kubelet - ,它负责在每个节点上将 kube-proxy 作为静态 pod 启动。静态 Pod 的文档表明,kubelet 扫描特定文件夹并创建该文件夹中包含的所有资源。

如果你检查节点中的 kubelet 进程,就能够看到正在运行的 kubelet --pod-manifest-path=/etc/kubernetes/manifests。

运行一个简单 ls 就可以揭示事实真相:

通过 cat 命令快速查看 kube-proxy.manifest 的内容:

为保留神秘感,(我)特意没有显示完整内容。

你还是能看到如何用 --iptables-sync-period=30s ,每隔 30 秒刷新一次 iptables 规则。你可以继续并修改这个命令,用来自定义更新该节点的 iptables 规则的最短和最长时间。

我的经验

删除 iptables 规则类似于让节点不可用。流量仍然路由到节点,但该节点无法进一步转发流量。通过监视路由规则的状态并在必要时更新它们,Kubernetes 就可以从类似故障中恢复了。

非常感谢 Manabu Sakai 的博客文章,给了我非常大的启发,还要感谢 Valentin Ouvrard 对 iptables 传播问题的调查。

原文链接:https://learnk8s.io/blog/kubernetes-chaos-engineering-lessons-learned参考文献:[1]https://blog.manabusakai.com/2018/02/fault-tolerance-of-kubernetes/[2]https://kubernetes.io/docs/reference/generated/kube-proxy/

72 comCount 0