13

使用 ReplicaSets 复制 Pods

本章涵盖

•      使用ReplicaSet对象复制Pods

•      在集群节点故障时保持 Pods 运行

•      Kubernetes 控制器中的调和控制循环

•      API 对象所有权与垃圾回收

到目前为止,在本书中,你都是通过直接创建 Pod 对象来部署工作负载的。在生产集群中,你可能需要部署相同 Pod 的几十个甚至上百个副本,因此单独创建和管理这些 Pod 会很困难。幸运的是,在 Kubernetes 中,你可以使用 ReplicaSet 对象自动创建和管理 Pod 副本。

注意  在引入 ReplicaSet 之前,类似的功能是由 ReplicationController 对象类型提供的,但它现在已被弃用。ReplicationController 的行为与 ReplicaSet 完全相同,因此本章中解释的所有内容同样适用于 ReplicationController。

在开始之前,请确保你的集群中已经存在 Kiada 套件的 Pods、Services 和其他对象。如果你按照上一章的练习操作,它们应该已经存在。如果没有,你可以通过创建 kiada 命名空间并使用以下命令应用 Chapter13/SETUP/ 目录中的所有清单来创建它们:

$ kubectl apply -f SETUP -R

注意  你可以在 https://github.com/luksa/kubernetes-in-action-2ndedition/tree/master/Chapter13 找到本章的代码文件。

13.1 介绍 ReplicaSetsReplicaSet

表示一组 Pod 副本(Pod 的精确副本)。与其一个一个地创建 Pod,你可以创建一个 ReplicaSet 对象,在其中指定 Pod 模板和所需的副本数量,然后让 Kubernetes 创建这些 Pod,如下图所示。
图 13.1 ReplicaSets 概述

ReplicaSet 允许你将 Pods 作为一个单元进行管理,但仅仅止于此。如果你想将这些 Pods 作为一个整体对外暴露,你仍然需要一个 Service 对象。如下面图所示,提供特定服务的每一组 Pods 通常需要同时拥有 ReplicaSet 和 Service 对象。

图 13.2 Services、ReplicaSets 和 Pods 之间的关系。

就像 Services 一样,ReplicaSet 的标签选择器和 Pod 标签决定了哪些 Pod 属于该 ReplicaSet。如下图所示,ReplicaSet 只关注与其标签选择器匹配的 Pod,忽略其他 Pod。

图 13.3 ReplicaSet 只关注与其标签选择器匹配的 Pod

根据目前的信息,你可能会认为只有在想要创建多个 Pod 副本时才会使用 ReplicaSet,但事实并非如此。即使你只需要创建一个 Pod,通过 ReplicaSet 创建也比直接创建更好,因为 ReplicaSet 可以确保 Pod 始终存在并执行其任务。想象一下,如果你直接为一个重要服务创建 Pod,而运行该 Pod 的节点在你不在的时候发生故障。你的服务将中断,直到你重新创建 Pod。如果你是通过 ReplicaSet 部署 Pod,它会自动重新创建 Pod。显然,通过 ReplicaSet 创建 Pod 要比直接创建更好。

然而,尽管 ReplicaSets 非常有用,但它们并不能提供运行长期工作负载所需的所有功能。在某些情况下,你可能希望将工作负载升级到新版本,这就是 ReplicaSets 的局限所在。因此,应用程序通常不是通过 ReplicaSets 部署的,而是通过允许你以声明式方式更新它们的 Deployments 部署的。由此产生了一个问题:如果你不打算使用 ReplicaSets,为何还需要了解它们?原因在于,Deployment 提供的大多数功能实际上是由 Kubernetes 在其下方创建的 ReplicaSets 提供的。Deployment 负责更新,而其余所有内容都是由底层的 ReplicaSets 处理的。因此,理解它们的功能以及工作原理非常重要。

13.1.1 创建 ReplicaSet

让我们从为 Kiada 服务创建 ReplicaSet 对象开始。该服务当前在三个 Pod 中运行,这些 Pod 是你直接从三个独立的 Pod 清单创建的,现在你将用一个单独的 ReplicaSet 清单来替换它们。在创建清单之前,让我们先看看在 spec 部分需要指定哪些字段。介绍 ReplicaSet specReplicaSet 是一个相对简单的对象。下表解释了在 ReplicaSet 的 spec 部分需要指定的三个关键字段。

