ReplicaSet

ReplicaSet 的目的是维护在任何给定时间运行的副本 Pod 的稳定集合。通常,你通过定义 Deployment 并让 Deployment 自动管理 ReplicaSet。

ReplicaSet 的目的是维护在任何给定时间运行的副本 Pod 的稳定集合。因此,它通常用于保证指定数量的相同 Pod 可用。

ReplicaSet 如何工作

ReplicaSet 使用一些字段进行定义,其中包括一个用于指定如何识别它可以拥有的 Pod 的选择器、一个表示应维护的 Pod 数量的副本数字,以及一个用于指定为满足副本数量要求而创建的新 Pod 的数据的 Pod 模板。然后,ReplicaSet 通过根据需要创建和删除 Pod 来实现其目的,以达到期望的数量。当 ReplicaSet 需要创建新的 Pod 时,它使用其 Pod 模板。

ReplicaSet 通过 Pod 的 metadata.ownerReferences 字段与其 Pod 链接,该字段指定当前对象由哪个资源所拥有。ReplicaSet 所拥有的所有 Pod 的 ownerReferences 字段中都包含其所属 ReplicaSet 的标识信息。ReplicaSet 通过此链接了解其维护的 Pod 的状态并相应地进行规划。

ReplicaSet 使用其选择器来标识要拥有的新 Pod。如果存在一个没有 OwnerReference 或 OwnerReference 不是 控制器(Controller) 且与 ReplicaSet 的选择器匹配的 Pod,它将立即被该 ReplicaSet 拥有。

何时使用 ReplicaSet

ReplicaSet 确保在任何给定时间运行指定数量的 Pod 副本。然而,Deployment 是一个更高级别的概念,它管理 ReplicaSet 并为 Pod 提供声明式更新以及许多其他有用的功能。因此,除非你需要自定义更新编排或根本不需要更新,否则我们建议使用 Deployment 而不是直接使用 ReplicaSet。

这实际上意味着你可能永远不需要直接操作 ReplicaSet 对象:请改用 Deployment,并在其 spec 部分定义你的应用。

示例

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
  labels:
    app: guestbook
    tier: frontend
spec:
  # modify replicas according to your case
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:
      labels:
        tier: frontend
    spec:
      containers:
      - name: php-redis
        image: us-docker.pkg.dev/google-samples/containers/gke/gb-frontend:v5

将此清单保存到 frontend.yaml 文件中并提交到 Kubernetes 集群,将创建所定义的 ReplicaSet 及其管理的 Pod。

kubectl apply -f https://kubernetes.ac.cn/examples/controllers/frontend.yaml

然后你可以获取当前部署的 ReplicaSet

kubectl get rs

并查看你创建的 frontend ReplicaSet

NAME       DESIRED   CURRENT   READY   AGE
frontend   3         3         3       6s

你还可以检查 ReplicaSet 的状态

kubectl describe rs/frontend

你将看到类似如下的输出

Name:         frontend
Namespace:    default
Selector:     tier=frontend
Labels:       app=guestbook
              tier=frontend
Annotations:  <none>
Replicas:     3 current / 3 desired
Pods Status:  3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:  tier=frontend
  Containers:
   php-redis:
    Image:        us-docker.pkg.dev/google-samples/containers/gke/gb-frontend:v5
    Port:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age   From                   Message
  ----    ------            ----  ----                   -------
  Normal  SuccessfulCreate  13s   replicaset-controller  Created pod: frontend-gbgfx
  Normal  SuccessfulCreate  13s   replicaset-controller  Created pod: frontend-rwz57
  Normal  SuccessfulCreate  13s   replicaset-controller  Created pod: frontend-wkl7w

最后,你可以检查创建的 Pod

kubectl get pods

你应该看到类似如下的 Pod 信息

NAME             READY   STATUS    RESTARTS   AGE
frontend-gbgfx   1/1     Running   0          10m
frontend-rwz57   1/1     Running   0          10m
frontend-wkl7w   1/1     Running   0          10m

