跳转至

节点与负载管理

工作负载是在 Kubernetes 上运行的应用程序。在 Kubernetes 中,无论你的负载是由单个组件还是由多个一同工作的组件构成, 你都可以在一组 Pod 中运行它。

在 Kubernetes 中,Pod 代表的是集群上处于运行状态的一组 容器的集合。

Kubernetes Pod 遵循预定义的生命周期。 例如,当在你的集群中运行了某个 Pod,但是 Pod 所在的 节点 出现致命错误时, 所有该节点上的 Pod 的状态都会变成失败。

Kubernetes 将这类失败视为 最终状态: 即使该节点后来恢复正常运行,你也需要创建新的 Pod 以恢复应用。

不过,为了减轻用户的使用负担,通常不需要用户直接管理每个 Pod。 而是使用负载资源来替用户管理一组 Pod。 这些负载资源通过配置 控制器 来确保正确类型的、处于运行状态的 Pod 个数是正确的,与用户所指定的状态相一致。

Kubernetes 提供若干种内置的工作负载资源:

  • Deployment 和ReplicaSet (替换原来的资源 ReplicationController)。 Deployment 很适合用来管理你的集群上的 无状态应用 ,Deployment 中的所有 Pod 都是相互等价的,并且在需要的时候被替换。
  • StatefulSet 让你能够运行 一个或者多个 以某种方式跟踪应用状态的 Pod。 例如,如果你的负载会将数据作持久存储,你可以运行一个 StatefulSet,将每个 Pod 与某个 PersistentVolume 对应起来。你在 StatefulSet 中各个 Pod 内运行的代码可以将数据复制到同一 StatefulSet 中的其它 Pod 中以提高整体的服务可靠性。
  • DaemonSet 定义 提供节点本地支撑设施 的 Pod。这些 Pod 可能对于你的集群的运维是 非常重要的,例如作为网络链接的辅助工具或者作为网络 插件 的一部分等等。每次你向集群中添加一个新节点时,如果该节点与某 DaemonSet 的规约匹配,则控制平面会为该 DaemonSet 调度一个 Pod 到该新节点上运行。
  • Job 和 CronJob。 定义一些一直运行到结束并停止的任务。 你可以使用 Job 来定义只需要 执行一次并且执行后即视为完成 的任务。你可以使用 CronJob 来 根据某个排期表 来多次运行同一个 Job。

Pod

Pod 是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元。

  1. Pod(就像docker中)是一组(一个或多个) 容器; 这些容器共享存储、网络、以及怎样运行这些容器的声明。
  2. Pod 中的内容总是并置(colocated)的并且一同调度,在共享的上下文中运行。
  3. Pod 所建模的是特定于应用的 “逻辑主机”,其中包含一个或多个应用容器, 这些容器相对紧密地耦合在一起。 在非云环境中,在相同的物理机或虚拟机上运行的应用类似于在同一逻辑主机上运行的云应用。

除了应用容器,Pod 还可以包含在 Pod 启动期间运行的 Init 容器。 你也可以注入临时性容器来调试正在运行的 Pod

Pod的抽象建模与用法

抽象

Pod 的共享上下文包括一组 Linux 名字空间、控制组(cgroup)和可能一些其他的隔离方面, 即用来隔离容器的技术。 在 Pod 的上下文中,每个独立的应用可能会进一步实施隔离。

Pod 类似于共享名字空间并共享文件系统卷的一组容器。

用法

Kubernetes 集群中的 Pod 主要有两种用法:

  • 运行单个容器的 Pod。"每个 Pod 安排 一个容器"模型是最常见的 Kubernetes 用例; 在这种情况下,可以将 Pod 看作单个容器的包装器,并且 Kubernetes 直接管理 Pod,而不是容器
  • 运行多个协同工作的容器的 Pod。 Pod 可以封装由紧密耦合且需要共享资源的多个并置容器组成的应用。 这些位于同一位置的容器构成一个内聚单元。将多个并置、同管的容器组织到一个 Pod 中是一种相对高级的使用场景。 只有在一些场景中,容器之间紧密关联时你才应该使用这种模式

Pod 通常不是直接创建的,而是使用工作负载资源 (ex. Deployment 或 Job) 创建的

