5. ReplicaSet与Deployment

ReplicaSet 与 Deployment 控制器

控制器(Controller)

​ 通过前面的课程学习我们知道了 master 的各组件中,API Server 仅负责将资源存储于 etcd 中,并将其变动通知给各其他组件,如 kubelet、kube-scheduler、kube-proxy 和 kube-controller-manager 等,kube-scheduler监控到处于未绑定状态的 Pod 对象出现时就启动调度器为其挑选最合适的工作节点,另外 Kubernetes 的核心功能之一还在于要确保各资源对象的当前状态(status)已匹配用户期望的状态(spec),使当前状态不断地向期望状态“调谐”(Reconcile)来完成容器应用管理,这些就是 kube-controller-manager 的任务,kube-controller-manager是一个独立的组件,但是它却包含了很多功能不同的控制器。

​ Kubernetes 控制器会监听资源的 **创建/更新/删除 **事件,并触发 Reconcile 调谐函数作为响应,整个调整过程被称作 Reconcile Loop(调谐循环) 或者 Sync Loop(同步循环)。Reconcile 是一个使用资源对象的命名空间和资源对象名称来调用的函数,使得资源对象的实际状态与 资源清单中定义的状态保持一致。调用完成后,Reconcile 会将资源对象的状态更新为当前实际状态。

​ 创建为具体的控制器对象之后,每个控制器均通过 API Server 提供的接口持续监控相关资源对象的当前状态,并在因故障、更新或其他原因导致系统状态发生变化时,尝试让资源的当前状态向期望状态迁移。简单来说,每个控制器对象运行一个调谐循环负责状态同步,并将目标资源对象的当前状态写入到其 status 字段中。

​ 实现调谐功能是依靠的 Kubernetes 实现的核心机制之一的 List-Watch,在资源对象的状态发生变动时,由 APIServer 负责写入 etcd 并通过水平触发机制主动通知给相关的客户端程序以确保其不会错过任何一个事件。控制器通过API Server 的 Watch 接口实时监控目标资源对象的变动并执行调谐操作,但并不会与其他控制器进行任何交互。

​ 工作负载(workload)一类的控制器资源类型包括 ReplicaSet、Deployment、DaemonSet、StatefulSet、Job 和CronJob 等,它们分别代表了一种类型的 Pod 控制器资源,接下来我们将分别介绍这些工作负载控制器的使用。

ReplicaSet

​ 假如我们现在有一个 Pod 正在提供线上的服务,我们来想想一下我们可能会遇到的一些场景:

  • 某次运营活动非常成功,网站访问量突然暴增
  • 运行当前 Pod 的节点发生故障了,Pod 不能正常提供服务了

​ 第一种情况,可能比较好应对,活动之前我们可以大概计算下会有多大的访问量,提前多启动几个 Pod 副本,活动结束后再把多余的 Pod 杀掉,虽然有点麻烦,但是还是能够应对这种情况的。

​ 第二种情况,可能某天夜里收到大量报警说服务挂了,然后起来打开电脑在另外的节点上重新启动一个新的 Pod,问题可以解决。

​ 但是如果我们都人工的去解决遇到的这些问题,似乎又回到了以前刀耕火种的时代了是吧?如果有一种工具能够来帮助我们自动管理 Pod 就好了,Pod 挂了自动帮我在合适的节点上重新启动一个 Pod,这样是不是遇到上面的问题我们都不需要手动去解决了。

​ 而 ReplicaSet 这种资源对象就可以来帮助我们实现这个功能,ReplicaSet(RS) 的主要作用就是维持一组 Pod 副本的运行,保证一定数量的 Pod 在集群中正常运行,ReplicaSet 控制器会持续监听它说控制的这些 Pod 的运行状态,在 Pod 发送故障数量减少或者增加时会触发调谐过程,始终保持副本数量一定。和 Pod 一样我们仍然还是通过 YAML 文件来描述我们的 ReplicaSet 资源对象,如下 YAML 文件是一个常见的ReplicaSet 定义:

