K8s控制器天团图鉴-五大控制器整活

K8s 控制器天团图鉴!RS 守门员死盯副本数,Deployment 滚动更新零停机,DaemonSet 每屋塞个宿管,Job 日结工干完就跑,CronJob 闹钟精准时查房

前情提要: 上一篇我们把 Pod 这颗豌豆荚扒了个底朝天,还学会了用 kubectl 手动捏 Pod。但问题来了------Pod 是个短命鬼:节点挂了它跟着殉葬,手动删掉它就没了,镜像更新总不能一个一个删了重建吧?难道让运维 7×24 小时守着屏幕补 Pod?

资本家听了都摇头,于是 K8s 派出了五位"卷王"------控制器(Controller) 。你只需要在 yaml 里写清楚"我想要什么、要几个",它们就像永动机一样盯着实际状态和期望状态的差距:多了就杀、少了就补、挂了就换、到点就跑、定点就闹。

今天这节课,我们请这五位卷王集体出道:ReplicaSet 守门员、Deployment 包工头、DaemonSet 宿管大妈、Job 日结工、CronJob 闹钟精,逐个看它们的人设、KPI 和整活现场。

本篇脑图(实验目录):

  • 第 0 回:课前碎碎念(复习、作业)
  • 第一回:Controllers 是什么------期望状态与调谐循环
  • 第二回:ReplicaSet(RS)------死盯副本数的守门员
  • 番外:ReplicationController(RC)------RS 的前任
  • 第三回:Deployment------管 RS 的包工头(扩缩容、滚动更新、版本回滚)
  • 第四回:DaemonSet(DS)------每个节点发一个的宿管
  • 第五回:Job------干完就跑的日结工
  • 第六回:CronJob------定闹钟的保洁阿姨(含两个综合案例)
  • 谢幕:环境清理 + 五控制器对比总结

实验环境: 沿用一拖二集群(master30 + worker31 + worker32),私有镜像仓库 hub.laoma.cloud,所有实验在新建的 controllers 命名空间中完成。

官方文档入口:控制器


第一回 · Kubernetes Controllers:控制器天团报到

学习参考:控制器

环境准备

大白话: 上工前先圈一块自留地。新建一个名为 controllers 的 Namespace,再把当前上下文的默认命名空间切到它身上------之后这条终端里敲的所有 kubectl 命令都默认落在 controllers 里,不用再每条都手写 -n controllers。

小提醒:kubectl config set-context --current --namespace=xxx 只改"当前上下文的默认值",不影响节点和其他上下文,放心玩。

bash 复制代码
root@master30:~# kubectl create ns controllers
root@master30:~# kubectl config set-context --current --namespace controllers

Controllers 介绍

大白话: Controller(控制器)就是 K8s 雇来的一群监工 。你在 yaml 里写的是"期望状态"(我想要 3 个 nginx),控制器就开始无限循环干活:盯一眼实际状态 → 和期望状态对比 → 不一样就动手摆平 。这个循环叫 调谐循环(Reconcile Loop),也叫控制循环。

多了就杀、少了就补、挂了就换、到点就跑------这就是官方说的"自愈"和"自动扩缩容",本质上全靠这群卷王死磕。

五位卷王人设速览(建议先扫一眼,后面逐个出道):

控制器 管什么容器 人设 一句话 KPI
ReplicaSet(RS) 服务类 守门员 场上任何时刻都必须给我保持 N 个 Pod
Deployment 服务类 包工头 管着一堆 RS,负责滚动更新、回滚、扩缩容
DaemonSet(DS) 服务类 宿管大妈 每个节点(房间)必须塞一个 Pod,新节点入住也发一个
Job 工作类 日结工 一次性任务,跑到 exit 0 就收工下班
CronJob 工作类 闹钟精 按 cron 时间表,到点就派一个 Job 出去干活

Controller 主要作用是确保所管理的资源处于用户期望的状态,通过不断地监控资源的状态,并根据实际状态与期望状态之间的差异执行相应的动作,来实现资源的自愈、自动扩展等功能。

先简单回顾一下:容器按照是否持续运行可分为两类。

  • 服务类容器: 一直运行任务,通常持续提供服务, 比如HTTP等。

  • 工作类容器:一次性任务, 比如批处理程序,完成后容器就退出。

Kubernetes 中用于管理服务类容器的控制器有 ReplicaSet 、Deployment 和 DaemonSet 等。

Kubernetes 中用于管理工作类容器的控制器有 Job 和 CronJob。

ReplicaSets

学习参考:ReplicaSets。

ReplicaSets 介绍

人设:守门员 / 看门大爷。 ReplicaSet 简称 RS,它的人生信条是"一个都不能少,一个也不能多"。

注意定位:RS 平时基本不直接出道,它是 Deployment 的下属------Deployment 负责指挥,RS 负责干活。理解 RS 是理解 Deployment 的前提。

ReplicaSet,简称RS,是维护一组在任何时候都处于运行状态的 Pod 副本的稳定集合。 因此,它通常用来保证给定数量的、完全相同的 Pod 的可用性。它主要被 Deployment 用来作为一种编排 Pod 创建、删除及更新的机制。

ReplicaSet 工作原理

大白话拆解 RS 三件套:

  • selector(选择算符) :认人标准------贴了 app: nginx 标签的 Pod 才算我的人;
  • replicas(副本数):编制表------我手底下必须有 3 个 Pod;
  • template(Pod 模板):招人画像------缺人时,按这个模板造新 Pod。

就像网吧老板规定店里必须坐着 3 个客人:走了一个(Pod 挂了)立刻拉一个进来;闯进来第四个(多了)直接请出去。

ReplicaSet 部分主要字段:

  • 一个用来识别可获得的 Pod 的集合的选择算符。
  • 一个用来标明应该维护的副本个数的数值。
  • 一个用来指定创建新 Pod 时要使用的 Pod 模板。

每个 ReplicaSet 根据 Pod 模板创建制定数量 Pod, 进而实现其存在价值。

  • 如果pod数量多于指定数量,则RS会终止额外的pod。
  • 如果pod数量少于指定数量,则RS会创建额外的pod。例如宿主机内核升级,在其他节点上创建新Pod。
ReplicaSet 使用
ReplicaSet 创建

先写 rs.yaml(已逐行加注释,建议对照食用):

bash 复制代码
root@master30:~# vim rs.yaml
yaml 复制代码
apiVersion: apps/v1          # apps/v1 是 RS/Deployment/DS 所在的 API 组
kind: ReplicaSet            # 资源类型:副本集 RS
metadata:
  name: nginx               # RS 的名字
spec:
  replicas: 3               # 期望副本数:必须始终保持 3 个 Pod(守门员的 KPI)
  selector:                 # 选择算符:靠标签"认人"
    matchLabels:
      app: nginx            # 只有贴了 app=nginx 标签的 Pod 才归我管
  template:                 # Pod 模板:缺人时按这个模子造新 Pod
    metadata:
      name: nginx
      labels:
        app: nginx          # 模板里的标签必须能被上面的 selector 匹配,否则创建报错
    spec:
      containers:
      - name: nginx
        image: hub.laoma.cloud/library/nginx:latest   # 私有仓库镜像(hub.laoma.cloud 保留)
        imagePullPolicy: IfNotPresent                 # 本地有就不拉,没网也能用
        ports:
        - containerPort: 80                           # 容器监听 80 端口

踩坑提示: selector.matchLabels 必须和 template.metadata.labels 对得上,否则 kubectl apply 会直接报错------这是 K8s 防止你"招人画像"和"认人标准"不一致。

bash 复制代码
root@master30:~# kubectl apply -f rs.yaml

root@master30:~# kubectl get rs
NAME    DESIRED   CURRENT   READY   AGE
nginx   3         3         3       8m45s

root@master30:~# kubectl get pods
NAME          READY   STATUS    RESTARTS   AGE
nginx-95pth   1/1     Running   0          8m48s
nginx-9jg4f   1/1     Running   0          8m48s
nginx-tjtp2   1/1     Running   0          8m48s

root@master30:~# kubectl describe pod nginx-9jg4f |grep Controlled
Controlled By:  ReplicaSet/nginx
ReplicaSet 健壮性测试

光说不练假把式,守门员到底尽不尽职?我们做两个"破坏实验"。

测试1:删除单个副本

bash 复制代码
root@master30:~# kubectl get rs
NAME    DESIRED   CURRENT   READY   AGE
nginx   3         3         3       8m45s
root@master30:~# kubectl get pods
NAME          READY   STATUS    RESTARTS   AGE
nginx-95pth   1/1     Running   0          8m48s
nginx-9jg4f   1/1     Running   0          8m48s
nginx-tjtp2   1/1     Running   0          8m48s

# 删除一个pod
root@master30:~# kubectl get pods
NAME          READY   STATUS    RESTARTS   AGE
nginx-95pth   1/1     Running   0          8m48s
nginx-9jg4f   1/1     Running   0          8m48s
nginx-tjtp2   1/1     Running   0          8m48s
root@master30:~# kubectl delete pod nginx-95pth --force 
root@master30:~# kubectl get pods
NAME          READY   STATUS    RESTARTS   AGE
nginx-4qw2z   1/1     Running   0          2s
nginx-9jg4f   1/1     Running   0          9m20s
nginx-tjtp2   1/1     Running   0          9m20s