表 13.1 ReplicaSet 规范中的主要字段

字段名

描述

replicas

所需的副本数量。当你创建 ReplicaSet 对象时,Kubernetes 会根据 Pod 模板创建相应数量的 Pod。它会保持这个数量的 Pod,直到你删除该 ReplicaSet。

selector

标签选择器包含 matchLabels 子字段中的标签映射,或 matchExpressions 子字段中的标签选择器要求列表。匹配该标签选择器的 Pod 被视为属于此 ReplicaSet。

template

ReplicaSet 的 Pod 模板。当需要创建新的 Pod 时,会使用此模板创建对象。

选择器和模板字段是必填项,但可以省略副本字段。如果省略,则会创建一个副本。

为 Kiada Pod 创建一个 ReplicaSet 对象清单。以下清单显示了它的样子。你可以在文件 rs.kiada.yaml 中找到该清单。

清单 13.1 Kiada ReplicaSet 对象清单

ReplicaSets 是 apps API 组的一部分,版本为 v1。如前表所述,replicas 字段指定此 ReplicaSet 应使用 template 字段中的模板创建三个 Pod 副本。你会注意到 Pod 模板中的标签与 selector 字段中的标签匹配。如果不匹配,Kubernetes API 会拒绝该 ReplicaSet,因为使用模板创建的 Pods 不会计算在期望的副本数量中,这将导致无限数量的 Pods 被创建。你是否注意到模板中没有 Pod 名称?那是因为 Pod 名称是根据 ReplicaSet 名称生成的。

模板的其余部分与你在前几章中创建的 kiada Pod 的清单完全匹配。要创建 ReplicaSet,你可以使用之前多次使用过的 kubectl apply 命令。命令如下:

13.1.2 检查 ReplicaSet 及其 Pods

要显示刚创建的 ReplicaSet 的基本信息,可以使用 kubectl get 命令,如下所示:

注意  replicaset 的简写是 rs。

该命令的输出显示所期望的副本数、当前副本数,以及就绪探针报告的被认为已就绪的副本数。此信息分别来自 ReplicaSet 对象的 replicas、fullyLabeledReplicas 和 readyReplicas 状态字段。另一个名为 availableReplicas 的状态字段表示有多少副本可用,但其值不会由 kubectl get 命令显示。如果使用 -o wide 选项运行 kubectl get replicasets 命令,会显示一些额外的非常有用的信息。运行以下命令以了解详情:

除了之前显示的列之外,这个扩展输出不仅显示了标签选择器,还显示了 Pod 模板中使用的容器名称和镜像。考虑到这些信息的重要性,令人惊讶的是在使用 kubectl get pods 列出 Pods 时并未显示这些信息。

提示  要查看容器和镜像名称,请使用 -o wide 选项列出 ReplicaSets,而不要试图从 Pods 中获取此信息。

要查看有关 ReplicaSet 的所有信息,请使用 kubectl describe 命令:

$ kubectl describe rs kiada

输出显示了 ReplicaSet 中使用的标签选择器、Pod 的数量及其状态,以及用于创建这些 Pod 的完整模板。

列出 ReplicaSet 中的 Pod

Kubectl 并没有提供直接列出 ReplicaSet 中 Pod 的方式,但你可以使用 ReplicaSet 的标签选择器,并在 kubectl get pods 命令中使用,如下所示:\

在你创建 ReplicaSet 之前,你在前面的章节中已经有三个 kiada Pods,而现在你有五个,这正是 ReplicaSet 中定义的期望副本数量。这三个已有 Pod 的标签与 ReplicaSet 的标签选择器匹配,因此被 ReplicaSet 采纳。为了确保集合中 Pod 的数量与期望副本数一致,又创建了两个额外的 Pod。

理解 ReplicaSet 中 Pods 的命名方式

如你所见,这两个新 Pod 的名称包含五个随机的字母数字字符,而不是沿用你 Pod 名称中数字的序列。Kubernetes 为其创建的对象分配随机名称是很常见的。