# nginx-rs.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: nginx-rs
  namespace: default 
spec:
  replicas: 3        # Pod 副本数量
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx 
          ports:
            - containerPort: 80

​ 上面的 YAML 文件结构和我们之前定义的 Pod 看上去没太大两样,有常见的 apiVersion、kind、metadata,在spec 下面描述 ReplicaSet 的基本信息,其中包含 3 个重要内容:

  • replias:表示期望的 Pod 的副本数量
  • selector:Label Selector,用来匹配要控制的 Pod 标签,需要和下面的 Pod 模板中的标签一致
  • template:Pod 模板,实际上就是以前我们定义的 Pod 内容,相当于把一个 Pod 的描述以模板的形式嵌入到了ReplicaSet 中来。

​ 上面就是我们定义的一个普通的 ReplicaSet 资源清单文件,ReplicaSet 控制器会通过定义的 Label Selector 标签去查找集群中的 Pod 对象:

验证:

ubuntu@ubuntu:~/example/replica_set_deployment$ kubectl apply -f ./nginx-rs.yaml 
replicaset.apps/nginx-rs created
ubuntu@ubuntu:~/example/replica_set_deployment$ kubectl get pods -l app=nginx
NAME             READY   STATUS    RESTARTS   AGE
nginx-rs-nrkmf   1/1     Running   0          112s
nginx-rs-q2z9w   1/1     Running   0          112s
nginx-rs-w7p9m   1/1     Running   0          112s
# 删除后这个会重建
ubuntu@ubuntu:~/example/replica_set_deployment$ kubectl delete pod nginx-rs-nrkmf
ubuntu@ubuntu:~/example/replica_set_deployment$ kubectl get pods -l app=nginx
NAME             READY   STATUS    RESTARTS   AGE
nginx-rs-q2z9w   1/1     Running   0          2m38s
nginx-rs-q4q7x   1/1     Running   0          6s
nginx-rs-w7p9m   1/1     Running   0          2m38s
ubuntu@ubuntu:~/example/replica_set_deployment$ 

​ 可以看到又重新出现了一个 Pod,这个就是上面我们所说的 ReplicaSet 控制器为我们做的工作,我们在 YAML 文件中声明了 3 个副本,然后现在我们删除了一个副本,就变成了两个,这个时候 ReplicaSet 控制器监控到控制的 Pod 数量和期望的 3 不一致,所以就需要启动一个新的 Pod 来保持 3 个副本,这个过程上面我们说了就是调谐的过程。同样可以查看 RS 的描述信息来查看到相关的事件信息:

ubuntu@ubuntu:~/example/replica_set_deployment$ kubectl describe rs nginx-rs
Name:         nginx-rs
Namespace:    default
Selector:     app=nginx
Labels:       <none>
Annotations:  <none>
Replicas:     3 current / 3 desired
Pods Status:  3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:  app=nginx
  Containers:
   nginx:
    Image:         nginx
    Port:          80/TCP
    Host Port:     0/TCP
    Environment:   <none>
    Mounts:        <none>
  Volumes:         <none>
  Node-Selectors:  <none>
  Tolerations:     <none>
Events:
  Type    Reason            Age    From                   Message
  ----    ------            ----   ----                   -------
  Normal  SuccessfulCreate  3m47s  replicaset-controller  Created pod: nginx-rs-nrkmf
  Normal  SuccessfulCreate  3m47s  replicaset-controller  Created pod: nginx-rs-q2z9w
  Normal  SuccessfulCreate  3m47s  replicaset-controller  Created pod: nginx-rs-w7p9m
  Normal  SuccessfulCreate  75s    replicaset-controller  Created pod: nginx-rs-q4q7x
ubuntu@ubuntu:~/example/replica_set_deployment$ 

​ 可以发现最开始通过 ReplicaSet 控制器创建了 3 个 Pod,后面我们删除了 Pod 后, ReplicaSet 控制器又为我们创建了一个 Pod,和上面我们的描述是一致的。如果这个时候我们把 RS 资源对象的 Pod 副本更改为 2spec.replicas=2,这个时候我们来更新下资源对象:

ubuntu@ubuntu:~/example/replica_set_deployment$ kubectl apply -f ./nginx-rs.yaml 
replicaset.apps/nginx-rs configured
ubuntu@ubuntu:~/example/replica_set_deployment$ kubectl get pods -l app=nginx
NAME             READY   STATUS    RESTARTS   AGE
nginx-rs-q2z9w   1/1     Running   0          4m43s
nginx-rs-w7p9m   1/1     Running   0          4m43s
ubuntu@ubuntu:~/example/replica_set_deployment$ kubectl get pods -l app=nginx
NAME             READY   STATUS    RESTARTS   AGE
nginx-rs-q2z9w   1/1     Running   0          4m43s
nginx-rs-w7p9m   1/1     Running   0          4m43s
ubuntu@ubuntu:~/example/replica_set_deployment$ kubectl describe rs nginx-rs
Name:         nginx-rs
Namespace:    default
Selector:     app=nginx
Labels:       <none>
Annotations:  <none>
Replicas:     2 current / 2 desired
Pods Status:  2 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:  app=nginx
  Containers:
   nginx:
    Image:         nginx
    Port:          80/TCP
    Host Port:     0/TCP
    Environment:   <none>
    Mounts:        <none>
  Volumes:         <none>
  Node-Selectors:  <none>
  Tolerations:     <none>
Events:
  Type    Reason            Age    From                   Message
  ----    ------            ----   ----                   -------
  Normal  SuccessfulCreate  5m5s   replicaset-controller  Created pod: nginx-rs-nrkmf
  Normal  SuccessfulCreate  5m5s   replicaset-controller  Created pod: nginx-rs-q2z9w
  Normal  SuccessfulCreate  5m5s   replicaset-controller  Created pod: nginx-rs-w7p9m
  Normal  SuccessfulCreate  2m33s  replicaset-controller  Created pod: nginx-rs-q4q7x
  Normal  SuccessfulDelete  28s    replicaset-controller  Deleted pod: nginx-rs-q4q7x
ubuntu@ubuntu:~/example/replica_set_deployment$ 

​ 可以看到 Replicaset 控制器在发现我们的资源声明中副本数变更为 2 后,就主动去删除了一个 Pod,这样副本数就和期望的始终保持一致了:

​ 我们可以随便查看一个 Pod 的描述信息可以看到这个 Pod 的所属控制器信息:

ubuntu@ubuntu:~/example/replica_set_deployment$ kubectl describe pod nginx-rs-w7p9m
Name:             nginx-rs-w7p9m
Namespace:        default
Priority:         0
Service Account:  default
Node:             node1/192.168.236.102
Start Time:       Tue, 09 Sep 2025 09:46:03 +0000
Labels:           app=nginx
Annotations:      <none>
Status:           Running
IP:               10.244.2.48
IPs:
  IP:           10.244.2.48
Controlled By:  ReplicaSet/nginx-rs
...

​ 另外被 ReplicaSet 持有的 Pod 有一个 metadata.ownerReferences 指针指向当前的 ReplicaSet,表示当前Pod 的所有者,这个引用主要会被集群中的垃圾收集器使用以清理失去所有者的 Pod 对象。这个 ownerReferences和数据库中的外键是不是非常类似。可以通过将 Pod 资源描述信息导出查看:

ubuntu@ubuntu:~/example/replica_set_deployment$ kubectl get pod nginx-rs-w7p9m -o yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2025-09-09T09:46:03Z"
  generateName: nginx-rs-
  generation: 1
  labels:
    app: nginx
  name: nginx-rs-w7p9m
  namespace: default
  ownerReferences:
  - apiVersion: apps/v1
    blockOwnerDeletion: true
    controller: true
    kind: ReplicaSet
    name: nginx-rs
    uid: 28aa1f42-a40c-455a-9b31-14122b90d994
  resourceVersion: "172785"
  uid: a735f175-d506-4974-8d96-42f36d2b9794
spec:
...