Pod 的 metadata.ownerReferences 字段,显示所属主资源。 正是通过这一连接,ReplicaSet 知道它所维护的 Pod 集合的状态, 并据此计划其操作行为。

ReplicaSet 使用 selector 获得 Pod 集合。如果某个 Pod 没有 OwnerReference 或者其 OwnerReference 不是一个控制器, 且其匹配到某 ReplicaSet 的选择算符,则该 Pod 立即被此 ReplicaSet 获得。

测试2:创建具有相同标签的pod

大白话: 这个实验更损------故意手动 kubectl run 造一个贴着相同标签的"野 Pod",看 RS 什么反应。

结果:新 Pod 刚诞生(0 岁,0s)就被 RS 判定为"超编人员",当场 Terminating。

原理:RS 只认标签不认人。 它不关心 Pod 是谁创建的,只要 selector 匹配且总数超了,一律干掉。这就是"编制外的 Pod,没有编制"。

bash 复制代码
root@master30:~# kubectl run nginx --image=hub.laoma.cloud/library/nginx:latest -l app=nginx;kubectl get pods
pod/nginx created
NAME          READY   STATUS        RESTARTS   AGE
nginx         0/1     Terminating   0          0s
nginx-4qw2z   1/1     Running       0          3m40s
nginx-9jg4f   1/1     Running       0          12m
nginx-tjtp2   1/1     Running       0          12m

# 刚创建出来就被Terminating
ReplicaSet 删除

大白话:删除分两种姿势,面试常考:

  • 默认级联删除 :kubectl delete rs nginx,老板(RS)和员工(Pod)一锅端;
  • 孤儿删除 :加 --cascade=orphan,只开掉老板,员工(Pod)保留下来变"无主 Pod"。

孤儿 Pod 也是 Pod,想清理就用标签一锅端:kubectl delete all -l app=nginx。

删除RS控制器,会删除它管理的pods。

bash 复制代码
root@master30:~# kubectl delete rs nginx 
root@master30:~# kubectl get pods

使用**--cascade=orphan**选项,只删除RS,保留pod。

bash 复制代码
root@master30:~# kubectl apply -f rs.yaml 
root@master30:~# kubectl delete rs nginx --cascade=orphan
root@master30:~# kubectl get pods
NAME              READY   STATUS    RESTARTS   AGE
pod/nginx-27vzb   1/1     Running   0          73s
pod/nginx-7lc95   1/1     Running   0          73s
pod/nginx-mmrhk   1/1     Running   0          73s

# 删除pod
root@master30:~# kubectl delete all -l app=nginx

ReplicationController

大白话: ReplicationController 简称 RC ,是 RS 的前任,K8s 上古时代的守门员。

两者干的活几乎一样,RS 是升级版,最大区别是 RS 的 selector 支持"基于集合"的标签选择算符 (比如 app in (nginx,apache)、env exists),而 RC 只支持"键值相等"这种老写法。

现在你几乎不会手写 RC,它主要出现在考古题和老教材里。见到认识就行,新人一律用 Deployment(底层自动帮你管 RS)。
学习参考:ReplicationController

ReplicationController ,简称RC,功能与ReplicaSets类似。 ReplicaSet 是 RC 的升级版, 支持新的基于集合的标签选择算符。

这里我们不详细介绍ReplicationController。

Deployment

学习参考:Deployment

Deployment 介绍

人设:包工头 / 项目经理。 Deployment 简称 deploy,它自己不直接捏 Pod,而是管理一堆 RS,再由 RS 管 Pod。

三代关系背下来:Deployment → ReplicaSet → Pod。

那为什么不直接用 RS?因为 RS 只会"数人头",不会帮你滚动更新镜像、一键回滚、暂停上线、记录历史版本------这些脏活累活全是 Deployment 的。官方明确建议:除非你要自定义更新编排,否则永远操作 Deployment,不要直接碰 RS。

Deployment,简称 deploy,为 Pod 和 ReplicaSet 提供声明式的更新能力。

ReplicaSet 能确保运行指定数量的pod。Deployment能管理ReplicaSets,并提供对pod的更新等功能。因此,我们建议你使用Deployment来管理ReplicaSets,除非你需要自定义更新编排。这意味着你可能永远不需要操作ReplicaSet对象,而是使用Deployment替代管理 。

Deployment 用例

大白话对应用例: 上线应用、发新版本(滚动更新)、翻车了回滚、流量大了扩容、改一半先暂停攒一波再恢复、查看上线是不是卡住了、清理旧 RS------Deployment 全包了。

以下是 Deployments 的典型用例:

Deployment 管理
创建
命令行方式

命令行一把梭,适合快速验证。注意观察 kubectl get all:一条命令下去,Deployment、ReplicaSet、Pod 三层资源一起出现。

bash 复制代码
root@master30:~# kubectl create deployment web --image=hub.laoma.cloud/library/nginx:1.27 --replicas=2

# 查看deployment创建的资源
root@master30:~# kubectl get all
NAME                      READY   STATUS    RESTARTS   AGE
pod/web-b78cbd74b-6fjxs   1/1     Running   0          3s
pod/web-b78cbd74b-mh6fh   1/1     Running   0          3s

NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/web   2/2     2            2           3s

NAME                            DESIRED   CURRENT   READY   AGE
replicaset.apps/web-b78cbd74b   2         2         2       3s

# 查看deployment详细信息
root@master30:~# kubectl describe deployments.apps web 
Name:                   web
Namespace:              hyc
CreationTimestamp:      Sat, 21 Oct 2023 13:17:06 +0000
Labels:                 app=web
Annotations:            deployment.kubernetes.io/revision: 1
Selector:               app=web
Replicas:               2 desired | 2 updated | 2 total | 2 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=web
  Containers:
   nginx:
    Image:        nginx:1.27
    Port:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   web-b78cbd74b (2/2 replicas created)
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  21s   deployment-controller  Scaled up replica set web-b78cbd74b to 2

# ReplicaSet与Deployment关系
root@master30:~# kubectl describe replicaset.apps web-b78cbd74b | grep Controlled
Controlled By:  Deployment/web
# 指明ReplicaSet是由Deployment/web创建的。

# pod与ReplicaSet关系
root@master30:~# kubectl describe pod web-b78cbd74b-6fjxs | grep Controlled
Controlled By:  ReplicaSet/web-b78cbd74b
# 指明pod是由ReplicaSet/web-b78cbd74b创建的。

注释: 上面 describe 输出里的 Namespace: hyc、Image: nginx:1.27 是课堂资料沿用的官方示例输出:你的实验命名空间实际是 controllers,镜像也应是私有仓库全路径 hub.laoma.cloud/library/nginx:1.27,一切以你自己集群的实际输出为准,不影响对字段含义的理解。

三代归属关系一目了然: Deployment Controlled By 向上看------RS 由 Deployment/web 管控;Pod 由 ReplicaSet/web-b78cbd74b 管控。层级链:Deployment → RS → Pod。

yaml 文件方式

生产环境推荐写法:配置即代码(GitOps),能入库、能复用、能 review。先用 --dry-run=client -o yaml 让 kubectl 帮你"打草稿",再手动微调:

bash 复制代码
# 获取deployment的yaml文件
root@master30:~# kubectl create deployment web --image=hub.laoma.cloud/library/nginx:1.27 --replicas=2 --dry-run=client -o yaml > deployment-web.yaml
root@master30:~# cat deployment-web.yaml
yaml 复制代码
apiVersion: apps/v1                  # Deployment 同样属于 apps/v1 组
kind: Deployment                    # 资源类型:Deployment(包工头)
metadata:
  labels:
    app: web
  name: web                         # Deployment 的名字
spec:
  replicas: 2                       # 期望 2 个 Pod
  selector:
    matchLabels:
      app: web                      # 管理标签 app=web 的 Pod
  template:                         # Pod 模板
    metadata:
      labels:
        app: web
    spec:
      containers:
      - image: hub.laoma.cloud/library/nginx:1.27   # 锁定 1.27 版本,后面滚动更新会换成 1.28/1.29
        name: nginx
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80

格式说明:

① apiVersion,是当前配置格式的版本。

② kind,是要创建的资源类型, 这里是Deployment。

③ metadata,是该资源的元数据, name是必需的元数据项。

④ spec,是该Deployment的规格说明。

⑤ spec.replicas,指明副本数量, 默认为1。

⑥ spec.template,定义Pod的模板, 这是配置文件的重要部分。

⑦ spec.template.metadata,定义Pod的元数据, 至少要定义一个label。 label的key和value可以任意指定。

⑧ spec.template.spec,描述Pod的规格, 此部分定义Pod中每一个容器的属性,name和image是必需的。

bash 复制代码
root@master30:~# kubectl apply -f deployment-web.yaml 

创建方式进行比较

记忆方法:

  • 临时测试 → 命令行(快);
  • 正式部署、跨环境、规模化 → yaml 文件(稳),配合 kubectl apply 实现"存在即更新"。