用于管理 Pod 的工作负载资源

pod由工作负载分配

通常你不需要直接创建 Pod,甚至单实例 Pod。

事实上,你会使用诸如 Deployment 或 Job 这类工作负载资源来创建 Pod。 如果 Pod 需要跟踪状态,可以考虑 StatefulSet 资源。

管理工作负载的资源

每个pod各司其职

每个 Pod 都旨在运行给定应用程序的单个实例。

如果希望横向扩展应用程序 (例如,运行多个实例以提供更多的资源),则应该使用多个 Pod,每个实例使用一个 Pod。 在 Kubernetes 中,这通常被称为副本(Replication)。 通常使用一种工作负载资源及其控制器来创建和管理一组 Pod 副本。

参见 Pod 和控制器以了解 Kubernetes 如何使用工作负载资源及其控制器以实现应用的扩缩和自动修复。

pod的共享资源

Pod 天生地为其成员容器提供了两种共享资源:网络存储

换言之,一个pod内的所有容器共享该pod内的存储卷&&网络资源 (IP-Addr / External Port...)

使用pod

你很少在 Kubernetes 中直接创建一个个的 Pod,甚至是单实例(Singleton)的 Pod。 这是因为 Pod 被设计成了相对临时性的、用后即抛的一次性实体。

当 Pod 由你或者间接地由控制器 创建时,它被调度在集群中的节点上运行。 Pod 会保持在该节点上运行,直到 Pod 结束执行、Pod 对象被删除、Pod 因资源不足而被驱逐或者节点失效为止

重启 Pod 中的容器不应与重启 Pod 混淆。 Pod 不是进程,而是容器运行的环境。 在被删除之前,Pod 会一直存在

Pod 操作系统

应该将 .spec.os.name 字段设置为 windows 或 linux 以表示你希望 Pod 运行在哪个操作系统之上。

这两个是 Kubernetes 目前支持的操作系统。将来,这个列表可能会被扩充 :)

Pod 和控制器

你可以使用 工作负载资源 来创建和管理多个 Pod。 资源的控制器能够处理副本的管理、上线,并在 Pod 失效时提供自愈能力。 例如,如果一个节点失败,控制器注意到该节点上的 Pod 已经停止工作, 就可以创建替换性的 Pod。调度器会将替身 Pod 调度到一个健康的节点执行。

常见的管理一个或者多个 Pod 的 工作负载资源的示例

Pod 模板

工作负载资源的控制器通常使用 Pod 模板(Pod Template) 来替你创建 Pod 并管理它们

Pod 模板是包含在工作负载对象中的规范,用来创建 Pod。这类负载资源包括 Deployment、 Job 和 DaemonSet 

工作负载的控制器会使用负载对象中的 PodTemplate 来生成实际的 Pod。 PodTemplate 是你用来运行应用时指定的负载资源的目标状态的一部分。

创建 Pod 时,你可以在 Pod 模板中包含 Pod 中运行的容器的环境变量

下面的示例是一个简单的 Job 的清单,其中的 template 指示启动一个容器。 该 Pod 中的容器会打印一条消息之后暂停。

YAML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: batch/v1
kind: Job
metadata:
  name: hello
spec:
  template:
    # 这里是 Pod 模板
    spec:
      containers:
      - name: hello
        image: busybox:1.28
        command: ['sh', '-c', 'echo "Hello, Kubernetes!" && sleep 3600']
      restartPolicy: OnFailure

Point-1

  • 修改 Pod 模板或者切换到新的 Pod 模板都 不会对已经存在的 Pod 直接起作用
  • 如果改变工作负载资源的 Pod 模板,工作负载资源需要使用更新后的模板来创建 Pod, 并使 用新创建的 Pod 替换旧的 Pod

也就是说,更改模板之后,原来的pod不会自己跟着变动,需要工作负载资源(Deployment/StatefulSet/DaemonSet/Job)自己根据新模板生成新的pod 并且想要实现“替换”的话,使用新的覆盖旧的!

例如,StatefulSet 控制器针对每个 StatefulSet 对象确保运行中的 Pod 与当前的 Pod 模板匹配。如果编辑 StatefulSet 以更改其 Pod 模板, StatefulSet 将开始基于更新后的模板创建新的 Pod