甚至有一个特殊的元数据字段,可以让你在不提供完整名称的情况下创建对象。你不是使用 name 字段,而是在 generateName 字段中指定名称前缀。你在第 8 章中第一次使用了这个字段,当时你多次运行 kubectl create 命令来创建多个 Pod 副本,并给每个副本一个唯一的名称。当 Kubernetes 为 ReplicaSet 创建 Pod 时,也采用相同的方法。当 Kubernetes 为 ReplicaSet 创建 Pod 时,它会将 generateName 字段设置为与 ReplicaSet 的名称匹配。然后 Kubernetes API 服务器生成完整的名称,并将其放入 name 字段中。要查看此操作,请选择创建的两个额外 Pod 中的一个,并按照以下方式检查其元数据部分:

对于 ReplicaSet Pod 来说,为 Pod 随机命名是合理的,因为这些 Pod 是彼此的精确副本,因此是可互换的。这些 Pod 之间也没有顺序的概念,所以使用连续编号是毫无意义的。即使现在 Pod 的名称看起来合理,想象一下如果你删除其中一些会发生什么。如果你乱序删除,它们的编号就不再是连续的。然而,对于有状态的工作负载,按顺序给 Pod 编号可能是合理的。这就是使用 StatefulSet 对象创建 Pod 时发生的情况。你将在第 16 章中学习更多关于 StatefulSet 的内容。

显示 ReplicaSet Pod 的日志

ReplicaSet Pod 的随机名称使它们在操作上有些困难。例如,要查看其中一个 Pod 的日志,在运行 kubectl logs 命令时输入 Pod 的名称相对繁琐。如果 ReplicaSet 只包含一个 Pod,输入完整名称似乎没有必要。幸运的是,在这种情况下,你可以按如下方式打印 Pod 的日志:

$ kubectl logs rs/kiada -c kiada

因此,你不需要指定 Pod 名称,而是输入 rs/kiada,其中 rs 是 ReplicaSet 的缩写,kiada 是 ReplicaSet 对象的名称。-c kiada 选项告诉 kubectl 打印 kiada 容器的日志。只有在 Pod 有多个容器时,你才需要使用此选项。如果 ReplicaSet 有多个 Pod,如你的情况,只会显示其中一个 Pod 的日志。如果你想查看所有 Pod 的日志,可以使用带标签选择器的 kubectl logs 命令。例如,要流式查看所有 kiada Pod 中 envoy 容器的日志,请运行以下命令:$ kubectl logs -l app=kiada -c envoy

要显示所有容器的日志,请使用 --all-containers 选项,而不是指定容器名称。当然,如果你显示多个 Pod 或容器的日志,就无法确定每一行日志来自哪里。使用 --prefix 选项可以在每行日志前加上 Pod 和容器的名称,例如:

$ kubectl logs -l app=kiada --all-containers –prefix

查看来自多个 Pod 的日志在流量分配到多个 Pod 时非常有用,因为你可以查看收到的每个请求,而不管是哪个 Pod 处理的。例如,尝试使用以下命令进行日志流式传输:$ kubectl logs -l app=kiada -c kiada --prefix -f现在在你的网页浏览器中或使用 curl 打开应用程序。使用 Ingress、LoadBalancer 或 NodePort 服务,如前两章所述。

13.1.3 理解 Pod 所有权

Kubernetes 根据你在 ReplicaSet 对象中指定的模板创建了两个新的 Pod。它们由 ReplicaSet 拥有和控制,就像你手动创建的三个 Pod 一样。当你使用 kubectl describe 命令检查 Pod 时,可以看到这一点。例如,可以按如下方式检查 kiada-001 Pod:

kubectl describe 命令从 Pod 清单的元数据部分获取此信息。让我们仔细看看。运行以下命令:

对象清单中的元数据部分有时包含 ownerReferences 字段,该字段包含对对象拥有者的引用。此字段可以包含多个拥有者,但大多数对象只有一个拥有者,就像 kiada-001 Pod 一样。在这个 Pod 的例子中,kiada ReplicaSet 是拥有者,而 Pod 则是所谓的从属对象。Kubernetes 有一个垃圾回收器,当拥有者被删除时,会自动删除从属对象。如果一个对象有多个拥有者,当所有拥有者都不存在时,该对象才会被删除。如果你删除拥有 kiada-001 及其他 Pods 的 ReplicaSet 对象,垃圾回收器也会删除这些 Pods。拥有者引用还可以指示哪个拥有者是对象的控制器。kiada-001 Pod 由 kiada ReplicaSet 控制,清单中的 controller: true 行就表明了这一点。这意味着你不应再直接控制这三个 Pods,而应通过 ReplicaSet 对象进行管理。