kubectl apply 是声明式玩法:你只管提交 yaml 描述"最终应该长什么样",K8s 自己决定该新建还是该更新。

  • 基于命令行的方式:
    • 简单、 直观、 快捷, 上手快。
    • 适合临时测试或实验。
  • 基于配置文件的方式:
    • 配置文件描述了What, 即应用最终要达到的状态。
    • 配置文件提供了创建资源的模板, 能够重复部署。
    • 可以像管理代码一样管理部署。
    • 适合正式的、 跨环境的、 规模化部署。
    • 这种方式要求熟悉配置文件的语法, 有一定难度。

**kubectl apply **命令不但能够创建Kubernetes资源, 也能对资源进行更新, 非常方便。

编辑

大白话:改 Deployment 有三条路:

  1. kubectl edit 在线热改(临时,改完不落盘,重启 yaml apply 会被覆盖,慎用);
  2. 改本地 yaml 再 kubectl apply(推荐,变更可追溯);
  3. kubectl scale 这类命令做专项修改(只改副本数等)。
bash 复制代码
# 方法1:命令行直接修改
root@master30:~# kubectl edit deployments.apps web

# 方法2:编辑资源文件,然后apply应用

# 方法3:命令行修改,例如修改deployment副本数
root@master30:~# kubectl scale deployment web --replicas=4
删除

跟 RS 一样,Deployment 默认级联删除(连同 RS、Pod 一锅端);--cascade=orphan 只删 Deployment 自己,下面的 RS/Pod 变孤儿。

删除deployments时,默认会删除deployments管理的子资源。

bash 复制代码
root@master30:~# kubectl delete deployments.apps web
deployment.apps "web" deleted

使用**--cascade=orphan选项**删除deployments,不会删除deployments管理的子资源。

bash 复制代码
root@master30:~# kubectl apply -f deployment-web.yaml
root@master30:~# kubectl delete deployments.apps web --cascade=orphan

# 先确认删除对象
root@master30:~# kubectl get all -l app=web

# 然后再删除
root@master30:~# kubectl delete all -l app=web
root@master30:~# kubectl get all -l app=web
水平伸缩

大白话: 水平伸缩(Horizontal Scaling)就是"加人/减人"------副本数从 2 变 3 是扩容,从 3 变 1 是缩容。

三种姿势:kubectl scale --replicas=N、kubectl edit、改 yaml 再 apply。注意扩容后新 Pod 的名字哈希前缀不变(同一个 RS),只有后半段随机串是新的。

bash 复制代码
# 创建 deployment
root@master30:~# kubectl create deployment web --image=hub.laoma.cloud/library/nginx:1.27 --replicas=2
root@master30:~# kubectl scale deployment web --replicas=3

# 或者
root@master30:~# kubectl edit deployments.apps web

# 或者修改yaml文件并apply
root@master30:~# kubectl get deployments.apps web -o yaml > web.yaml
root@master30:~# vim web.yaml

root@master30:~# kubectl get pod
NAME                  READY   STATUS    RESTARTS   AGE
web-5899d78c9-cckln   1/1     Running   0          9m28s
web-5899d78c9-p8pk4   1/1     Running   0          43s
web-5899d78c9-phntq   1/1     Running   0          9m28s
健壮性测试

这次直接关节点,看看 Pod 会不会被"转移复活"。下面这段时间线是本节最重要的考点。

关闭 worker节点,测试pod重建。

bash 复制代码
root@master30:~# kubectl get pod -o wide
NAME                  READY   STATUS    RESTARTS   AGE     IP              NODE               NOMINATED NODE   READINESS GATES
web-5899d78c9-cckln   1/1     Running   0          14m     10.224.51.134   worker31.hyc.cloud   <none>           <none>
web-5899d78c9-p8pk4   1/1     Running   0          6m12s   10.224.51.135   worker31.hyc.cloud   <none>           <none>
web-5899d78c9-phntq   1/1     Running   0          14m     10.224.225.69   worker32.hyc.cloud   <none>           <none>

# 关闭 worker32
root@worker32:~# init 0

# 等待一段时间(5分钟), Kubernetes 判定 worker32 不可用, 将worker32上的Pod标记为Unknown状态, 并在worker31上重建Pod,维持总副本数为3

# 当worker32恢复后, Unknown的Pod会被删除, 已经运行的Pod不会重新调度回worker32。

K8s 判断节点宕机,要经过 3 个阶段:

  1. Node 节点上kubelet 默认 每 10s 发一次心跳上报自身状态(kubelet → kube-apiserver)

  2. Master 上 controller-manager 每 5 秒检查一次心跳,连续 40s 没收到 Node节点心跳,判定 Node 节点不健康,标记为 NotReady。

    参数:node-monitor-period=5s 和 node-monitor-grace-period=40s。

    该参数属于 kube-controller-manager 组件,路径:

    bash 复制代码
    /etc/kubernetes/manifests/kube-controller-manager.yaml

    编辑静态 Pod 配置文件,找到 command 段,在其下面添加或修改以上参数行,保存文件,触发静态 Pod 重启。

  3. 节点 NotReady 持续满 5 分钟,开始把 Pod 驱逐到别的节点。

    参数:pod-eviction-timeout=300s。

    该参数也属于 kube-controller-manager 组件,该组件以静态 Pod 形式运行在 master 节点。修改方法同上。

更新镜像

大白话: 更新镜像 = 发新版。kubectl set image 之后,Deployment 不会让所有 Pod 同时重启,而是新建一个 RS(名字里的哈希变了),让新 RS 慢慢扩容、旧 RS 慢慢缩到 0。

旧 RS 不会被删除(副本数停在 0),这是故意留着给你一键回滚的------历史版本都在。

小提示:--record 参数在新版 Kubernetes(1.23 起)已被标记废弃,CHANGE-CAUSE 现在靠注解记录;实验照敲能看到效果,知道它是老写法即可。

bash 复制代码
# 设置 deployment 的 image 为 hub.laoma.cloud/library/nginx:1.28

# 获取容器名称
root@master30:~# kubectl get deployments.apps -o wide
NAME   READY   UP-TO-DATE   AVAILABLE   AGE    CONTAINERS   IMAGES       SELECTOR
web    3/3     3            3           139m   nginx       hub.laoma.cloud/library/nginx:1.27   app=web

# 或者
root@master30:~# kubectl describe deployments.apps web |grep Container -i -A2
  Containers:
   nginx:
    Image:        hub.laoma.cloud/library/nginx:1.27

# 更新镜像为hub.laoma.cloud/library/nginx:1.28
root@master30:~# kubectl set image deployment/web nginx=hub.laoma.cloud/library/nginx:1.28 --record
# 或者直接编辑deploy设置镜像
root@master30:~# kubectl edit deployments.apps web 

# 新增了一个replicaset,用于创建新的pod
root@master30:~# kubectl get rs
NAME                             DESIRED   CURRENT   READY   AGE
replicaset.apps/web-5899d78c9    0         0         0       26m
replicaset.apps/web-6c57bdf5f4   3         3         3       9s

# 查看镜像版本
root@master30:~# kubectl get deployments.apps web -o wide
NAME   READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES       SELECTOR
web    3/3     3            3           26m   nginx        hub.laoma.cloud/library/nginx:1.28   app=web
版本控制

大白话: Deployment 自带"版本存档"功能,kubectl rollout 就是它的控制台,常用子命令如下:

子命令 作用
kubectl rollout status 盯着上线进度(滚动更新时最常用)
kubectl rollout history 查看历史版本(REVISION 1/2/3...)
kubectl rollout undo 回滚到上一版本(--to-revision=N 可指定版本)
kubectl rollout pause / resume 暂停 / 恢复上线(攒一堆改动一起发)
kubectl rollout restart 重启所有 Pod(不改镜像也会触发新 RS,常用于加载新配置/证书)

一个反直觉的点: 回滚后版本号不会变回 1,而是生成一个新的 REVISION 4。版本历史只增不减,回滚本质是"用旧内容再发一版"。

使用 kubectl rollout 命令控制deployment版本。

bash 复制代码
root@master30:~# kubectl rollout -h
Manage the rollout of one or many resources.
        
 Valid resource types include:

  *  deployments
  *  daemonsets
  *  statefulsets

Examples:
  # Rollback to the previous deployment
  kubectl rollout undo deployment/abc
  
  # Check the rollout status of a daemonset
  kubectl rollout status daemonset/foo
  
  # Restart a deployment
  kubectl rollout restart deployment/abc
  
  # Restart deployments with the 'app=nginx' label
  kubectl rollout restart deployment --selector=app=nginx

Available Commands:
  history       View rollout history
  pause         Mark the provided resource as paused
  restart       Restart a resource
  resume        Resume a paused resource
  status        Show the status of the rollout
  undo          Undo a previous rollout

Usage:
  kubectl rollout SUBCOMMAND [options]

Use "kubectl rollout <command> --help" for more information about a given command.
Use "kubectl options" for a list of global command-line options (applies to all commands).

示例:

bash 复制代码
# 再次更新镜像为 hub.laoma.cloud/library/nginx:1.29
root@master30:~# kubectl set image deployment/web nginx=hub.laoma.cloud/library/nginx:1.29 --record 