​ 我们可以看到 Pod 中有一个 metadata.ownerReferences 的字段指向了 ReplicaSet 资源对象。如果要彻底删除Pod,我们就只能删除 RS 对象:

Deployment

​ 前面我们学习了 ReplicaSet 控制器,了解到该控制器是用来维护集群中运行的 Pod 数量的,但是往往在实际操作的时候,我们反而不会去直接使用 RS,而是会使用更上层的控制器,比如接下来我们要学习的主角 Deployment,Deployment 一个非常重要的功能就是实现了 Pod 的滚动更新,比如我们应用更新了,我们只需要更新我们的容器镜像,然后修改 Deployment 里面的 Pod 模板镜像,那么 Deployment 就会用滚动更新(Rolling Update)的方式来升级现在的 Pod,这个能力是非常重要的,因为对于线上的服务我们需要做到不中断服务,所以滚动更新就成了必须的一个功能。而 Deployment 这个能力的实现,依赖的就是前面的 ReplicaSet 这个资源对象,实际上我们可以通俗的理解就是每个 Deployment 就对应集群中的一次部署,这样就更好理解了

​ Deployment 资源对象的格式和 ReplicaSet 几乎一致,如下资源对象就是一个常见的 Deployment 资源类型:

# nginx-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
  namespace: default 
spec:
  replicas: 3        # Pod 副本数量
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx 
          ports:
            - containerPort: 80

验证:

ubuntu@ubuntu:~/example/replica_set_deployment$ kubectl apply -f ./nginx-deploy.yaml 
deployment.apps/nginx-deploy created
ubuntu@ubuntu:~/example/replica_set_deployment$ kubectl get pods -l app=nginx
NAME                            READY   STATUS    RESTARTS   AGE
nginx-deploy-86c57bc6b8-7qt4w   1/1     Running   0          52s
nginx-deploy-86c57bc6b8-m464j   1/1     Running   0          52s
nginx-deploy-86c57bc6b8-nvkjx   1/1     Running   0          52s
ubuntu@ubuntu:~/example/replica_set_deployment$ kubectl get pods -l app=nginx
NAME                            READY   STATUS    RESTARTS   AGE
nginx-deploy-86c57bc6b8-7qt4w   1/1     Running   0          52s
nginx-deploy-86c57bc6b8-m464j   1/1     Running   0          52s
nginx-deploy-86c57bc6b8-nvkjx   1/1     Running   0          52s
# 查看pod信息
ubuntu@ubuntu:~/example/replica_set_deployment$ kubectl describe pod nginx-deploy-86c57bc6b8-7qt4w
Name:             nginx-deploy-86c57bc6b8-7qt4w
Namespace:        default
Priority:         0
Service Account:  default
Node:             node1/192.168.236.102
Start Time:       Tue, 09 Sep 2025 09:58:37 +0000
Labels:           app=nginx
                  pod-template-hash=86c57bc6b8
Annotations:      <none>
Status:           Running
IP:               10.244.2.50
IPs:
  IP:           10.244.2.50
# 发现还是 ReplicaSet 控制
Controlled By:  ReplicaSet/nginx-deploy-86c57bc6b8
Containers:
...
# 继续查看这个 nginx-deploy-86c57bc6b8
ubuntu@ubuntu:~$ ubuntu@ubuntu:~$  kubectl describe rs nginx-deploy-86c57bc6b8c^C
ubuntu@ubuntu:~$ kubectl describe rs nginx-deploy-86c57bc6b8
Name:           nginx-deploy-86c57bc6b8
Namespace:      default
Selector:       app=nginx,pod-template-hash=86c57bc6b8
Labels:         app=nginx
                pod-template-hash=86c57bc6b8
Annotations:    deployment.kubernetes.io/desired-replicas: 3
                deployment.kubernetes.io/max-replicas: 4
                deployment.kubernetes.io/revision: 1