13.2 更新 ReplicaSet

在 ReplicaSet 中,你需要指定所需的副本数量、Pod 模板和标签选择器。选择器是不可变的,但你可以更新其他两个属性。通过更改所需的副本数量,你可以对 ReplicaSet 进行扩缩容。让我们看看这样做会发生什么。

13.2.1 扩缩容 ReplicaSet

在 ReplicaSet 中,你已将所需的副本数量设置为五个,这也是当前由 ReplicaSet 拥有的 Pod 数量。然而,你现在可以更新 ReplicaSet 对象以更改此数量。你可以通过更改清单文件中的值并重新应用,或使用 kubectl edit 命令直接编辑对象来实现。不过,扩缩容 ReplicaSet 最简单的方法是使用 kubectl scale 命令。

使用 kubectl scale 命令扩展 ReplicaSet

让我们将 kiada Pod 的数量增加到六个。为此,请执行以下命令:

现在再次检查ReplicaSet,确认它现在有6个pod:

这些列显示 ReplicaSet 现在配置了六个 Pod,这也是当前 Pod 的数量。其中一个 Pod 还没有准备好,但只是因为它刚刚创建。再次列出 Pod 以确认已创建了一个额外的 Pod 实例:

如预期,一个新的 Pod 被创建,使 Pod 的总数达到了所需的六个。如果这个应用程序服务于实际用户,并且由于流量增加你需要扩展到一百个或更多 Pod,你可以用相同的命令迅速做到。但是,你的集群可能无法处理那么多 Pod。

缩减扩容

就像你扩展一个 ReplicaSet 一样,你也可以用相同的命令缩减它。你也可以通过编辑其清单文件来扩展或缩减 ReplicaSet,使用命令 kubectl edit。让我们使用这种方法将其扩展到四个副本。运行以下命令:

$ kubectl edit rs kiada

这应该会在你的文本编辑器中打开 ReplicaSet 对象清单。找到 replicas 字段并将其值改为 4。保存文件并关闭编辑器,以便 kubectl 可以将更新后的清单发布到 Kubernetes API。验证你现在有四个 Pod:

正如预期的那样,其中两个 Pod 正在被终止,当它们容器中的进程停止运行时,这些 Pod 应该会消失。但 Kubernetes 如何决定删除哪些 Pod 呢?它只是随机选择吗?了解在缩减 ReplicaSet 时哪些 Pod 会先被删除当你缩减一个 ReplicaSet 时,Kubernetes 会遵循一些深思熟虑的规则来决定首先删除哪些 Pod。它按以下顺序删除 Pod:

1. 尚未分配到节点的 Pod。

2. 状态未知的 Pod。

3. 尚未准备就绪的 Pod。

4. 删除成本较低的 Pod。

5. 与更多相关副本共置的 Pod。

6. 已准备就绪时间较短的 Pod。

7. 容器重启次数较多的 Pod。

8. 创建时间晚于其他 Pod 的 Pod。

这些规则确保尚未被调度的 Pod 以及有缺陷的 Pod 会被优先删除,而运行良好的 Pod 则保持不变。你还可以通过在 Pod 上设置注解 `controller.kubernetes.io/pod-deletion-cost` 来影响哪个 Pod 会先被删除。该注解的值必须是一个可以解析为 32 位整数的字符串。没有该注解的 Pod 以及注解值较低的 Pod 会先于注解值较高的 Pod 被删除。Kubernetes 还会尝试保持 Pod 在集群节点之间的均匀分布。下图展示了一个将 ReplicaSet 从五个副本扩缩到三个副本的示例。由于第三个节点运行的副本比另外两个节点多两个,因此第三个节点上的 Pod 会先被删除。如果不存在此规则,你可能会在单个节点上得到三个副本。

图 13.4 Kubernetes 保持相关 Pod 在集群节点之间均匀分布。

缩减到零

在某些情况下,将副本数量缩减到零是有用的。所有由 ReplicaSet 管理的 Pod 都会被删除,但 ReplicaSet 对象本身将保留,并且可以随时再次扩展。你现在可以通过运行以下命令来尝试:

正如你将在下一章看到的,当 ReplicaSet 由 Deployment 对象拥有时,将其缩放为零是非常常见的。提示如果你需要临时关闭工作负载的所有实例,请将期望的副本数设置为零,而不是删除 ReplicaSet 对象。