你还可以验证这些 Pod 的所有者引用是否设置为 frontend ReplicaSet。为此,获取一个正在运行的 Pod 的 yaml

kubectl get pods frontend-gbgfx -o yaml

输出将类似于此,其中 frontend ReplicaSet 的信息设置在 metadata 的 ownerReferences 字段中

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2024-02-28T22:30:44Z"
  generateName: frontend-
  labels:
    tier: frontend
  name: frontend-gbgfx
  namespace: default
  ownerReferences:
  - apiVersion: apps/v1
    blockOwnerDeletion: true
    controller: true
    kind: ReplicaSet
    name: frontend
    uid: e129deca-f864-481b-bb16-b27abfd92292
...

非模板 Pod 获取

虽然你可以毫无问题地创建“裸” Pod(即没有控制器管理的 Pod),但强烈建议确保这些裸 Pod 没有与你 ReplicaSet 的选择器匹配的标签。原因是 ReplicaSet 不仅限于拥有其模板指定的 Pod,它还可以按照前面章节指定的方式获取其他 Pod。

以前面的 frontend ReplicaSet 示例为例,以及以下清单中指定的 Pod

apiVersion: v1
kind: Pod
metadata:
  name: pod1
  labels:
    tier: frontend
spec:
  containers:
  - name: hello1
    image: gcr.io/google-samples/hello-app:2.0

---

apiVersion: v1
kind: Pod
metadata:
  name: pod2
  labels:
    tier: frontend
spec:
  containers:
  - name: hello2
    image: gcr.io/google-samples/hello-app:1.0

由于这些 Pod 没有控制器(或任何对象)作为其所有者引用,并且与 frontend ReplicaSet 的选择器匹配,它们将立即被该 ReplicaSet 拥有。

假设你在 frontend ReplicaSet 已经部署并设置了其初始 Pod 副本以满足其副本数量要求之后创建这些 Pod

kubectl apply -f https://kubernetes.ac.cn/examples/pods/pod-rs.yaml

新创建的 Pod 将被 ReplicaSet 拥有,然后立即被终止,因为 ReplicaSet 的 Pod 数量将超过其期望的数量。

获取 Pod

kubectl get pods

输出显示新创建的 Pod 要么已经被终止,要么正在被终止。

NAME             READY   STATUS        RESTARTS   AGE
frontend-b2zdv   1/1     Running       0          10m
frontend-vcmts   1/1     Running       0          10m
frontend-wtsmm   1/1     Running       0          10m
pod1             0/1     Terminating   0          1s
pod2             0/1     Terminating   0          1s

如果你先创建 Pod

kubectl apply -f https://kubernetes.ac.cn/examples/pods/pod-rs.yaml

然后再创建 ReplicaSet

kubectl apply -f https://kubernetes.ac.cn/examples/controllers/frontend.yaml

你将会看到 ReplicaSet 已经拥有了这些 Pod,并且只按照其 spec 创建了新的 Pod,直到其新创建的 Pod 数量与原始 Pod 的数量之和达到其期望的数量。当获取 Pod 时,

kubectl get pods

其输出将显示

NAME             READY   STATUS    RESTARTS   AGE
frontend-hmmj2   1/1     Running   0          9s
pod1             1/1     Running   0          36s
pod2             1/1     Running   0          36s

通过这种方式,ReplicaSet 可以拥有一个非同构的 Pod 集合

编写 ReplicaSet 清单

与所有其他 Kubernetes API 对象一样,ReplicaSet 需要 apiVersionkindmetadata 字段。对于 ReplicaSet,kind 始终是 ReplicaSet。

当控制平面为 ReplicaSet 创建新的 Pod 时,ReplicaSet 的 .metadata.name 是命名这些 Pod 的基础之一。ReplicaSet 的名称必须是一个有效的 DNS 子域名 值,但这可能会对 Pod 主机名产生意外结果。为了获得最佳兼容性,名称应遵循 DNS 标签 的更严格规则。

ReplicaSet 还需要一个 .spec

Pod 模板