Controlled By:  Deployment/nginx-deploy
Replicas:       3 current / 3 desired
Pods Status:    3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:  app=nginx
           pod-template-hash=86c57bc6b8
  Containers:
   nginx:
    Image:         nginx
    Port:          80/TCP
    Host Port:     0/TCP
    Environment:   <none>
    Mounts:        <none>
  Volumes:         <none>
  Node-Selectors:  <none>
  Tolerations:     <none>
Events:
  Type    Reason            Age   From                   Message
  ----    ------            ----  ----                   -------
  Normal  SuccessfulCreate  6m1s  replicaset-controller  Created pod: nginx-deploy-86c57bc6b8-nvkjx
  Normal  SuccessfulCreate  6m1s  replicaset-controller  Created pod: nginx-deploy-86c57bc6b8-m464j
  Normal  SuccessfulCreate  6m1s  replicaset-controller  Created pod: nginx-deploy-86c57bc6b8-7qt4w
...
# 其中有这样的一个信息:Controlled By: Deployment/nginx-deploy,明白了吧?意思就是我们的 Pod 依赖的控制器 RS 实际上被我们的 Deployment 控制着呢,

​ ReplicaSet 作用和之前一样还是来保证 Pod 的个数始终保存指定的数量,所以 Deployment 中的容器restartPolicy=Always 是唯一的就是这个原因,因为容器必须始终保证自己处于 Running 状态,ReplicaSet 才可以去明确调整 Pod 的个数。而 Deployment 是通过管理 ReplicaSet 的数量和属性来实现水平扩展/收缩以及滚动更新两个功能的。

水平伸缩

​ 水平扩展/收缩的功能比较简单,因为 ReplicaSet 就可以实现,所以 Deployment 控制器只需要去修改它缩控制的ReplicaSet 的 Pod 副本数量就可以了。比如现在我们把 Pod 的副本调整到 4 个,那么 Deployment 所对应的ReplicaSet 就会自动创建一个新的 Pod 出来,这样就水平扩展了,我们可以使用一个新的命令 kubectl scale 命令来完成这个操作:

# 注意这个并不修改yaml
ubuntu@ubuntu:~/example/replica_set_deployment$ kubectl scale deployment nginx-deploy --replicas=4
deployment.apps/nginx-deploy scaled
ubuntu@ubuntu:~/example/replica_set_deployment$ kubectl get pods -l app=nginx
NAME                            READY   STATUS    RESTARTS   AGE
nginx-deploy-86c57bc6b8-4rk7b   1/1     Running   0          15s
nginx-deploy-86c57bc6b8-7qt4w   1/1     Running   0          10m
nginx-deploy-86c57bc6b8-hfztg   1/1     Running   0          15s
nginx-deploy-86c57bc6b8-nvkjx   1/1     Running   0          10m
ubuntu@ubuntu:~/example/replica_set_deployment$ 

滚动更新

滚动更新

​ 如果只是水平扩展/收缩这两个功能,就完全没必要设计 Deployment 这个资源对象了,Deployment 最突出的一个功能是支持滚动更新,比如现在我们需要把应用容器更改为 nginx:1.7.9 版本,修改后的资源清单文件如下所示:

# nginx-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx 
  strategy:
    type: RollingUpdate # 更新策略类型(默认为 RollingUpdate)
    rollingUpdate:
      maxSurge: 1       # 滚动更新时,最多可额外创建 1 个 Pod
      maxUnavailable: 1 # 滚动更新时,最多允许 1 个 Pod 不可用
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.7.9
          ports:
            - containerPort: 80

验证

ubuntu@ubuntu:~/example/replica_set_deployment$ kubectl apply -f ./nginx-deploy.yaml 
deployment.apps/nginx-deploy configured
ubuntu@ubuntu:~/example/replica_set_deployment$ kubectl rollout status deployment/nginx-deploy
Waiting for deployment "nginx-deploy" rollout to finish: 2 out of 3 new replicas have been updated...
ubuntu@ubuntu:~/example/replica_set_deployment$ kubectl describe deploy nginx-deploy
Name:                   nginx-deploy
Namespace:              default
CreationTimestamp:      Tue, 09 Sep 2025 10:11:30 +0000
Labels:                 <none>
Annotations:            deployment.kubernetes.io/revision: 2
Selector:               app=nginx
Replicas:               3 desired | 3 updated | 4 total | 2 available | 2 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  1 max unavailable, 1 max surge
Pod Template:
  Labels:  app=nginx
  Containers:
   nginx:
    Image:         nginx:1.7.9
    Port:          80/TCP
    Host Port:     0/TCP
    Environment:   <none>
    Mounts:        <none>
  Volumes:         <none>
  Node-Selectors:  <none>
  Tolerations:     <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    ReplicaSetUpdated