# 查看更新记录
root@master30:~# kubectl rollout history deployment web
deployment.apps/web 
REVISION  CHANGE-CAUSE
1         <none>
2         kubectl set image deployment/web nginx=nginx:1.28 --record=true
3         kubectl set image deployment/web nginx=nginx:1.29 --record=true

# 回滚到版本1
root@master30:~# kubectl rollout undo deployment web --to-revision=1
deployment.apps/web rolled back
root@master30:~# kubectl rollout history deployment web
deployment.apps/web 
REVISION  CHANGE-CAUSE
2         kubectl set image deployment/web *=nginx:1.28 --record=true
3         kubectl set image deployment/web *=nginx:1.29 --record=true
4         <none>
滚动更新

大白话: 滚动更新(Rolling Update)的精髓是"边杀旧的、边起新的,全程不断服"。两个参数控制节奏,必须搞懂:

Kubernetes提供了两个参数maxSurge和maxUnavailable来精细控制Pod的替换数量 。

  • **maxSurge,此参数控制滚动更新过程中副本总数超过 DESIRED 的上限的数量。**maxSurge可以是具体的整数(比如3) , 也可以是百分百, 向上取整。 maxSurge默认值为25%。

    例如, DESIRED为10, 那么副本总数的最大值为 roundUp(10 + 10 * 25%) =13, 所以我们看到 CURRENT 就是13。

  • maxUnavailable,此参数控制滚动更新过程中不可用的副本相占DESIRED的最大比例。 maxUnavailable可以是具体的整数(比如3), 也可以是百分百, 向下取整。 maxUnavailable默认值为25%。

    例如, DESIRED为10, 那么可用的副本数至少要为10 - roundDown(10 * 25%)= 8, 所以我们看到AVAILABLE是8。

总结:

  • maxSurge 值越大, 初始创建的新副本数量就越多。
  • **maxUnavailable **值越大, 初始销毁的旧副本数量就越多,更新初期造成不可用副本数量越多。

白话速记(10 个副本、默认 25% 时):

  • maxSurge = 临时工名额 :更新期间最多允许"超编" 13 - 10 = 3 个,即先造出 3 个新 Pod 顶上;
  • maxUnavailable = 最多允许空着的工位 :更新期间至少要有 8 个 Pod 能对外服务,最多挂 2 个;
  • 节点资源紧张、塞不下额外 Pod 时,把 maxSurge 调小;追求稳妥、可用容量不能掉太多时,把 maxUnavailable 调小(甚至设 0)。

理想情况下, 我们这个案例滚动更新的过程应该是这样的:

  1. 创建3个新副本,此时Running副本数为10,maxSurge副本总数达到13。
  2. 销毁2个旧副本,同时再创建2个新副本。此时Running副本数为8。如果之前创建的3个副本状态没有变更为Running,则ContainerCreating副本数为5,maxSurge副本总数仍为13。
  3. 当新副本状态变更为Running, 例如之前创建的5个新副本在同一时刻状态变为running。当然这是一种理想情况。
  4. 此时running状态副本为13个,那么此时可以一次性销毁5个旧副本,同时又可以一次性创建5个新副本,使running副本数回到8。
  5. 这个过程会持续进行, 直到所有的旧副本被新副本替换,滚动更新完成。

**实践:**更新deployment镜像,并使用以下脚本监控:

bash 复制代码
root@master30:~# vim monitor_pod_numbers
bash 复制代码
#!/bin/bash
# 滚动更新监控脚本:每 0.5 秒统计一次各种 STATUS 的 Pod 各有多少个,追加到 output.log
while true
do
  echo '===================' >> output.log
  # --no-headers:不输出表头
  # awk '{print $3}':取第 3 列(STATUS 状态列)
  # sort | uniq -c:排序后统计每种状态出现次数
  # sed -r 's/^ +//':去掉行首空格,让日志更整齐
  kubectl get pods --no-headers | awk '{print $3}' | sort | uniq -c | sed -r 's/^ +//' >> output.log
  sleep 0.5
done
# 用法:bash monitor_pod_numbers 后台跑,另开终端 tail -f output.log 看实时变化

日志内容类似:

bash 复制代码
===================
10 Running
===================
5 ContainerCreating
8 Running
2 Terminating
===================
5 ContainerCreating
8 Running
2 Terminating
===================
5 ContainerCreating
8 Running
===================
5 ContainerCreating
8 Running
===================
3 ContainerCreating
1 Pending
9 Running
4 Terminating
===================
5 ContainerCreating
8 Running
5 Terminating
===================
5 ContainerCreating
8 Running
5 Terminating
===================
5 ContainerCreating
8 Running
3 Terminating
===================
10 Running
4 Terminating
===================
10 Running
2 Terminating
===================
10 Running
===================
10 Running
===================

DaemonSet

学习参考:DaemonSet

DaemonSet 介绍

人设:宿管大妈。 DaemonSet 简称 DS ,它不管你要几个副本,而是规定:每个节点上都给我跑一个。

新节点加入集群 → 自动发一个 Pod;节点被移除 → 上面的 Pod 一起回收。所以 DS 的 yaml 里根本没有 replicas 字段,它的副本数永远等于"符合条件的节点数"。

DaemonSet,简写DS,确保全部(或者某些)节点上运行一个 Pod 的副本。 当有新节点加入集群时, 也会在新节点上新增一个 Pod 。 当有节点从集群移除时,移除节点上的 Pod 也会被回收。

DaemonSet 用例

思考一下: 什么样的软件必须"每台机器都来一个"?

  • 网络组件(calico-node、kube-proxy)------每个节点都得联网、都得有网络规则;
  • 日志收集(fluentd / logstash / Fluent Bit)------每个节点的日志都得有人收;
  • 监控采集(Prometheus Node Exporter)------每个节点的 CPU、内存、磁盘都得有人报。

这些"每机一份"的守护进程,就是 DaemonSet 的主场。

DaemonSet 的一些典型用例:

  • 在每个节点上运行集群守护进程,例如存储守护进程 glusterd 和 ceph。
  • 在每个节点上运行日志收集守护进程, 例如 fluentd 或 logstash。
  • 在每个节点上运行监控守护进程,例如 Prometheus Node Exporter 或 collectd。
DaemonSet 用法

简单的用法:为每种类型的守护进程在所有的节点上都启动一个 DaemonSet。

复杂的用法:为同一种守护进程部署多个 DaemonSet;每个具有不同的标志, 并且对不同硬件类型具有不同的内存、CPU 要求。

DaemonSet 使用
DaemonSet 创建

DaemonSet 的 yaml(已逐行注释):

bash 复制代码
root@master30:~# vim daemonset.yaml
yaml 复制代码
apiVersion: apps/v1
kind: DaemonSet                    # 资源类型:守护进程集 DS
metadata:
 name: busybox
spec:
 selector:
   matchLabels:
    app: busybox
 template:
  metadata:
   labels:
    app: busybox
  spec:
    containers:
    - name: busybox
      image: hub.laoma.cloud/library/busybox
      imagePullPolicy: IfNotPresent
      command:                     # busybox 默认启动后立刻退出,这里让它睡 10 小时(36000 秒)
      - sleep                      # 否则 Pod 会一直 CrashLoopBackOff,观察不到 DS 效果
      - "36000"
# 注意:DS 没有 replicas 字段!它的副本数 = 符合条件的节点数(本例 2 个 worker 各一个)
bash 复制代码
root@master30:~# kubectl apply -f daemonset.yaml
DaemonSet 查看
bash 复制代码
root@master30:~# kubectl get pod -o wide 
NAME            READY   STATUS    RESTARTS   AGE    IP              NODE                  NOMINATED NODE   READINESS GATES
busybox-5hfv6   1/1     Running   0          102s   10.224.41.132   worker31.hyc.cloud   <none>           <none>
busybox-jdnsv   1/1     Running   0          102s   10.224.193.66   worker32.hyc.cloud   <none>           <none>

root@master30:~# kubectl get ds
NAME    DESIRED CURRENT READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
busybox 2       2       2       2            2           <none>          118s
DaemonSet 调度

大白话: master 节点默认贴着一张"闲人免进 "的标签,叫污点(Taint) :node-role.kubernetes.io/control-plane:NoSchedule。

  • 普通 Pod 和 DS 看到污点,默认不往 master 上调度;
  • 命令末尾的 - 是"减号"语法,kubectl taint node <节点> <污点键>- 表示摘掉这个污点,master 立刻对外开放,DS 马上在 master 上补一个 Pod(DESIRED 从 2 变 3);
  • 再把污点加回去,DESIRED 回到 2,但 master 上已经跑着的那个 DS Pod 不会被立刻驱逐(污点只阻止新调度,不主动踢人;DS 计数也不再包含它,驱逐行为由具体污点效果与控制器逻辑决定)。
bash 复制代码
# master节点是默认是不可调度节点
root@master30:~# kubectl describe node master.hyc.cloud |grep Taints
Taints:             node-role.kubernetes.io/control-plane:NoSchedule

# 设置master节点可调度
root@master30:~# kubectl taint node master.hyc.cloud node-role.kubernetes.io/control-plane-
root@master30:~# kubectl get ds
NAME      DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
busybox   3         3         3       3            3           <none>          7m43s