Point-2

每个工作负载资源都实现了自己的规则,用来处理对 Pod 模板的更新

在节点上,kubelet 并不直接监测或管理与 Pod 模板相关的细节或模板的更新,这些细节都被抽象出来。 这种抽象和关注点分离简化了整个系统的语义, 并且使得用户可以在不改变现有代码的前提下就能扩展集群的行为

Pod 更新与替换

当某工作负载的 Pod 模板被改变时, 控制器会基于更新的模板创建新的 Pod 对象而不是对现有 Pod 执行更新或者修补操作

Kubernetes 并不禁止你直接管理 Pod。对运行中的 Pod 的 某些字段执行就地更新操作还是可能的 。不过,类似 patch 和 replace 这类更新操作有一些限制

详见k8s restrict-patch&&replace

资源共享和通信

Pod 使它的 成员容器 间能够进行数据共享和通信

Pod 中的存储

一个 Pod 可以设置一组共享的存储。 Pod 中的所有容器都可以访问该共享卷,从而允许这些容器共享数据。

卷还允许 Pod 中的 持久数据保留下来,即使其中的容器需要重新启动。 有关 Kubernetes 如何在 Pod 中实现共享存储并将其提供给 Pod 的更多信息, 请参考存储

Pod 联网

  1. 每个 Pod 都在每个地址族中获得一个唯一的 IP 地址
  2. Pod 中的每个容器 共享 (current pod's) 网络名字空间,包括 IP 地址和网络端口 
  3. Pod 内的容器可以使用 localhost 互相通信。 当 Pod 中的容器与 Pod 之外的实体通信时,它们必须协调如何使用共享的网络资源(例如端口)

在同一个 Pod 内,所有容器共享一个 IP 地址和端口空间,并且可以通过 localhost 发现对方。

不同 Pod 中的容器的 IP 地址互不相同,如果没有特殊配置,就无法通过 OS 级 IPC 进行通信。 如果某容器希望与运行于 其他 Pod 中的容器 通信,可以通过 IP 联网 的方式实现。

Pod 中的容器所看到的系统主机名与为 Pod 配置的 name 属性值相同。 网络部分提供了更多有关此内容的信息## 静态 Pod

静态 Pod

静态 Pod(Static Pod) 直接由特定节点上的 kubelet 守护进程管理, 不需要 API 服务器看到它们

  • 尽管大多数 Pod 都是通过控制面(例如,Deployment) 来管理的,但对于静态 Pod 而言,由 kubelet 直接监控每个 Pod,并在其失效时重启之
  • 静态 Pod 通常绑定到某个节点(node)上的 kubelet。 其主要用途是运行自托管的控制面。 在自托管场景中,使用 kubelet 来管理各个独立的控制面组件
  • kubelet 自动尝试为每个静态 Pod 在 Kubernetes API 服务器上创建一个镜像 Pod。 这意味着:在节点上运行的 Pod 在 API 服务器上是可见的,但不可以通过 API 服务器来控制。
  • API服务器可见静态Pod,但是静态Pod目之所及只有当前node的Kubelet,因此:静态 Pod 的 spec 不能引用其他的 API 对象(例如: ServiceAccount、 ConfigMap、 Secret 等)

Pod 管理多个容器

Pod 中的容器被自动并置到集群中的同一物理机或虚拟机上,并可以一起进行调度。 容器之间可以共享资源和依赖、彼此通信、协调何时以及何种方式终止自身

集群中的 Pod 主要有两种用法:

  • 运行单个容器的 Pod
    • "每个 Pod 一个容器" 模型是最常见的 Kubernetes 用例; 在这种情况下,可以将Pod 看作单个容器的包装器
    • Kubernetes 直接管理 Pod,而不是容器
  • 运行多个需要协同工作的容器的 Pod
    • Pod 可以封装由多个紧密耦合且需要共享资源的并置容器组成的应用。
    • 这些位于同一位置的容器可能形成单个内聚的服务单元 —— 一个容器将文件从共享卷提供给公众, 而另一个单独的边车容器则刷新或更新这些文件。
    • Pod 将这些容器和存储资源打包为一个可管理的实体

Kubernetes 直接管理 Pod,而不是容器。这是一个非常重要的视角!

例如,你可能有一个容器,为共享卷中的文件提供 Web 服务器支持,以及一个单独的 边车(Sidercar) 容器负责从远端更新这些文件,如下图所示:

Pod 创建示意图

  1. 有些 Pod 具有 Init 容器和 应用容器。 Init 容器默认会在启动应用容器之前运行并完成
  2. 你还可以拥有为主应用 Pod 提供辅助服务的 边车容器(例如:服务网格)

工作负载管理

Kubernetes 提供了几个内置的 API 来声明式管理工作负载及其组件。

最终,你的应用以容器的形式在 Pods 中运行; 但是,直接管理单个 Pod 的工作量将会非常繁琐。例如,如果一个 Pod 失败了,你可能希望运行一个新的 Pod 来替换它。Kubernetes 可以为你完成这些操作。

你可以使用 Kubernetes API 创建工作负载对象, 这些对象所表达的是比 Pod 更高级别的抽象概念,Kubernetes 控制平面根据你定义的 工作负载对象 规约自动管理 Pod 对象

用于管理工作负载的内置 API 包括:

  • Deployment (也间接包括 ReplicaSet) 是在集群上运行应用的最常见方式
    • Deployment 适合在集群上管理 无状态应用 工作负载
    • Deployment 中的任何 Pod 都是完全等价的、可互换的,可以在需要时进行替换
  • StatefulSet 
    • 允许你管理一个或多个 运行相同应用 代码、但具 有不同身份标识 的 Pod
    • StatefulSet 与 Deployment 不同:Deployment 中的 Pod 预期是可互换的。StatefulSet 最常见的用途:建立其 Pod 与其持久化存储之间的关联
    • 例如,你可以运行一个将每个 Pod 关联到 PersistentVolume 的 StatefulSet。如果该 StatefulSet 中的一个 Pod 失败了,Kubernetes 将创建一个新的 Pod, 并连接到相同的 PersistentVolume
  • DaemonSet 定义了在特定节点上提供本地设施的 Pod, 例如允许该节点上的容器访问存储系统的驱动
    • 当必须 在合适的节点上运行某种驱动或其他节点级别的服务 时, 你可以使用 DaemonSet
    • DaemonSet 中的每个 Pod 执行类似于经典 Unix / POSIX 服务器上的系统守护进程的角色
    • DaemonSet 可能对集群的操作至关重要, 例如作为插件让该节点访问集群网络, 也可能帮助你管理节点,或者提供增强正在运行的容器平台所需的、不太重要的设施
    • 可以在集群的每个节点上运行 DaemonSets(及其 Pod),或者仅在某个子集上运行 (例如,只在安装了 GPU 的节点上安装 GPU 加速驱动)

你可以使用 Job 和/或 CronJob 定义一次性任务和定时任务。 Job 表示一次性任务,而每个 CronJob 可以根据排期表重复执行。

ReplicaSet

ReplicaSet 的目的是:维护一组在 任何时候都处于运行状态Pod 副本 的稳定集合。

因此,它通常用来保证 给定数量的、完全相同的 Pod 的可用性

ReplicaSet 确保任何时间都有指定数量的 Pod 副本在运行。然而,Deployment 是一个更高级的概念,它管理 ReplicaSet,并向 Pod 提供声明式的更新以及许多其他有用的功能。

因此,我们建议使用 Deployment 而不是直接使用 ReplicaSet, 除非你需要自定义更新业务流程或根本不需要更新。

这实际上意味着,你可能永远不需要操作 ReplicaSet 对象:而是 使用 Deployment,并在 spec 部分定义你的应用

YAML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
  labels:
    app: guestbook
    tier: frontend
spec:
  # 按你的实际情况修改副本数
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:
      labels:
        tier: frontend
    spec:
      containers:
      - name: php-redis
        image: gcr.io/google_samples/gb-frontend:v3

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

Bash
1
2
3
 cd ~/k8s
❯ kubectl apply -f ./frontend.yaml                             
replicaset.apps/frontend created

ReplicaSet 的设置

查看当前被部署的 ReplicaSet

Bash
1
kubectl get rs

它会显示出当前系统中所有的ReplicaSet

Bash
1
2
3
4
 kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
frontend                      3         3         0       2m52s
nginx-deployment-86dcfdf4c6   10        10        10      4d21h

查看所有 ReplicaSet 的状态

Bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
 kubectl describe rs
Name:         frontend
Namespace:    default
Selector:     tier=frontend
Labels:       app=guestbook
              tier=frontend
Annotations:  <none>
Replicas:     3 current / 3 desired
Pods Status:  0 Running / 3 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:  tier=frontend
  Containers:
   php-redis:
    Image:        gcr.io/google_samples/gb-frontend:v3
    Port:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age   From                   Message
  ----    ------            ----  ----                   -------
  Normal  SuccessfulCreate  4m4s  replicaset-controller  Created pod: frontend-8xd7h
  Normal  SuccessfulCreate  4m4s  replicaset-controller  Created pod: frontend-79pgn
  Normal  SuccessfulCreate  4m4s  replicaset-controller  Created pod: frontend-8pcqg


Name:           nginx-deployment-86dcfdf4c6
Namespace:      default
Selector:       app=nginx,pod-template-hash=86dcfdf4c6
Labels:         app=nginx
                pod-template-hash=86dcfdf4c6
Annotations:    deployment.kubernetes.io/desired-replicas: 10
                deployment.kubernetes.io/max-replicas: 13
                deployment.kubernetes.io/revision: 1
Controlled By:  Deployment/nginx-deployment
Replicas:       10 current / 10 desired
Pods Status:    10 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:  app=nginx
           pod-template-hash=86dcfdf4c6
  Containers:
   nginx:
    Image:        nginx:1.14.2
    Port:         80/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:           <none>

它会显示系统中所有的ReplicaSet

事实上,全部的信息太长了,太眼花缭乱,有时我们只需要显示一个指定的ReplicaSet的信息

查看指定 ReplicaSet 的状态

Bash
1
 kubectl describe rs/frontend

返回形如:

Bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 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:  0 Running / 3 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:  tier=frontend
  Containers:
   php-redis:
    Image:        gcr.io/google_samples/gb-frontend:v3
    Port:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age    From                   Message
  ----    ------            ----   ----                   -------
  Normal  SuccessfulCreate  6m43s  replicaset-controller  Created pod: frontend-8xd7h
  Normal  SuccessfulCreate  6m43s  replicaset-controller  Created pod: frontend-79pgn
  Normal  SuccessfulCreate  6m43s  replicaset-controller  Created pod: frontend-8pcqg

查看ReplicaSet启动相应的Pod 集合

Bash
1
kubectl get pods

会看到类似如下的 Pod 信息

Bash
1
2
3
4
5
 kubectl get pods
NAME                                READY   STATUS             RESTARTS      AGE
frontend-79pgn                      0/1     ImagePullBackOff   0             8m31s
frontend-8pcqg                      0/1     ImagePullBackOff   0             8m31s
frontend-8xd7h                      0/1     ImagePullBackOff   0             8m31s

验证pod与ReplicaSet的从属关系

为了验证对饮关系,我们可以查看 Pod 的“属主引用“被设置为前端的 ReplicaSet。

属主引用: 当前对象的“owner”,可以清晰的看出派生与从属关系

要实现这点,可取回运行中的某个 Pod 的 YAML:

Bash
1
kubectl get pods frontend-79pgn -o yaml
YAML
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2024-07-07T09:39:15Z"
  generateName: frontend-
  labels:
    tier: frontend
  name: frontend-79pgn
  namespace: default
  ownerReferences:
  - apiVersion: apps/v1
    blockOwnerDeletion: true
    controller: true
    kind: ReplicaSet
    name: frontend
    uid: 2ba26b8f-2091-4c09-b242-d1e1efa96c18
  resourceVersion: "1725794"
  uid: 05daf6d3-568c-4228-831a-c6c4da88c56a
spec:
  containers:
  - image: gcr.io/google_samples/gb-frontend:v3
    imagePullPolicy: IfNotPresent
    name: php-redis
    resources: {}
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: kube-api-access-l95j4
      readOnly: true
  dnsPolicy: ClusterFirst
  enableServiceLinks: true
  nodeName: docker-desktop
  preemptionPolicy: PreemptLowerPriority
  priority: 0
  restartPolicy: Always
  schedulerName: default-scheduler
  securityContext: {}
  serviceAccount: default
  serviceAccountName: default
  terminationGracePeriodSeconds: 30
  tolerations:
  - effect: NoExecute
    key: node.kubernetes.io/not-ready
    operator: Exists
    tolerationSeconds: 300
  - effect: NoExecute
    key: node.kubernetes.io/unreachable
    operator: Exists
    tolerationSeconds: 300
  volumes:
  - name: kube-api-access-l95j4
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          expirationSeconds: 3607
          path: token
      - configMap:
          items:
          - key: ca.crt
            path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
          - fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
            path: namespace
status:
  conditions:
  - lastProbeTime: null
    lastTransitionTime: "2024-07-07T09:39:18Z"
    status: "True"
    type: PodReadyToStartContainers
  - lastProbeTime: null
    lastTransitionTime: "2024-07-07T09:39:15Z"
    status: "True"
    type: Initialized
  - lastProbeTime: null
    lastTransitionTime: "2024-07-07T09:39:15Z"
    message: 'containers with unready status: [php-redis]'
    reason: ContainersNotReady
    status: "False"
    type: Ready
  - lastProbeTime: null
    lastTransitionTime: "2024-07-07T09:39:15Z"
    message: 'containers with unready status: [php-redis]'
    reason: ContainersNotReady
    status: "False"
    type: ContainersReady
  - lastProbeTime: null
    lastTransitionTime: "2024-07-07T09:39:15Z"
    status: "True"
    type: PodScheduled
  containerStatuses:
  - image: gcr.io/google_samples/gb-frontend:v3
    imageID: ""
    lastState: {}
    name: php-redis
    ready: false
    restartCount: 0
    started: false
    state:
      waiting:
        message: Back-off pulling image "gcr.io/google_samples/gb-frontend:v3"
        reason: ImagePullBackOff
  hostIP: 192.168.65.3
  hostIPs:
  - ip: 192.168.65.3
  phase: Pending
  podIP: 10.1.1.255
  podIPs:
  - ip: 10.1.1.255
  qosClass: BestEffort
  startTime: "2024-07-07T09:39:15Z"

frontend ReplicaSet 的信息被设置在 metadata 的 ownerReferences 字段中

YAML
1
2
3
4
5
6
7
ownerReferences:
  - apiVersion: apps/v1
    blockOwnerDeletion: true
    controller: true
    kind: ReplicaSet
    name: frontend
    uid: 2ba26b8f-2091-4c09-b242-d1e1efa96c18

可以看出:这个pod的owner是ReplicaSet,它的名字叫frontend,经由controller管理

非模板 Pod 的获得

尽管你完全可以直接创建裸的 Pod,强烈建议你确保这些裸的 Pod 并不包含可能与你的某个 ReplicaSet 的选择算符相匹配的标签。

原因在于 ReplicaSet 并不仅限于拥有在其(当前)模板中设置的 Pod,它还可以像前面小节中所描述的那样获得其他 Pod。

以前面的 frontend ReplicaSet 为例,并在以下清单中指定这些 Pod

YAML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 没有控制器(Controller,或其他对象)作为其属主引用, 并且其标签与 frontend ReplicaSet 的选择算符匹配,它们会立即被该 ReplicaSet (上一小节设定的名为frontend的ReplicaSet)获取

Test-1

先创立ReplicaSet, 再创建pods

假定你在 frontend ReplicaSet 已经被部署之后创建 Pod,并且你已经在 ReplicaSet 中(上一节名为frontend的ReplicaSet)设置了其初始的 Pod 副本数(3)以满足其副本计数需要

Bash
1
kubectl apply -f ./pod-rs.yaml
Bash
1
2
3
 kubectl apply -f ./pod-rs.yaml  
pod/pod1 created
pod/pod2 created

新的 Pod 会被该 ReplicaSet (frontend ReplicaSet)获取,并立即被 ReplicaSet 终止, 因为它们的存在会使得 ReplicaSet 中 Pod 个数 (原3 + 现2) 超出其期望值 (3)

现在我们来查看一下系统中的pods情况

Bash
1
kubectl get pods
Bash
1
2
3
4
5
6
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 或者已经被终止,或者处于终止过程中

Test-2

先创立pods, 再创建ReplicaSet

如果你先行创建 Pod:

Bash
1
kubectl apply -f https://kubernetes.io/examples/pods/pod-rs.yaml

之后再创建 ReplicaSet:

Bash
1
kubectl apply -f https://kubernetes.io/examples/controllers/frontend.yaml

你会看到 ReplicaSet 已经获得了该 Pod (pod1 && pod2),并仅根据其规约创建新的 Pod, 直到新的 Pod 和原来的 Pod 的总数达到其预期个数(3)

检验现在的pods情况:

Bash
1
kubectl get pods

将会生成下面的输出:

Text Only
1
2
3
4
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 集合

这里的“异质”:这些pods有些是根据ReplicaSet的yaml文本连带生成的,有些是“外来人口”,不经由此yaml定义

Summary

一个 ReplicaSet 中可以包含异质的 Pod 集合

当我创建一个ReplicaSet时,它会看整个系统里所有的pods,并根据自己yaml生成文本的预期数量对现有全部的pods进行管理

具体而言,如果yaml生成文本的预期数量是Num

先创立pods(num=n), 再创建ReplicaSet(num=m)

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 伪代码:pseudocode

/*创立pods(num=n)*/
if (n < Num)
    create pod-(0) ~ pod-(n-1)
    goto NestStep
else
    terminal (n-Num) pods by some Algorithm
    end

NextStep:
    /*创建ReplicaSet*/
    if (n + m < Num)
        create all the pods (connecting by ReplicaSet Yaml)
        namely: 
            append pod-m(1) ... pod-m(m)
    else
        int RestPod = (n + m - Num);
        append the resting pods
        namely:
            create pod-m(1) ... pod-m(RestPod)

先创立ReplicaSet, 再创建pods

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 伪代码:pseudocode

/*创立ReplicaSet(num=m)*/
create pod-(0) ~ pod-(m-1)

/*创建pods(num=n)*/
if (n + m < Num)
    create all the pods
    namely: 
        append pod-m(1) ... pod-m(m)
else
    int RestPod = (n + m - Num);
    create the resting pods
    namely:
        append pod-m(1) ... pod-m(RestPod)

编写 ReplicaSet 的清单

与所有其他 Kubernetes API 对象一样,ReplicaSet 也需要 apiVersionkind、和 metadata 字段。 对于 ReplicaSet 而言,其 kind 始终是 ReplicaSet。

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

ReplicaSet 也需要 .spec 部分

Pod 模板

.spec.template 是一个 Pod 模板, 要求设置标签。

在 frontend.yaml 示例中,我们指定了标签 tier: frontend

注意不要将标签与其他控制器的选择算符重叠,否则那些控制器会尝试收养此 Pod。

Pod 选择算符

.spec.selector 字段是一个标签选择算符。 如前文中所讨论的,这些是用来标识要被获取的 Pod 的标签。

在签名的 frontend.yaml 示例中,选择算符为:

YAML
1
2
matchLabels:
  tier: frontend

在 ReplicaSet 中,.spec.template.metadata.labels 的值必须与 spec.selector 值相匹配,否则该配置会被 API 拒绝。

YAML
1
2
3
4
5
6
7
selector:
    matchLabels:
        tier: frontend
    template:
        metadata:
          labels:
            tier: frontend

Replicas

你可以通过设置 .spec.replicas 来指定要同时运行的 Pod 个数。 ReplicaSet 创建、删除 Pod 以与此值匹配。

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

删除机制

(1) 删除 ReplicaSet 和它的 Pod

要删除 ReplicaSet 和它的所有 Pod,使用 kubectl delete 命令。

默认情况下,垃圾收集器 自动删除所有依赖的 Pod

Format

Bash
1
kubectl delete -f path/to/XXX.yaml

结果示例:

Bash
1
2
3
 cd ~/.k8s
❯ kubectl delete -f ./nginx-deployment.yaml
deployment.apps "nginx-deployment" deleted
Bash
1
2
 kubectl get rs
No resources found in default namespace.
Bash
1
2
3
 kubectl get pods
NAME    READY   STATUS    RESTARTS      AGE
nginx   1/1     Running   1 (14m ago)   18h

(2) 只删除 ReplicaSet

你可以只删除 ReplicaSet 而不影响它的各个 Pod,方法是使用 kubectl delete 命令并设置 --cascade=orphan 选项

cascade 级联 在这里就是“上下文”连接方式

Format

Bash
1
kubectl delete -f /path/to/XXX.yaml --cascade=orphan

结果示例:

Bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
 kubectl create -f ./frontend.yaml
replicaset.apps/frontend created

❯ kubectl get rs
NAME       DESIRED   CURRENT   READY   AGE
frontend   3         3         0       5s

❯ kubectl get pods
NAME             READY   STATUS             RESTARTS      AGE
frontend-cf2qr   0/1     ImagePullBackOff   0             59s
frontend-jhk92   0/1     ImagePullBackOff   0             59s
frontend-sjvzm   0/1     ImagePullBackOff   0             59s
nginx            1/1     Running            1 (20m ago)   19h
Bash
1
2
 kubectl delete -f ./frontend.yaml --cascade="orphan"
replicaset.apps "frontend" deleted
Bash
1
2
3
4
5
6
7
8
9
 kubectl get rs
No resources found in default namespace.

❯ kubectl get pods
NAME             READY   STATUS             RESTARTS      AGE
frontend-cf2qr   0/1     ImagePullBackOff   0             3m7s
frontend-jhk92   0/1     ImagePullBackOff   0             3m7s
frontend-sjvzm   0/1     ImagePullBackOff   0             3m7s
nginx            1/1     Running            1 (22m ago)   19h

一旦删除了原来的 ReplicaSet,就可以创建一个新的来替换它。

由于新旧 ReplicaSet 的 .spec.selector 是相同的,新的 ReplicaSet 将接管老的 Pod。 但是,它不会努力使现有的 Pod 与新的、不同的 Pod 模板匹配。 若想要以可控的方式更新 Pod 的规约,可以使用 Deployment 资源,因为 ReplicaSet 并不直接支持滚动更新

将 Pod 从 ReplicaSet 中隔离

可以通过 改变标签 来从 ReplicaSet 中移除 Pod。 这种技术可以用来从服务中去除 Pod,以便进行排错、数据恢复等。 以这种方式移除的 Pod 将被自动替换(假设副本的数量没有改变)

ReplicaSet 的替代方案

Deployment(推荐)

Deployment 是一个可以拥有 ReplicaSet 并使用声明式方式在服务器端完成对 Pod 滚动更新的对象。

尽管 ReplicaSet 可以独立使用,目前它们的主要用途是:提供给 Deployment 作为编排 Pod 创建、删除和更新的一种机制。

当使用 Deployment 时,你不必关心如何管理它所创建的 ReplicaSet,Deployment 拥有并管理其 ReplicaSet。 因此,建议你在需要 ReplicaSet 时使用 Deployment

裸 Pod

与用户直接创建 Pod 的情况不同,ReplicaSet 会替换那些由于某些原因被删除或被终止的 Pod,例如在节点故障或破坏性的节点维护(如内核升级)的情况下。

因为这个原因,我们建议你使用 ReplicaSet,即使应用程序只需要一个 Pod。 想像一下,ReplicaSet 类似于进程监视器,只不过它在多个节点上监视多个 Pod, 而不是在单个节点上监视单个进程。 ReplicaSet 将本地容器重启的任务委托给了节点上的某个代理(例如,Kubelet)去完成

Job

使用Job 代替 ReplicaSet, 可以用于那些 期望自行终止 的 Pod

DaemonSet

对于管理那些提供主机级别功能(如主机监控和主机日志)的容器, 就要用 DaemonSet 而不用 ReplicaSet。

这些 Pod 的寿命与主机寿命有关:这些 Pod 需要先于主机上的其他 Pod 运行, 并且在机器准备重新启动/关闭时安全地终止

ReplicationController

ReplicaSet 是 ReplicationController 的后继者。二者目的相同且行为类似,只是 ReplicationController 不支持 标签用户指南 中讨论的基于集合的选择算符需求

因此,相比于 ReplicationController,应优先考虑 ReplicaSet