13.2.2 更新 Pod 模板

在下一章中,你将学习 Deployment 对象,它与 ReplicaSets 在处理 Pod 模板更新的方式上有所不同。正是这种差异使得你通常使用 Deployment 而不是 ReplicaSets 来管理 Pods。因此,了解 ReplicaSets 无法完成的操作非常重要。

编辑 ReplicaSet 的 Pod 模板

kiada Pods 当前具有表示应用名称和发布类型(无论是稳定版本还是其他类型)的标签。如果有一个标签能表示确切的版本号就更好了,这样在同时运行不同版本时,你可以轻松区分它们。

要向 ReplicaSet 创建的 Pods 添加标签,必须将标签添加到其 Pod 模板中。不能使用 kubectl label 命令添加标签,因为那样会将标签添加到 ReplicaSet 本身,而不是 Pod 模板中。没有 kubectl 命令可以直接完成此操作,因此必须像之前一样使用 kubectl edit 编辑清单。找到 template 字段,并在模板的 metadata.labels 字段中添加标签键 ver,值为 0.5,如下示例所示。

清单 13.2 向 Pod 模板添加标签

确保将标签添加到正确的位置。不要将其添加到选择器中,因为这会导致 Kubernetes API 拒绝你的更新,因为选择器是不可变的。版本号必须用引号括起来,否则 YAML 解析器会将其解释为十进制数字,更新将失败,因为标签值必须是字符串。保存文件并关闭编辑器,以便 kubectl 可以将更新的清单提交到 API 服务器。

注意  你是否注意到 Pod 模板中的标签和选择器中的标签并不完全相同?它们不必完全相同,但选择器中的标签必须是模板中标签的子集。

了解 ReplicaSet 的 Pod 模板是如何使用的你更新了 Pod 模板,现在检查更改是否反映在 Pods 中。按如下方式列出 Pods 及其标签:

由于这些 Pods 仍然只有来自原始 Pod 模板的两个标签,很明显 Kubernetes 并没有更新这些 Pods。然而,如果你现在将 ReplicaSet 的副本数量增加一个,新创建的 Pod 应该会包含你添加的标签,如下所示:

你应该把 Pod 模板看作是 Kubernetes 用来制作新 Pod 的饼干切模。当你更改 Pod 模板时,只有饼干切模会改变,这只会影响之后创建的 Pod。

13.3 理解 ReplicaSet 控制器的操作

在前面的章节中,你已经看到,改变 ReplicaSet 对象中的副本数和模板会导致 Kubernetes 对属于该 ReplicaSet 的 Pods 执行某些操作。执行这些操作的 Kubernetes 组件被称为控制器。通过集群的 API 创建的绝大多数对象类型都有一个关联的控制器。例如,在上一章中,你学习了 Ingress 控制器,它管理 Ingress 对象。还有 Endpoints 控制器用于 Endpoints 对象,Namespace 控制器用于 Namespace 对象,等等。

不出所料,ReplicaSets 由 ReplicaSet 控制器管理。你对 ReplicaSet 对象所做的任何更改,都会被该控制器检测并处理。当你扩展 ReplicaSet 时,是控制器负责创建或删除 Pods。每次执行这些操作时,它还会创建一个 Event 对象,通知你它所做的操作。正如你在第四章中学到的,你可以在 kubectl describe 命令的输出底部看到与对象相关的事件,如下一个代码片段所示,也可以通过使用 kubectl get events 命令专门列出 Event 对象。

要理解 ReplicaSets,你必须理解它们的控制器的操作。

13.3.1 介绍调和控制循环如下图所示,控制器会观察所有者对象和依赖对象的状态。在状态发生每次变化后,控制器会将依赖对象的状态与所有者对象中指定的期望状态进行比较。如果这两种状态不一致,控制器会对依赖对象进行修改以调和两者状态。这就是所谓的调和控制循环,你会在所有控制器中发现。

图 13.5 控制器的调和控制循环

ReplicaSet 控制器的协调控制循环包括观察 ReplicaSet 和 Pod。每当 ReplicaSet 或 Pod 发生变化时,控制器会检查与该 ReplicaSet 关联的 Pod 列表,并确保 Pod 的实际数量与 ReplicaSet 中指定的期望数量相匹配。如果实际 Pod 数量低于期望数量,它会根据 Pod 模板创建新的副本。如果 Pod 数量高于期望数量,它会删除多余的副本。下图中的流程图解释了整个过程。