# 设置master节点不可调度
root@master30:~# kubectl taint node master.hyc.cloud node-role.kubernetes.io/control-plane:NoSchedule
root@master30:~# kubectl get ds
NAME      DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
busybox   2         2         2       2            2           <none>          8m37s
# pod数量为2个,但是master节点上的busybox pod不会自动删除,也不会计数到这里。
DaemonSet 健壮性
bash 复制代码
# 删除其中一个pod
root@master30:~# kubectl delete pod busybox-5hfv6

# 自动创建新pod
root@master30:~# kubectl get pod -o wide 
NAME            READY   STATUS    RESTARTS   AGE    IP              NODE                  NOMINATED NODE   READINESS GATES
busybox-jdnsv   1/1     Running   0          7m6s   10.224.193.66   worker32.hyc.cloud   <none>           <none>
busybox-wlqqv   1/1     Running   0          8s     10.224.41.133   worker31.hyc.cloud   <none>           <none>
DaemonSet 删除

删除 DaemonSet 时,默认会删除它创建的所有 Pod,使用**--cascade=orphan**选项,将保留DaemonSet 创建 Pod。

bash 复制代码
[root@master master]# kubectl delete daemonsets.apps busybox 
daemonset.apps "busybox" deleted
K8s 集群中 DS

知识点联动: 你以为 DS 是冷门资源?其实装好集群它就在打工了。

  • kube-proxy:每台节点一个,负责 Service 网络规则;
  • calico-node:每台节点一个,负责网络策略与跨节点通信。

可以看到它们还带了 nodeSelector: kubernetes.io/os=linux------这是 DS 的另一个常用技能:只在符合条件的节点上部署。

  1. Kubernetes 使用 DaemonSet 控制器运行系统组件,例如kube-proxy、calico-node。
bash 复制代码
root@master30:~# kubectl get daemonsets.apps --namespace kube-system 
NAME          DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
calico-node   3         3         3       3            3           kubernetes.io/os=linux   4h12m
kube-proxy    3         3         3       3            3           kubernetes.io/os=linux   4h56m
  1. 分析calico-node 的yaml文件:
yaml 复制代码
kind: DaemonSet
apiVersion: apps/v1
metadata:
  name: calico-node
  namespace: kube-system
  labels:
    k8s-app: calico-node
spec:
  selector:
    matchLabels:
      k8s-app: calico-node
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
  template:
    metadata:
      labels:
        k8s-app: calico-node
      annotations:
        scheduler.alpha.kubernetes.io/critical-pod: ''
    spec:
      nodeSelector:
        kubernetes.io/os: linux
      hostNetwork: true
      containers:
        - name: calico-node
          image: hub.laoma.cloud/calico/node:v3.28.0

注意: 完整配置文件内容要更复杂一些, 为了方便学习DaemonSet, 这里只保留了最重要的内容。

生产级示例
采集节点日志-Fluent Bit

大白话: 想让 Pod 看到宿主机的文件,就用 hostPath 把宿主机目录挂进容器。

Fluent Bit 挂了两个宿主机目录:

  • /var/log:系统日志;
  • /var/lib/docker/containers:容器标准输出日志(只读挂载,readOnly: true,防止采集器把日志写坏)。

这样 DS 在每个节点起一个 Fluent Bit,就能把全集群的日志统一收走。

yaml 复制代码
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluent-bit-ds
  namespace: kube-system
spec:
  selector:
    matchLabels:
      app: fluent-bit
  template:
    metadata:
      labels:
        app: fluent-bit
    spec:
      containers:
      - name: fluent-bit
        image: cr.fluentbit.io/fluent/fluent-bit:latest
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
采集节点监控指标-Node Exporter

大白话: Node Exporter 是 Prometheus 的"节点体检员",靠读取宿主机的 /proc(进程与内核状态)和 /sys(硬件与设备信息)来算 CPU、内存、磁盘指标。

套路和上面完全一样:hostPath 挂宿主目录(只读)→ DS 每节点一个 → 9100 端口暴露指标。记住这个"DS + hostPath + 只读"的监控/日志三板斧。

yaml 复制代码
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-exporter-ds
  namespace: monitoring
spec:
  selector:
    matchLabels:
      app: node-exporter
  template:
    metadata:
      labels:
        app: node-exporter
    spec:
      containers:
      - name: node-exporter
        image: prom/node-exporter:latest
        ports:
        - containerPort: 9100
        volumeMounts:
        - name: proc
          mountPath: /host/proc
          readOnly: true
        - name: sys
          mountPath: /host/sys
          readOnly: true
      volumes:
      - name: proc
        hostPath:
          path: /proc
      - name: sys
        hostPath:
          path: /sys

Job

人设:日结工。 前面几位管的是"常驻服务",Job 管的是"一次性任务":跑完、退出码为 0、收工。

关键特征:

  • 任务失败了(退出码非 0)?换/重启 Pod 继续干,直到成功;
  • 节点宕机了?把 Job 的 Pod 重新调度到别的节点;
  • 删除 Job 连它创建的 Pod 一起清掉;挂起 Job 会删掉所有活跃 Pod。
    学习参考:Job
Job 介绍
  • Job 用于运行一次性任务。
  • 如果pod运行任务失败,则创建新的pod继续运行任务,直到任务运行完成,也就是pod中任务退出代码为0, Job结束。
  • 在job运行过程中,如果托管pod的节点发生故障,Job pod将被自动重新安排到另一个节点。
  • 删除 Job 的操作会清除所创建的全部 Pod。
  • 挂起 Job 的操作会删除 Job 的所有活跃 Pod,直到 Job 被再次恢复执行。
Job 用例

典型场景:数据库清理、数据备份、批量计算(比如算圆周率)------都是"干完就结束,不需要常驻"的活。

简单的使用场景:

  • 执行数据库清理
  • 备份 Kubernetes 集群
Job 使用
Job 基本管理

注意 Job 的 restartPolicy: 只能是 Never 或 OnFailure,不能写 Always------如果容器退出后永远自动重启,那它就不是一次性任务了。

bash 复制代码
root@master30:~# kubectl create job -h
Create a job with the specified name.

Examples:
  # Create a job
  kubectl create job my-job --image=busybox
  
  # Create a job with command
  kubectl create job my-job --image=busybox -- date

......

Usage:
  kubectl create job NAME --image=image [--from=cronjob/name] -- [COMMAND]
[args...] [options]

示例1:

bash 复制代码
root@master30:~# kubectl create job myjob --image=hub.laoma.cloud/library/busybox -- echo hello k8s job!
root@master30:~# kubectl get all
NAME              READY   STATUS      RESTARTS   AGE
pod/myjob-pdrwv   0/1     Completed   0          11m

NAME              COMPLETIONS   DURATION   AGE
job.batch/myjob   1/1           20s        11m

root@master30:~# kubectl logs myjob-pdrwv 
hello k8s job!

# 删除 Job 的操作会清除所创建的全部 Pod
# 使用--cascade=orphan选项,可以保留job创建的pod
root@master30:~# kubectl delete jobs.batch myjob 

通过 yaml 文件创建job。

bash 复制代码
root@master30:~# vim job.yaml
yaml 复制代码
apiVersion: batch/v1                 # Job/CronJob 属于 batch(批处理)API 组
kind: Job
metadata:
  name: myjob
spec:
  template:                         # Pod 模板
    metadata:
      name: myjob
    spec:
      containers:
      - name: hello
        image: hub.laoma.cloud/library/busybox
        imagePullPolicy: IfNotPresent
        command: ["echo", "hello k8s job! "]   # 一次性任务:打印一句话就退出
      restartPolicy: Never          # 失败不重启容器,而是新建 Pod 重试
bash 复制代码
root@master30:~# kubectl apply -f job.yaml

**示例2:**计算pi,保留小数点200位。

用 perl 镜像算 200 位圆周率,演示"任务需要跑一段时间"的场景。

小提示:下面 kubectl logs 后面的 Pod 名(pi-qzxv4)是官方示例里的名字,实际操作时以你自己 kubectl get pods 看到的 Pod 名为准 (本例真实 Pod 名是 pi-2hn9n)。

bash 复制代码
root@master30:~# vim job-pi.yaml
yaml 复制代码
apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: hub.laoma.cloud/library/perl        # perl 镜像里自带高精度数学库
        imagePullPolicy: IfNotPresent
        # -Mbignum=bpi 加载高精度圆周率函数;print bpi(200) 打印 200 位
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(200)"]
      restartPolicy: Never
  backoffLimit: 4                    # 最多失败重试 4 次
bash 复制代码
root@master30:~# kubectl apply -f job-pi.yaml

# 等几分钟
root@master30:~# kubectl get all
NAME           READY   STATUS      RESTARTS   AGE
pod/pi-2hn9n   0/1     Completed   0          3m13s

NAME           COMPLETIONS   DURATION   AGE
job.batch/pi   1/1           76s        3m13s

root@master30:~# kubectl logs pi-qzxv4 
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303820
restartPolicy

本节是 Job 的核心考点,两种失败处理策略务必分清:

restartPolicy 任务失败后怎么处理 现场现象
Never 不重启容器,而是不断新建新 Pod 重试 会留下一堆 StartError 的 Pod 尸体
OnFailure 在同一个 Pod 里重启容器重试 始终只有 1 个 Pod,RESTARTS 次数不断上涨,状态变 CrashLoopBackOff

下面故意把命令 echo 改成不存在的 echoxxx,让任务必败,观察两种策略的差别。

restart 策略只能是:

  • Never :只要任务没有完成,就会创建新的 pod,直到 job 完成,所以有可能会产生多个pod。
  • OnFailure :只要任务没有完成,就会重启 pod,直到job完成。

我们做个试验, 修改job.yaml, 故意引入一个错误 :echo 修改为 echoxxx。

bash 复制代码
root@master30:~# vim job.yaml
yaml 复制代码
apiVersion: batch/v1
kind: Job
metadata:
  name: myjob
spec:
  template:
    metadata:
      name: myjob
    spec:
      containers:
      - name: hello
        image: hub.laoma.cloud/library/busybox
        imagePullPolicy: IfNotPresent
        command: ["echoxxx", "hello k8s job! "]
      restartPolicy: Never

再次应用,验证。

bash 复制代码
root@master30:~# kubectl apply -f job.yaml
root@master30:~# kubectl get all
NAME              READY   STATUS       RESTARTS   AGE
pod/myjob-6hp5t   0/1     StartError   0          33s
pod/myjob-gczbz   0/1     StartError   0          2s
pod/myjob-n4bpv   0/1     StartError   0          22s

NAME              COMPLETIONS   DURATION   AGE
job.batch/myjob   0/1           33s        33s

# 可以看到有多个Pod, 状态均不正常。 

# 查看某个 pod 详细信息
root@master30:~# kubectl describe pod myjob-6hp5t
......
Events:
  Type     Reason     Age   From               Message
  ----     ------     ----  ----               -------
  Normal   Scheduled  50s   default-scheduler  Successfully assigned hyc/myjob-6hp5t to worker32.hyc.cloud
  Normal   Pulled     50s   kubelet            Container image "busybox" already present on machine
  Normal   Created    50s   kubelet            Created container hello
  Warning  Failed     50s   kubelet            Error: failed to create containerd task: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "echoxxx": executable file not found in $PATH: unknown

日志显示没有可执行程序, 符合我们的预期。

问题现象: 为什么会看到这么多失败的Pod?

解释: 当第一个Pod启动时, 容器失败退出, 根据 restartPolicy: Never, 此失败容器不会被重启, 但 Job 默认预期完成的Pod数量是1, 目前COMPLETIONS为0, 不满足, 所以 Job 会创建新的Pod, 直到 COMPLETIONS 为1。 对于我们这个例子, SUCCESSFUL 永远也到不了1, 所以Job 会一直创建新的Pod。 为了终止这个行为, 我们删除Job 。

清理 job

bash 复制代码
root@master30:~# kubectl delete jobs.batch myjob

如果将 restartPolicy 设置为 OnFailure 会怎么样?

yaml 复制代码
apiVersion: batch/v1
kind: Job
metadata:
  name: myjob
spec:
  template:
    metadata:
      name: myjob
    spec:
      containers:
      - name: hello
        image: hub.laoma.cloud/library/busybox
        imagePullPolicy: IfNotPresent
        command: ["echoxxxx", "hello k8s job! "]
      restartPolicy: OnFailure

再次应用,验证。

bash 复制代码
root@master30:~# kubectl apply -f job.yaml
root@master30:~# kubectl get all
NAME              READY   STATUS             RESTARTS   AGE
pod/myjob-xbwxh   0/1     CrashLoopBackOff   1          68s

NAME              COMPLETIONS   DURATION   AGE
job.batch/myjob   0/1           68s        68s

# pod执行失败后,会重启。
# pod 数量总是1,RESTARTS数值不断增加。
backoffLimit

大白话: 重试也要有个限度,不然任务写错了会无限刷 Pod(全是失败尸体)。backoffLimit 就是"最多允许失败重试几次",默认值为 6。本例设成 2,失败 2 次后 Job 直接判定失败,不再折腾。

如果Job执行失败,我们可以指定job执行失败最大次数。

yaml 复制代码
apiVersion: batch/v1
kind: Job
metadata:
  name: myjob
spec:
  backoffLimit: 2
  template:
    metadata:
      name: myjob
    spec:
      containers:
      - name: hello
        image: hub.laoma.cloud/library/busybox
        imagePullPolicy: IfNotPresent
        command: ["echoxx", "hello k8s job! "]
      restartPolicy: Never

测试结果:最多重建2次。

bash 复制代码
root@master30:~# kubectl get all
NAME              READY   STATUS               RESTARTS   AGE
pod/myjob-bdgxq   0/1     StartError   0          30s
pod/myjob-ffth2   0/1     StartError   0          83s
pod/myjob-abcde   0/1     StartError   0          83s

NAME              COMPLETIONS   DURATION   AGE
job.batch/myjob   0/1           83s        83s
completions

大白话: .spec.completions = 这个 Job 要攒够多少个 Pod 成功才算整体完工。默认 1(一个成功就收工)。设成 2,就是"必须有 2 个 Pod 都成功退出"。

我们还可以通过 completions 指定 Pod 执行完成多少次,才算Job执行完成。

yaml 复制代码
apiVersion: batch/v1
kind: Job
metadata:
  name: myjob
spec:
  completions: 2
  template:
    metadata:
      name: myjob
    spec:
      containers:
      - name: hello
        image: hub.laoma.cloud/library/busybox
        imagePullPolicy: IfNotPresent
        command: ["echo", "hello k8s job! "]
      restartPolicy: Never

上面配置的含义是: 每次运行1个Pod, 直到总共有2个Pod成功完成。

bash 复制代码
root@master30:~# kubectl get all
NAME              READY   STATUS      RESTARTS   AGE
pod/myjob-72zgz   0/1     Completed   0          4s
pod/myjob-8brdq   0/1     Completed   0          8s

NAME              COMPLETIONS   DURATION   AGE
job.batch/myjob   2/2           12s        12s

如果不指定completions, 默认值均为1。

parallelism

大白话: .spec.parallelism = 允许同时并行跑几个 Pod,默认 1(排队一个一个来)。

completions: 6 + parallelism: 2 = 总共要 6 个成功,每次放 2 个并行跑。

真实用途:批量处理任务池(比如转码 1000 个视频、扫描一堆文件),每个 Pod 领一个任务走,副本越多总耗时越短------这就是分布式批处理的雏形。

有时我们希望Job同时运行多个Pod, 提高Job的执行效率,通过parallelism设置并行Pod数量 。

yaml 复制代码
apiVersion: batch/v1
kind: Job
metadata:
  name: myjob
spec:
  completions: 6
  parallelism: 2
  template:
    metadata:
      name: myjob
    spec:
      containers:
      - name: hello
        image: hub.laoma.cloud/library/busybox
        imagePullPolicy: IfNotPresent
        command: ["echo", "hello k8s job! "]
      restartPolicy: Never
bash 复制代码
root@master30:~# kubectl get all
NAME              READY   STATUS      RESTARTS   AGE
pod/myjob-72zgz   0/1     Completed   0          4s
pod/myjob-8brdq   0/1     Completed   0          8s
pod/myjob-8l5cx   0/1     Completed   0          8s
pod/myjob-9gkt8   0/1     Completed   0          12s
pod/myjob-wcwwh   0/1     Completed   0          12s
pod/myjob-xs6pd   0/1     Completed   0          4s

NAME              COMPLETIONS   DURATION   AGE
job.batch/myjob   6/6           12s        12s

效果: 每次运行2个Pod, 直到总共有6个Pod成功完成。

如果不指定parallelism, 默认值均为1。

上面的例子只是为了演示Job的并行特性, 实际用途不大。 不过现实中确实存在很多需要并行处理的场景。 比如批处理程序, 每个副本(Pod) 都会从任务池中读取任务并执行, 副本越多, 执行时间就越短, 效率就越高。这种类似的场景都可以用Job来实现。

activeDeadlineSeconds

大白话: 给 Job 设一个"总工时上限",到点没干完也全部杀掉,Job 状态变成 Failed,原因 DeadlineExceeded。

优先级:activeDeadlineSeconds > backoffLimit。 哪怕重试次数还没用完,只要超时,立刻停手。两个一起设时,谁先到听谁的。

一旦 Job 运行时间达到该值,其所有运行中的 Pod 都会被终止 ,并且 Job 的状态更新为 type: Failed 及 reason: DeadlineExceeded。该值适用于 Job 的整个生命期,无论 Job 创建了多少个 Pod。

yaml 复制代码
apiVersion: batch/v1
kind: Job
metadata:
  name: pi-with-timeout
spec:
  backoffLimit: 5
  activeDeadlineSeconds: 10
  template:
    spec:
      containers:
      - name: pi
        image: hub.laoma.cloud/library/perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never