.spec.template 是一个Pod 模板,其中也需要设置标签。在我们的 frontend.yaml 示例中,我们有一个标签:tier: frontend。请注意不要与其它控制器的选择器重叠,以免它们尝试采用此 Pod。

对于模板的重启策略字段 .spec.template.spec.restartPolicy,唯一允许的值是 Always,这也是默认值。

Pod 选择器

.spec.selector 字段是一个标签选择器。正如前面讨论的,这些标签用于识别潜在的 Pod。在我们的 frontend.yaml 示例中,选择器是

matchLabels:
  tier: frontend

在 ReplicaSet 中,.spec.template.metadata.labels 必须与 spec.selector 匹配,否则将被 API 拒绝。

副本数

你可以通过设置 .spec.replicas 来指定应并发运行多少个 Pod。ReplicaSet 将创建/删除其 Pod 以匹配此数量。

如果你没有指定 .spec.replicas,则默认值为 1。

使用 ReplicaSet

删除 ReplicaSet 及其 Pod

要删除 ReplicaSet 及其所有 Pod,请使用kubectl delete。默认情况下,垃圾收集器会自动删除所有依赖的 Pod。

当使用 REST API 或 client-go 库时,必须在 -d 选项中将 propagationPolicy 设置为 BackgroundForeground。例如:

kubectl proxy --port=8080
curl -X DELETE  'localhost:8080/apis/apps/v1/namespaces/default/replicasets/frontend' \
  -d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Foreground"}' \
  -H "Content-Type: application/json"

仅删除 ReplicaSet

你可以使用带有 --cascade=orphan 选项的 kubectl delete 命令删除 ReplicaSet 而不影响其任何 Pod。当使用 REST API 或 client-go 库时,必须将 propagationPolicy 设置为 Orphan。例如:

kubectl proxy --port=8080
curl -X DELETE  'localhost:8080/apis/apps/v1/namespaces/default/replicasets/frontend' \
  -d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Orphan"}' \
  -H "Content-Type: application/json"

原始 ReplicaSet 被删除后,你可以创建一个新的 ReplicaSet 来替换它。只要旧的 ReplicaSet 和新的 ReplicaSet 的 .spec.selector 相同,新的 ReplicaSet 就会收养旧的 Pod。但是,它不会尝试使现有 Pod 与新的、不同的 Pod 模板匹配。要以受控的方式将 Pod 更新到新的 spec,请使用 Deployment,因为 ReplicaSet 不直接支持滚动更新。

终止 Pod

特性状态: Kubernetes v1.33 [alpha] (默认禁用: false)

你可以通过在 API 服务器kube-controller-manager 上设置 DeploymentReplicaSetTerminatingReplicas 特性门控 来启用此特性。

由于删除或缩容而变为终止状态的 Pod 可能需要很长时间才能终止,并且在此期间可能会消耗额外的资源。因此,所有 Pod 的总数可能会暂时超过 .spec.replicas。可以通过 ReplicaSet 的 .status.terminatingReplicas 字段跟踪正在终止的 Pod。

将 Pod 从 ReplicaSet 中隔离

你可以通过更改 Pod 的标签将其从 ReplicaSet 中移除。此技术可用于将 Pod 从服务中移除以进行调试、数据恢复等。通过这种方式移除的 Pod 将自动被替换(假设副本数量没有改变)。

伸缩 ReplicaSet

通过简单地更新 .spec.replicas 字段,可以轻松地对 ReplicaSet 进行扩缩容。ReplicaSet 控制器确保与标签选择器匹配的期望数量的 Pod 可用且正在运行。

缩容时,ReplicaSet 控制器根据以下通用算法对可用的 Pod 进行排序,以确定优先删除哪些 Pod:

  1. Pending(待定)状态(且不可调度)的 Pod 首先被缩容。
  2. 如果设置了 controller.kubernetes.io/pod-deletion-cost 注解,则值较低的 Pod 将排在前面。
  3. 位于具有更多副本的节点上的 Pod 比位于具有更少副本的节点上的 Pod 排在前面。
  4. 如果 Pod 的创建时间不同,则创建时间较新的 Pod 排在创建时间较旧的 Pod 前面(创建时间以整数对数刻度进行分桶)。