图 13.6 ReplicaSet 控制器的协调循环

13.3.2 了解 ReplicaSet 控制器如何响应 Pod 变化

你已经看到控制器如何立即响应 ReplicaSet 的 replicas 字段的变化。然而,这并不是唯一导致期望数量和实际 Pod 数量不一致的情况。如果没有人操作 ReplicaSet,但实际的 Pod 数量发生了变化怎么办?ReplicaSet 控制器的工作是确保 Pod 的数量始终与指定数量相匹配。因此,在这种情况下,它也应该发挥作用。

删除由 ReplicaSet 管理的 Pod

让我们看看如果删除由 ReplicaSet 管理的其中一个 Pod,会发生什么。选择一个并使用 kubectl delete 将其删除:

现在再次列出pod:

你删除的 Pod 已经消失,但一个新的 Pod 已经出现来替代缺失的 Pod。Pod 的数量再次与 ReplicaSet 对象中设置的期望副本数相匹配。同样,ReplicaSet 控制器会立即作出反应,将实际状态与期望状态进行调节。即使你删除所有 kiada Pod,也会立即出现三个新的 Pod,以便继续为你的用户提供服务。你可以通过运行以下命令看到这一点:

$ kubectl delete pod -l app=kiada

创建一个与 ReplicaSet 标签选择器匹配的 Pod

就像 ReplicaSet 控制器在发现 Pod 数量少于需求时会创建新的 Pod 一样,当它发现 Pod 数量过多时,也会删除 Pod。当你减少副本的期望数量时,你已经见过这种情况,但如果你手动创建一个与 ReplicaSet 标签选择器匹配的 Pod,会发生什么呢?从控制器的角度来看,其中一个 Pod 必须消失。让我们创建一个名为 one-kiada-too-many 的 Pod。该名称与控制器分配给 ReplicaSet Pod 的前缀不匹配,但该 Pod 的标签与 ReplicaSet 的标签选择器匹配。你可以在文件 pod.one-kiada-too-many.yaml 中找到该 Pod 的清单。使用 kubectl apply 应用清单以创建 Pod,然后立即按如下方式列出 kiada Pod:

正如预期的那样,一旦 ReplicaSet 控制器检测到 Pod,它会立即删除该 Pod。控制器不喜欢你创建与 ReplicaSet 标签选择器匹配的 Pod。如图所示,Pod 的名称无关紧要,只有 Pod 的标签才重要。

当运行 ReplicaSet Pod 的节点发生故障时,会发生什么?

在之前的示例中,你看到了当有人篡改 ReplicaSet 的 Pod 时,ReplicaSet 控制器是如何反应的。虽然这些示例很好地说明了 ReplicaSet 控制器的工作原理,但它们并没有真正展示使用 ReplicaSet 运行 Pod 的真正好处。通过 ReplicaSet 创建 Pod 而不是直接创建 Pod 的最佳理由是,当集群节点发生故障时,Pod 会被自动替换。

警告  在下一个示例中,将导致集群节点发生故障。在配置不当的集群中,这可能会导致整个集群故障。因此,只有在你愿意在必要时从零重新构建集群的情况下,才应执行此操作。

要观察节点停止响应时会发生什么,你可以禁用其网络接口。如果你使用 kind 工具创建了你的集群,可以使用以下命令禁用 kind-worker2 节点的网络接口:

$ docker exec kind-worker2 ip link set eth0 down

注意  选择一个至少运行了一个你的 kiada Pods 的节点。使用 -o wide 选项列出 Pods,以查看每个 Pod 运行在哪个节点上。

注意  如果你使用的是 GKE,你可以使用 gcloud compute ssh 命令登录节点,并使用 sudo ifconfig eth0 down 命令关闭其网络接口。ssh 会话将停止响应,因此你需要按回车键,然后输入“~.”(波浪号和点,不带引号)来关闭它。

很快,表示集群节点的 Node 对象的状态会变为 NotReady。

此状态表示节点上运行的 Kubelet 已经有一段时间未与 API 服务器联系。由于这并不明确表明节点已宕机,因为这可能只是暂时的网络故障,因此不会立即影响节点上运行的 Pods 的状态。它们仍将显示为正在运行。然而,经过几分钟后,Kubernetes 会发现节点已宕机,并将这些 Pods 标记为删除。