Job 的 .spec.activeDeadlineSeconds 优先级高于其 .spec.backoffLimit 设置。 因此,如果一个 Job 正在重试一个或多个失效的 Pod,该 Job 一旦到达 activeDeadlineSeconds 所设的时限即不再部署额外的 Pod,即使其重试次数还未达到 backoffLimit 所设的限制。

ttlSecondsAfterFinished

大白话: Job 干完后 Completed/Failed 的 Pod 会一直留着(方便你看日志),但攒多了也是垃圾。ttlSecondsAfterFinished 就是"完工后多少秒自动扫地出门",连 Job 带 Pod 级联删除;设成 0 表示干完立刻删。

版本说明:课堂资料里写它是 Alpha 特性(需要 TTLAfterFinished 特性门控),那是较早版本的情况;该特性在 Kubernetes 1.23 已正式 GA,现在的版本默认开启、直接可用。

通过设置 Job 的 .spec.ttlSecondsAfterFinished 字段,可以让该控制器清理掉已结束的资源。

TTL 控制器清理 Job 时,会级联式地删除 Job 对象。 换言之,它会删除所有依赖的对象,包括 Pod 及 Job 本身。 注意,当 Job 被删除时,系统会考虑其生命周期保障,例如其 Finalizers。

例如:

yaml 复制代码
apiVersion: batch/v1
kind: Job
metadata:
  name: pi-with-ttl
spec:
  ttlSecondsAfterFinished: 100
  template:
    spec:
      containers:
      - name: pi
        image: hub.laoma.cloud/library/perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never

Job pi-with-ttl 在结束 100 秒之后,可以成为被自动删除的对象。

如果该字段设置为 0,Job 在结束之后立即成为可被自动删除的对象。 如果该字段没有设置,Job 不会在结束之后被 TTL 控制器自动清除。

注意这种 TTL 机制仍然是一种 Alpha 状态的功能特性,需要配合 TTLAfterFinished 特性门控使用。有关详细信息,可参考 TTL 控制器的文档。

CronJob

人设:闹钟精 / 定时保洁阿姨。 CronJob 就是"K8s 版的 Linux crontab":按时间表周期性地创建 Job,再由 Job 创建 Pod。

典型场景:定时备份、定时清理、定时出报表。
学习参考:CronJob

CronJob 介绍

为什么 CronJob 名字最多 52 个字符?

CronJob 会在你的名字后面自动追加 11 个字符(时间戳等)来给 Job 命名,而 Job 名上限是 63 字符,所以 CronJob 本体最多 53 字符(实际建议 52 以内)。名字太长会直接创建失败,起名别太任性。

Linux中有cron程序定时执行任务, Kubernetes的CronJob提供了类似的功能, 用于周期性地执行Job,例如备份、生成报告等。

CronJob 创建新的 Job 和(间接)Pod 时,CronJob 的 .metadata.name 是命名这些 Pod 的部分基础。 CronJob 的名称必须是一个合法的 DNS 子域值, 但这会对 Pod 的主机名产生意外的结果。为获得最佳兼容性,名称应遵循更严格的 DNS 标签规则。 即使名称是一个 DNS 子域,它也不能超过 52 个字符。这是因为 CronJob 控制器将自动在你所提供的 Job 名称后附加 11 个字符,并且存在 Job 名称的最大长度不能超过 63 个字符的限制。

CronJob 使用

命令行创建用 --schedule 传 cron 表达式;下面是等效 yaml。

版本提示(重要): 课堂资料里写的是 apiVersion: batch/v1beta1,这是 1.21 之前的老写法 ,batch/v1beta1 在 1.25 起被移除 ,现在一律写 apiVersion: batch/v1。实验时按自己集群版本选择。

bash 复制代码
root@master30:~# kubectl create cronjob -h
Create a cronjob with the specified name.

Aliases:
cronjob, cj

Examples:
  # Create a cronjob
  kubectl create cronjob my-job --image=busybox --schedule="*/1 * * * *"
  
  # Create a cronjob with command
  kubectl create cronjob my-job --image=busybox --schedule="*/1 * * * *" -- date

......

Usage:
  kubectl create cronjob NAME --image=image --schedule='0/5 * * * ?' --
[COMMAND] [args...] [flags] [options]

示例:

bash 复制代码
root@master30:~# kubectl create cronjob mycronjob --image=hub.laoma.cloud/library/busybox --schedule='*/2 * * * *' -- echo hello k8s job!

等效的配置文件:

yaml 复制代码
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: mycronjob
spec:
  schedule: "*/2 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: hub.laoma.cloud/library/busybox
            command: ["echo", "hello k8s job! "]
          restartPolicy: Never

等待一段时间,最多创建3个job。

bash 复制代码
root@master30:~# kubectl get all
NAME                           READY   STATUS      RESTARTS   AGE
pod/mycronjob-28301160-gvm2v   0/1     Completed   0          5m18s
pod/mycronjob-28301162-25jll   0/1     Completed   0          3m18s
pod/mycronjob-28301164-h7mf7   0/1     Completed   0          78s

NAME                      SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
cronjob.batch/mycronjob   */2 * * * *   False     0        78s             36m

NAME                           COMPLETIONS   DURATION   AGE
job.batch/mycronjob-28301160   1/1           18s        5m18s
job.batch/mycronjob-28301162   1/1           19s        3m18s
job.batch/mycronjob-28301164   1/1           19s        78s
CronJob 参数

CronJob 的所有行为都靠下面几个字段控制。

Cron 时间表

大白话: cron 表达式是"分时日月周 "五段,空格隔开,* 代表"每一个":

.spec.schedule 字段是必需的,格式遵循 Cron 语法。

bash 复制代码
# ┌───────────── 分钟 (0 - 59)
# │ ┌───────────── 小时 (0 - 23)
# │ │ ┌───────────── 月的某天 (1 - 31)
# │ │ │ ┌───────────── 月份 (1 - 12)
# │ │ │ │ ┌───────────── 周的某天 (0 - 6)(周日到周一;在某些系统上,7 也是星期日)
# │ │ │ │ │                          或者是 sun,mon,tue,web,thu,fri,sat
# │ │ │ │ │
# │ │ │ │ │
# * * * * *

例如 0 0 13 * 5 表示此任务必须在每个星期五的午夜以及每个月的 13 日的午夜开始。

两个细节别踩坑(原文照抄官方文档,这里做个勘误补充):

  1. 表中 web 是笔误,星期三英文缩写应为 wed (Wednesday),完整顺序是 sun mon tue wed thu fri sat;
  2. 0 0 13 * 5 逐段翻译是"分=0、时=13、日=13、月=每月、周=5(周五)",也就是每月 13 号的 13:00 和每个周五的 13:00 都会触发------既不是午夜(午夜是 0 0 * * *),"日"和"周"两段同时指定时在 cron/K8s 里是并集(满足其一即可),不是"既要又要"。
任务模板

大白话: .spec.jobTemplate 是 CronJob 套娃的核心------它就是一份 Job 的模板,字段和 Job 的 spec 一模一样。

唯一区别:它是嵌套 在 CronJob 里面的,所以模板里不用再写 apiVersion 和 kind。到点后 CronJob 就照着这个模板捏一个 Job 出来。

.spec.jobTemplate ,为 CronJob 创建的 Job 定义模板,它是必需的。它和 Job 的语法完全一样, 只不过它是嵌套的,没有 apiVersion 和 kind。 你可以为模板化的 Job 指定通用的元数据, 例如标签或注解。 有关如何编写一个任务的 .spec, 请参考编写 Job 规约。

任务延迟开始的最后期限

大白话: 万一到点时集群很忙、或者节点挂了,任务没被准时创建,还允不允许补跑?startingDeadlineSeconds 就是"迟到容忍窗口"。

比如每天备份两次,允许最多晚 8 小时(28800 秒)开跑;超过这个点,这次备份就不补了(等下一班),因为太晚的备份已经失去意义。错过窗口的 Job 会被直接视为失败。

.spec.startingDeadlineSeconds 字段是可选的。 它表示任务如果由于某种原因错过了调度时间,开始该任务的截止时间的秒数。

  • 过了截止时间,CronJob 就不会开始该任务的实例(未来的任务仍在调度之中)。 例如,你每天运行两次备份任务,允许它最多延迟 8 小时开始,但不能更晚, 因为更晚进行的备份将变得没有意义:你宁愿等待下一次计划的运行。

  • 对于错过已配置的最后期限的 Job,Kubernetes 将其视为失败的任务。 如果你没有为 CronJob 指定 startingDeadlineSeconds,那 Job 就没有最后期限。

  • 如果 .spec.startingDeadlineSeconds 字段被设置(非空), CronJob 控制器将会计算从预期创建 Job 到当前时间的时间差。 如果时间差大于该限制,则跳过此次执行。例如,如果将其设置为 200,则 Job 控制器允许在实际调度之后最多 200 秒内创建 Job。

并发性规则

大白话:上一个任务还没跑完,下一个时间点又到了,怎么办?

策略 行为 打个比方
Allow(默认) 新旧任务并行,各跑各的 上一个保洁还没走,下一个保洁拎着拖把也进来了
Forbid 跳过本次,不允许并发 阿姨一看屋里有人,扭头就走,这次不干了
Replace 杀掉旧任务,用新任务顶替 阿姨直接把上一个阿姨请出去,自己接手