OldReplicaSets:  nginx-deploy-86c57bc6b8 (1/1 replicas created)
NewReplicaSet:   nginx-deploy-7f5cc64fb9 (3/3 replicas created)
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  18m   deployment-controller  Scaled up replica set nginx-deploy-86c57bc6b8 from 0 to 3
  Normal  ScalingReplicaSet  80s   deployment-controller  Scaled up replica set nginx-deploy-7f5cc64fb9 from 0 to 1
  Normal  ScalingReplicaSet  80s   deployment-controller  Scaled down replica set nginx-deploy-86c57bc6b8 from 3 to 2
  Normal  ScalingReplicaSet  80s   deployment-controller  Scaled up replica set nginx-deploy-7f5cc64fb9 from 1 to 2
  Normal  ScalingReplicaSet  7s    deployment-controller  Scaled down replica set nginx-deploy-86c57bc6b8 from 2 to 1
  Normal  ScalingReplicaSet  7s    deployment-controller  Scaled up replica set nginx-deploy-7f5cc64fb9 from 2 to 3
ubuntu@ubuntu:~/example/replica_set_deployment$ 

​ Deployment 控制器首先是将之前控制的 nginx-deploy-86c57bc6b8 这个 RS资源对象进行缩容操作,然后滚动更新开始了,可以发现 Deployment 为一个新的 nginx-deploy-5b7b9ccb95 RS资源对象首先新建了一个新的 Pod,然后将之前的 RS 对象缩了,再然后新的 RS 对象扩容。实现滚动更新。

回滚操作(其实推荐改yaml,单一来源)

# 查看滚动更新记录
ubuntu@ubuntu:~/example/replica_set_deployment$ kubectl rollout history deployment nginx-deploy
deployment.apps/nginx-deploy 
REVISION  CHANGE-CAUSE
1         <none>
2         <none>
# 回滚上一个记录
ubuntu@ubuntu:~/example/replica_set_deployment$ kubectl rollout undo deployment nginx-deploy
deployment.apps/nginx-deploy rolled back
# 查看滚动更新记录
ubuntu@ubuntu:~/example/replica_set_deployment$ kubectl rollout history deployment nginx-deploy
deployment.apps/nginx-deploy 
REVISION  CHANGE-CAUSE
2         <none>
3         <none>
# 查看 Deployment 的状态:
ubuntu@ubuntu:~/example/replica_set_deployment$  kubectl get rs
NAME                      DESIRED   CURRENT   READY   AGE
nginx-deploy-7f5cc64fb9   0         0         0       6m38s
nginx-deploy-86c57bc6b8   3         3         3       23m
ubuntu@ubuntu:~/example/replica_set_deployment$ 

回滚到指定版本

如果有多个历史版本,可以通过 --to-revision 指定回滚到某个版本:

# 查看历史版本
kubectl rollout history deployment nginx-deploy

# 回滚到 revision=1
kubectl rollout undo deployment nginx-deploy --to-revision=1

备注

在很早之前的 Kubernetes 版本中,默认情况下会为我们暴露下所有滚动升级的历史记录,也就是 ReplicaSet对象,但一般情况下没必要保留所有的版本,毕竟会存在 etcd 中,我们可以通过配置spec.revisionHistoryLimit 属性来设置保留的历史记录数量,不过新版本中该值默认为 10,如果希望多保存
几个版本可以设置该字段。

posted @ 2025-09-09 18:39  beamsoflight  阅读(8)  评论(0)    收藏  举报