注意  节点不可用到其 Pods 被删除之间的时间可以通过 Taints(污点)和 Tolerations(容忍)机制进行配置,相关内容在第 23 章中有详细说明。

一旦 Pods 被标记为删除,ReplicaSet 控制器会创建新的 Pods 来替代它们。你可以在以下输出中看到这一点。

如你在输出中所见,位于 kind-worker2 节点的两个 Pod 已被标记为终止,并已被调度到健康节点 kind-worker 的两个新 Pod 所替代。同样,ReplicaSet 中指定的三个 Pod 副本正在运行。正在删除的两个 Pod 将保持在终止状态,直到节点重新上线。实际上,这些 Pod 中的容器仍在运行,因为节点上的 Kubelet 无法与 API 服务器通信,因此不知道它们应当被终止。然而,当节点的网络接口重新上线时,Kubelet 会终止这些容器,并删除 Pod 对象。以下命令可以恢复节点的网络接口:

你的集群可能使用的网关IP不是172.18.0.1。执行如下命令查找:

注意  如果你正在使用 GKE,必须使用 gcloud compute instances reset <节点名称> 命令远程重置节点。

什么时候 Pods 不会被替换?

前面的章节已经演示了 ReplicaSet 控制器确保总是有与 ReplicaSet 对象中指定数量相同的健康 Pod。但是情况总是这样吗?是否可能出现一种状态,即 Pod 的数量与期望的副本数匹配,但这些 Pods 无法为其客户端提供服务?

还记得存活性(liveness)和就绪性(readiness)探针吗?如果容器的存活性探针失败,容器会被重启。如果探针失败多次,容器重启前会有较长的时间延迟。这是由于第6章中解释的指数退避机制造成的。在退避延迟期间,容器不会运行。然而,假设容器最终会恢复服务。如果容器未通过就绪性探针而不是存活性探针,也假设问题最终会被解决。因此,那些容器持续崩溃或未通过探针的 Pod 永远不会被自动删除,即使 ReplicaSet 控制器可以轻松地用可能正常运行的 Pod 替换它们。因此,需要注意的是,ReplicaSet 并不保证始终拥有与 ReplicaSet 对象中指定的数量相同的健康副本。

你可以通过使用以下命令失败一个pod的准备探测来自己看到这一点:

注意如果在运行 kubectl exec 命令时指定的是 ReplicaSet 而不是 Pod 名称,指定的命令将只在其中一个 Pod 中运行,而不是在所有 Pod 中运行,就像使用 kubectl logs 一样。大约三十秒后,kubectl get pods 命令会显示其中一个 Pod 的容器不再处于就绪状态:

该 Pod 不再接收来自客户端的任何流量,但 ReplicaSet 控制器并未删除和替换它,尽管它知道三个 Pod 中只有两个是就绪且可访问的,如 ReplicaSet 状态所示:

重要提示

ReplicaSet 只确保所需数量的 Pod 存在。它并不能保证这些 Pod 的容器实际上正在运行并准备好处理流量。如果在真实的生产集群中发生这种情况,而剩余的 Pod 无法处理所有流量,你需要自己删除有问题的 Pod。但如果你想先找出 Pod 出了什么问题怎么办?如何在不删除 Pod 的情况下快速替换故障 Pod,以便你进行调试?你可以将 ReplicaSet 扩容一个副本,但在调试完有问题的 Pod 后,你又必须缩减回原来的规模。幸运的是,有一种更好的方法。下一节将会讲解这一方法。

13.3.3 将 Pod 从 ReplicaSet 的控制中移除

你已经知道,ReplicaSet 控制器会持续确保匹配 ReplicaSet 标签选择器的 Pod 数量与预期的副本数一致。因此,如果你将某个 Pod 从匹配选择器的 Pod 集合中移除,控制器会替换它。要做到这一点,你只需更改有问题的 Pod 的标签,如下图所示。

图 13.7 更改 Pod 的标签以将其从 ReplicaSet 中移除