注意:并发策略只约束同一个 CronJob 创建的任务;不同 CronJob 之间永远互不影响。

.spec.concurrencyPolicy 也是可选的。它声明了 CronJob 创建的任务执行时发生重叠如何处理。 仅能声明下列规则中的一种:

  • Allow(默认):CronJob 允许并发任务执行。
  • Forbid: CronJob 不允许并发任务执行;如果新任务的执行时间到了而老任务没有执行完,CronJob 会忽略新任务的执行。
  • Replace:如果新任务的执行时间到了而老任务没有执行完,CronJob 会用新任务替换当前正在运行的任务。

请注意,并发性规则仅适用于相同 CronJob 创建的任务。如果有多个 CronJob,它们相应的任务总是允许并发执行的。

任务历史限制

大白话: CronJob 每次都留一个 Job 记录,攒多了列表没法看。默认保留 3 个成功、1 个失败 的历史 Job,超出的自动清理。设成 0 表示一个不留。

.spec.successfulJobsHistoryLimit 和 .spec.failedJobsHistoryLimit 字段是可选的。 这两个字段指定应保留多少已完成和失败的任务。 默认设置分别为 3 和 1。将限制设置为 0 代表相应类型的任务完成后不会保留。

有关自动清理任务的其他方式, 参见自动清理完成的 Job。

综合案例

两个落地案例,把前面学的 hostPath、调度、镜像权限串起来。

案例1:定期清理主机目录内容

思路四件套(背下来就是模板):

  1. 清理命令:rm -fr /var/data/*;
  2. 固定节点:nodeName: worker31.hyc.cloud,让 Pod 每次都落在同一台机器;
  3. 挂宿主机目录:hostPath 把 /var/data 挂进 Pod;
  4. 周期执行:套一层 CronJob,schedule: '* * * * *' 每分钟一次(实验用,生产别这么写)。

小细节:

  • 命令里先 sleep 3 再 rm,是给容器启动、挂载点就绪留缓冲时间;
  • restartPolicy: OnFailure 放在 Pod 模板层级(Job 的模板里);
  • jobTemplate 里没写 selector 和 template 标签也能跑------CronJob 会自动生成匹配的 selector 与标签。

创建一个pod定期每分钟执行一次清空worker31节点**/var/data**目录内容。

思路:

  1. 清理目录:rm -fr /var/data/*
  2. pod每次都在worker31上运行:使用 nodeName: worker31
  3. 将物理主机/var/data挂载给pod:使用 hostPath 类型 volume
  4. 周期性执行使用 cronjob
yaml 复制代码
apiVersion: batch/v1
kind: CronJob
metadata:
  name: clean
spec:
  jobTemplate:
    metadata:
      name: clean
    spec:
      template:
        metadata:
        spec:
          nodeName: worker31.hyc.cloud
          containers:
          - command:
            - sh
            - -c
            - sleep 3 && rm -fr /var/data/*
            image: hub.laoma.cloud/library/busybox
            name: clean
            volumeMounts:
            - mountPath: /var/data
              name: data
          volumes:
          - name: data
            hostPath:
              path: /var/data
          restartPolicy: OnFailure
  schedule: '* * * * *'
案例2:通过 cronjob 来操作 k8s 集群

思路: 让 Pod 里的容器自己会敲 kubectl。

  1. 用一个自带 kubectl 的镜像(这里用 bitnami/kubectl,已同步到私有仓库 hub.laoma.cloud/kubernetes/kubectl:1.30.2);
  2. 把当前集群的 kubeconfig 凭据通过 ConfigMap 挂进 Pod。

安全警告(生产必看):

  • 这个 Pod 挂的是你的管理员凭据,等于"定时执行的 cluster-admin",千万别把清理范围写得太宽;
  • ConfigMap 里的 config 文件会被挂载到 /.kube/config,而 bitnami/kubectl 镜像默认的 KUBECONFIG 路径正好是 /.kube/config,所以挂上就能直接用。

思路:

  1. 找到一个具有kubectl命令的镜像,我们这里使用 bitnami/kubectl。
  2. 将当前集群的凭据映射到pod中给kubectl使用。

过程如下:

bash 复制代码
# 创建cm保存kubectl凭据
[root@master controller]# kubectl create cm kubeconfig --from-file=config=/root/.kube/config

# 通过 volume 挂载给pod
bash 复制代码
apiVersion: batch/v1
kind: CronJob
metadata:
  name: clean
spec:
  schedule: '* * * * *'
  jobTemplate:
    metadata:
      name: clean
    spec:
      template:
        spec:
          restartPolicy: Never
          containers:
          - image: hub.laoma.cloud/kubernetes/kubectl:1.30.2
            imagePullPolicy: IfNotPresent
            name: kubectl
            args:
            - delete
            - pods
            - -l
            - app=hello
            - -n
            - controllers
            volumeMounts:
            - name: kubeconfig
              mountPath: "/.kube"
          volumes:
          - name: kubeconfig
            configMap:
              name: kubeconfig

hub.laoma.cloud/kubernetes/kubectl:v1.36.0 来源 bitnami/kubectl.

bitnami/kubectl镜像使用参考。

环境清理

大白话: 实验做完一键拆家:删除 controllers 命名空间,里面的 Deployment、RS、DS、Job、CronJob、Pod 全部级联删除,干干净净。

bash 复制代码
root@master30:~# kubectl delete ns controllers

谢幕 · 五控制器横向对比

维度 ReplicaSet Deployment DaemonSet Job CronJob
容器类型 服务类(常驻) 服务类(常驻) 服务类(常驻) 工作类(一次性) 工作类(周期性)
副本数怎么定 replicas 固定 replicas 固定 每个节点一个 成功 N 个即结束 每次调度一个 Job
典型能力 保副本、自愈 滚动更新、回滚、扩缩容、暂停 节点全覆盖 失败重试、并行、超时 cron 定时、并发策略
有没有 replicas 有 有 没有 用 completions 用 completions
典型出镜 一般不直接用 无状态 Web 服务 calico/kube-proxy/日志/监控 备份、算 pi、批处理 定时备份、定时清理
野 Pod 同标签 会被秒杀 底层 RS 同样会管 只认节点 不相关 不相关

一句话记忆口诀:

  • RS:守门员,盯着人头不放;
  • Deployment:包工头,RS 的上司,滚更回滚全靠它;
  • DS:宿管大妈,一个节点一个坑,新节点自动补;
  • Job:日结工,exit 0 就下班;
  • CronJob:闹钟精,到点派 Job 出门干活。

命令速查:

bash 复制代码
# 声明式三剑客
kubectl apply -f xxx.yaml
kubectl get all
kubectl describe <资源类型> <名字>

# 扩缩容
kubectl scale deployment web --replicas=3

# 更新与回滚
kubectl set image deployment/web nginx=hub.laoma.cloud/library/nginx:1.28
kubectl rollout status deployment web
kubectl rollout history deployment web
kubectl rollout undo deployment web --to-revision=1

# 污点(master 调度开关)
kubectl taint node master.hyc.cloud node-role.kubernetes.io/control-plane-    # 摘掉污点
kubectl taint node master.hyc.cloud node-role.kubernetes.io/control-plane:NoSchedule  # 加回污点

# 清理
kubectl delete ns controllers

几个一定要带走的考点:

  1. 节点宕机判定时间线:10s 心跳 → 40s 无心跳标 NotReady → 5 分钟(300s)后驱逐重建;
  2. maxSurge 管"最多超编",maxUnavailable 管"最多缺勤",默认都是 25%;
  3. 回滚不回到旧 REVISION 号,而是生成新 REVISION;
  4. Job 的 restartPolicy 只能是 Never 或 OnFailure;activeDeadlineSeconds 优先于 backoffLimit;
  5. CronJob 并发三策略:Allow / Forbid / Replace;现代版本 apiVersion 用 batch/v1。
相关推荐
时间售卖铺2 小时前
MySQL 库表设计:数据库范式详解
运维·数据库·mysql·云原生
马立杰2 小时前
Docker Build 编译补丁版 PHP7.4 FPM 容器镜像教程(CVE-2026-109618 CVE-2026-109619)
运维·docker·容器·php
tedcloud1233 小时前
Docker Compose 部署 Memos:从安装到备份恢复
linux·开发语言·jvm·后端·docker·容器·rust
无人生还别怕3 小时前
如何搭建k8s服务(docker部署)
linux·运维·docker·云原生·容器·kubernetes·openanolisos
姜鱼问生4 小时前
报数据库连不上?Docker Compose 启动顺序假象:depends_on 不是万能的!
数据库·docker·容器
我滴老baby4 小时前
自建服务20个,书签300条?我用一个Docker容器做了个“服务启动台”,手机也能打开
运维·docker·容器
lwx9148524 小时前
Docker容器模拟主机
运维·docker·容器
时间售卖铺4 小时前
MySQL 数据库账户及授权
运维·数据库·mysql·云原生
SelectDB技术团队4 小时前
SelectDB 架构演进与 ClickHouse 迁移指南
大数据·数据库·clickhouse·云原生·架构·数据分析·apache doris