如果以上所有条件都匹配,则随机选择。

Pod 删除开销

特性状态: Kubernetes v1.22 [beta]

使用 controller.kubernetes.io/pod-deletion-cost 注解,用户可以设置缩容 ReplicaSet 时优先移除哪些 Pod。

该注解应设置在 Pod 上,取值范围为 [-2147483648, 2147483647]。它表示与其他属于同一 ReplicaSet 的 Pod 相比,删除该 Pod 的开销。具有较低删除开销的 Pod 优先于具有较高删除开销的 Pod 被删除。

对于未设置此注解的 Pod,其隐式值为 0;允许使用负值。无效值将被 API 服务器拒绝。

此特性为 Beta 版本,默认启用。你可以通过在 kube-apiserver 和 kube-controller-manager 中使用 特性门控 PodDeletionCost 来禁用它。

示例用例

应用的各个 Pod 可能具有不同的利用率水平。在缩容时,应用可能倾向于移除利用率较低的 Pod。为避免频繁更新 Pod,应用应在发出缩容指令前更新一次 controller.kubernetes.io/pod-deletion-cost(将注解设置为与 Pod 利用率水平成比例的值)。如果应用本身控制缩容(例如,Spark 部署的 driver Pod),则此方法有效。

ReplicaSet 作为 Horizontal Pod Autoscaler 的目标

ReplicaSet 也可以作为Horizontal Pod Autoscaler (HPA) 的目标。也就是说,ReplicaSet 可以被 HPA 自动扩缩容。以下是一个针对我们在前一个示例中创建的 ReplicaSet 的 HPA 示例。

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: frontend-scaler
spec:
  scaleTargetRef:
    kind: ReplicaSet
    name: frontend
  minReplicas: 3
  maxReplicas: 10
  targetCPUUtilizationPercentage: 50

将此清单保存到 hpa-rs.yaml 文件中并提交到 Kubernetes 集群,将创建所定义的 HPA,它会根据复制的 Pod 的 CPU 使用率自动扩缩目标 ReplicaSet。

kubectl apply -f https://k8s.io/examples/controllers/hpa-rs.yaml

或者,你可以使用 kubectl autoscale 命令来实现相同的目的(而且更容易!)。

kubectl autoscale rs frontend --max=10 --min=3 --cpu-percent=50

ReplicaSet 的替代方案

Deployment 是一种对象,它可以拥有 ReplicaSet 并通过声明式、服务器端滚动更新来更新 ReplicaSet 及其 Pod。虽然 ReplicaSet 可以独立使用,但如今它们主要被 Deployment 用作协调 Pod 创建、删除和更新的机制。当你使用 Deployment 时,无需担心管理它们创建的 ReplicaSet。Deployment 拥有并管理其 ReplicaSet。因此,当你需要 ReplicaSet 时,建议使用 Deployment。

裸 Pod

与用户直接创建 Pod 的情况不同,ReplicaSet 会替换因任何原因删除或终止的 Pod,例如节点故障或干扰性节点维护(如内核升级)。因此,即使你的应用只需要单个 Pod,我们也建议使用 ReplicaSet。可以将其类比于进程管理器,只是它监督跨多个节点的多个 Pod,而不是单个节点上的单个进程。ReplicaSet 将本地容器重启委托给节点上的某个代理,例如 Kubelet。

Job

对于预期会自行终止的 Pod(即批量作业),请使用 Job 而不是 ReplicaSet。

DaemonSet

对于提供机器级别功能的 Pod(例如机器监控或机器日志),请使用 DaemonSet 而不是 ReplicaSet。这些 Pod 的生命周期与机器的生命周期绑定:这些 Pod 需要在其他 Pod 启动之前在机器上运行,并且在机器准备重启/关机时可以安全终止。

ReplicationController

ReplicaSet 是 ReplicationController 的后继者。两者服务于相同的目的,行为类似,只是 ReplicationController 不支持标签用户指南中所述的基于集合的选择器需求。因此,ReplicaSet 优于 ReplicationController。

下一步