ReplicaSet 控制器会用一个新的 Pod 替换掉旧的 Pod,从那时起,它就不再关注有问题的 Pod。你可以在新的 Pod 接管流量的同时,冷静地弄清楚它的问题所在。我们来尝试操作上一节中你使就绪探针失败的 Pod。要使一个 Pod 匹配 ReplicaSet 的标签选择器,它必须具有标签 app=kiada 和 rel=stable。没有这些标签的 Pod 不会被视为 ReplicaSet 的一部分。因此,要将损坏的 Pod 从 ReplicaSet 中移除,需要移除或更改这两个标签中的至少一个。一种方法是将 rel 标签的值更改为 debug,如下所示:

$ kubectl label po kiada-78j7m rel=debug --overwrite

由于现在只有两个 Pod 匹配标签选择器,比所需的副本数少一个,控制器会立即创建另一个 Pod,如下输出所示:

正如你可以从 APP 和 REL 列中的值看到的,三个 Pod 符合选择器,而损坏的 Pod 不符合。这个 Pod 不再由 ReplicaSet 管理。因此,当你检查完该 Pod 后,需要手动删除它。

注意  当你从 ReplicaSet 中移除一个 Pod 时,对应 ReplicaSet 对象的引用将从该 Pod 的 ownerReferences 字段中移除。

现在你已经看到 ReplicaSet 控制器如何响应本节和前面章节中显示的所有事件,你已经了解了关于该控制器所需知道的一切。

13.4 删除 ReplicaSetReplicaSet

表示一组作为单元管理的 Pod 副本。通过创建 ReplicaSet 对象,你表明希望在集群中根据特定的 Pod 模板拥有一定数量的 Pod 副本。通过删除 ReplicaSet 对象,你表明不再需要这些 Pod。因此,当你删除 ReplicaSet 时,属于它的所有 Pods 也会被删除。这是由垃圾回收器完成的,如本章前面所述。

13.4.1 删除 ReplicaSet 及其所有关联的 Pods

要删除 ReplicaSet 及其控制的所有 Pods,请运行以下命令:

正如预期的那样,这也会删除pod:

但在某些情况下,你并不希望如此。那么如何防止垃圾回收器删除这些 Pods 呢?在我们讨论这个问题之前,请通过重新应用 rs.kiada.versionLabel.yaml 文件来重新创建 ReplicaSet。

13.4.2 在删除 ReplicaSet 的同时保留 Pods在本章开头,你已经了解到 ReplicaSet 中的标签选择器是不可变的。如果你想更改标签选择器,就必须删除原有的 ReplicaSet 对象并创建一个新的。然而,在这样做的过程中,你可能不希望 Pods 被删除,因为那样会导致你的服务不可用。幸运的是,你可以告诉 Kubernetes 将 Pods 设为孤立(orphan)而不是删除它们。要在删除 ReplicaSet 对象时保留 Pods,请使用以下命令:$ kubectl delete rs kiada --cascade=orphan #Areplicaset.apps "kiada" deleted现在,如果你列出 Pods,会发现它们仍然存在。如果查看它们的 manifest,你会注意到 ReplicaSet 对象已从 ownerReferences 中移除。这些 Pods 现在变为孤立状态,但如果你创建一个具有相同标签选择器的新 ReplicaSet,它将收养这些 Pods。再次应用 rs.kiada.versionLabel.yaml 文件来查看此操作效果。

13.5 总结

在本章,你已经学到:

  • ReplicaSet 表示一组相同的 Pod,你可以将其作为一个单元进行管理。在 ReplicaSet 中,你需要指定 Pod 模板、所需副本数量以及标签选择器。
  • 几乎所有 Kubernetes API 对象类型都有一个关联的控制器来处理该类型的对象。在每个控制器中,都会运行一个协调控制循环,不断将实际状态与期望状态进行协调。
  • ReplicaSet 控制器确保实际的 Pod 数量始终与 ReplicaSet 中指定的期望数量相匹配。当这两个数字出现偏差时,控制器会立即通过创建或删除 Pod 对象来进行协调。
  • 你可以随时更改所需的副本数量,控制器将采取必要的措施以满足你的请求。然而,当你更新 Pod 模板时,控制器不会更新现有的 Pods。
  • 由 ReplicaSet 创建的 Pods 由该 ReplicaSet 拥有。如果你删除所有者,依赖对象将被垃圾回收器删除,但你可以指示 kubectl 将它们设为孤立状态。

在下一章中,你将用 Deployment 对象替换 ReplicaSet。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