Skip to content

Kubernetes 工作负载、滚动更新与任务调度

Pod 是 Kubernetes 的最小调度单位,但商业系统通常不直接管理裸 Pod,而是使用 Deployment、StatefulSet、DaemonSet、Job 和 CronJob 等工作负载控制器。它们不是几种不同的“启动命令”,而是针对不同运行语义设计的持续控制器。

本页围绕五个问题展开:应用是否长期运行、实例是否需要稳定身份、是否要求每个节点一个实例、任务是否以完成为目标、任务是否按时间周期创建。学习重点不是背对象名称,而是理解每个控制器创建什么下级对象、怎样判断完成、失败后如何重试、不合适的选型会造成什么后果。

学习目标

学完后应能:

  1. 解释为什么生产服务不应直接使用裸 Pod。
  2. 从 Deployment 追踪到 ReplicaSet 和 Pod,解释模板哈希与 Revision。
  3. 准确计算 maxSurgemaxUnavailable 和滚动更新容量边界。
  4. 解释 Readiness、minReadySecondsprogressDeadlineSeconds 如何影响发布。
  5. 解释优雅终止、Endpoint 移除、preStop、SIGTERM、宽限期和 SIGKILL。
  6. 区分暂停、继续、回滚和重新发布,并说明它们不能撤销哪些副作用。
  7. 解释 StatefulSet 的序号身份、Headless Service、PVC和有序更新。
  8. 解释为什么 StatefulSet 不会自动让数据库获得复制、一致性和故障转移。
  9. 解释 DaemonSet 如何选择节点及其 Host 权限风险。
  10. 解释 Job 的 completions、parallelism、重试、完成条件与幂等要求。
  11. 解释 CronJob 的 missed schedule、并发策略、时区和重复执行边界。
  12. 根据状态、Condition、Event 和日志定位不同 Workload 的生产故障。

一、先根据运行语义选对象

工作负载核心语义商业场景不适合
Deployment一组可替换的无状态长期副本Spring Boot API、网关、查询服务依赖固定实例身份和本地持久数据
StatefulSet有序且稳定身份的长期副本需要稳定Ordinal/DNS/PVC的集群成员误以为它自动提供数据库高可用
DaemonSet每个符合条件的Node一个Pod日志、监控、CNI、节点安全Agent普通按QPS水平扩缩的业务API
Job运行到成功完成数据迁移、一次性批处理、补数永不退出的常驻服务
CronJob按计划创建Job对账、日报、周期扫描要求毫秒精确或严格恰好一次
mermaid
flowchart TD
    A["需要运行什么工作" ] --> B{"是否长期运行"}
    B -- "否" --> C{"是否周期触发"}
    C -- "否" --> D["Job"]
    C -- "是" --> E["CronJob创建Job"]
    B -- "是" --> F{"是否每个Node一个"}
    F -- "是" --> G["DaemonSet"]
    F -- "否" --> H{"是否需要稳定序号/DNS/PVC"}
    H -- "否" --> I["Deployment"]
    H -- "是" --> J["StatefulSet"]

这里的“无状态”不是服务完全不访问数据,而是任意副本可以处理请求,持久状态放在数据库、对象存储、MQ 等外部系统;删除某个 Pod 后,新副本无需恢复该 Pod 本地唯一数据。

二、为什么不能直接创建裸Pod

裸 Pod 可以用于临时实验,但不适合长期商业服务:

  • Pod 所在 Node 故障后,没有上层 Workload 保证创建替代 Pod。
  • 没有版本化 ReplicaSet 和滚动更新策略。
  • 无法声明整体副本数和发布进度。
  • 删除后 UID 改变,不能把 Pod 当固定服务器。
  • 人工复制多个 YAML 容易出现配置漂移。

控制器的核心不是“Pod死了重启它”,而是重新读取当前状态并补齐期望:

mermaid
flowchart TD
    A["Workload Spec声明期望"] --> B["控制器通过Informer观察对象"]
    B --> C["计算期望与实际差异"]
    C --> D{"是否已经收敛"}
    D -- "否" --> E["创建、缩放、更新或删除下级对象"]
    E --> F["状态与事件写回API"]
    F --> B
    D -- "是" --> G["等待下一次变化"]

事件可能重复、丢失后重新 List,控制器必须按状态幂等 Reconcile,而不是假设每个事件只处理一次。

三、Deployment、ReplicaSet和Pod的关系

mermaid
flowchart TD
    A["Deployment:发布策略和期望副本"] --> B["ReplicaSet v1:某一Pod模板版本"]
    A --> C["ReplicaSet v2:新Pod模板版本"]
    B --> D["旧版Pod"]
    B --> E["旧版Pod"]
    C --> F["新版Pod"]

职责分别是:

  • Deployment Controller 管理 ReplicaSet 的创建、缩放、滚动更新和历史。
  • ReplicaSet Controller 维持某一个 Pod Template 版本的副本数。
  • Pod 被 Scheduler 绑定到 Node 后,由 kubelet 创建容器。

Deployment 不直接调用容器运行时,也不直接把现有 Pod 的镜像字段原地修改。Pod Template 改变时,它创建新的 ReplicaSet,再按策略缩放新旧 ReplicaSet。

四、Pod Template哈希和Revision

Deployment Controller 根据 Pod Template 生成 pod-template-hash,用于区分不同模板对应的 ReplicaSet。修改这些内容通常会产生新 ReplicaSet:

  • 容器镜像。
  • Command/Args。
  • 环境变量。
  • Pod Label/Annotation。
  • Probe、Resource、Volume、SecurityContext。

仅修改 Deployment 的 replicas 通常不会创建新 ReplicaSet,因为 Pod Template 没变。

bash
kubectl get deployment order-api -n commerce
kubectl get rs -n commerce -l app=order-api --show-labels
kubectl rollout history deployment/order-api -n commerce

Revision 是 Deployment 发布历史概念,不等于 Git Commit、镜像 Tag 或数据库版本。商业发布记录应同时保存:

text
Deployment Revision
Git Commit
镜像Digest
配置版本
数据库迁移版本
共享库/Helm Chart版本
发布时间和操作者

五、Deployment完整商业Demo

下面部署三个订单服务副本。Digest 为格式示例,实际使用 Registry 返回的真实值。

yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: order-api
  namespace: commerce
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-api
  namespace: commerce
  labels:
    app: order-api
spec:
  replicas: 3
  revisionHistoryLimit: 5
  minReadySeconds: 10
  progressDeadlineSeconds: 300
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: order-api
  template:
    metadata:
      labels:
        app: order-api
        version: git-8f03a1c
    spec:
      serviceAccountName: order-api
      terminationGracePeriodSeconds: 45
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: ScheduleAnyway
          labelSelector:
            matchLabels:
              app: order-api
      containers:
        - name: order-api
          image: registry.example.com/order-api@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 8080
          resources:
            requests:
              cpu: 250m
              memory: 512Mi
            limits:
              cpu: "1"
              memory: 1Gi
          startupProbe:
            httpGet:
              path: /actuator/health/liveness
              port: http
            periodSeconds: 5
            failureThreshold: 24
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: http
            periodSeconds: 5
            timeoutSeconds: 2
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: http
            periodSeconds: 10
            timeoutSeconds: 2
            failureThreshold: 3
          lifecycle:
            preStop:
              exec:
                command: ["sh", "-c", "sleep 5"]
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            runAsNonRoot: true
            capabilities:
              drop: ["ALL"]

示例只创建没有额外权限的 ServiceAccount,没有附加 RoleBinding。若应用不需要访问 Kubernetes API,就不应因为“以后可能用到”而授予资源读取权限。preStop 示例要求镜像中存在 sh;Distroless 镜像应依靠应用正确处理 SIGTERM,或使用镜像实际支持的受测 Hook,不能复制一条无法执行的命令。

5.1 selector为什么必须匹配template label

Deployment 用 Selector 确定自己管理哪些 Pod。如果:

yaml
selector:
  matchLabels:
    app: order-api

那么 Pod Template 必须包含相同 Label。Selector 创建后通常不可变,变更服务归属时应设计迁移,而不是企图在线随意改 Selector。

5.2 为什么生产优先Digest

Tag 可能被覆盖,相同 order-api:1.2.0 在不同时间可能指向不同内容。Digest 是镜像内容寻址标识,更适合审计、回滚和多环境推广。

5.3 Request和Limit影响什么

  • Scheduler主要根据 Request 判断节点是否能承诺容量。
  • CPU Limit 可能造成 Throttling。
  • Memory Limit 是容器总内存边界,超限可能 OOMKilled。
  • Request太小会造成过度装箱,太大则让Pod难以调度并浪费可承诺容量。

5.4 topologySpreadConstraints解决什么

如果三个副本全落在一个 Node,该 Node 故障会同时失去全部实例。拓扑分散约束尝试把副本分布到不同 Node/Zone。ScheduleAnyway 是软约束,容量不足时仍可调度;DoNotSchedule 是硬约束,拓扑不满足会让 Pod Pending。

六、一次滚动更新到底发生什么

更新镜像:

bash
kubectl set image deployment/order-api -n commerce \
  order-api=registry.example.com/order-api@sha256:<new-digest>

控制链:

mermaid
flowchart TD
    A["Deployment Pod Template改变"] --> B["Controller计算新Template Hash"]
    B --> C["创建新ReplicaSet,初始副本为0"]
    C --> D["按maxSurge扩容新ReplicaSet"]
    D --> E["新Pod被调度并启动"]
    E --> F{"Readiness和minReadySeconds是否满足"}
    F -- "否" --> G["等待并记录发布进度"]
    F -- "是" --> H["按maxUnavailable缩容旧ReplicaSet"]
    H --> I{"新副本是否全部可用"}
    I -- "否" --> D
    I -- "是" --> J["旧ReplicaSet缩到0并保留历史"]

注意:实际控制器会反复 Reconcile,并非单线程严格只执行图中一步。多个 Pod 创建、Ready、终止可能并行发生。

七、maxSurge和maxUnavailable如何计算

假设:

yaml
replicas: 10
maxSurge: 25%
maxUnavailable: 25%

百分比计算边界:

  • maxSurge 百分比向上取整:ceil(10 × 25%) = 3
  • maxUnavailable 百分比向下取整:floor(10 × 25%) = 2

因此滚动期间:

text
Pod总数上限 = replicas + maxSurge = 13
可用Pod下限 = replicas - maxUnavailable = 8

这里的总数上限描述的是控制器允许创建的非终止新旧副本边界。已经进入 Terminating、但尚未真正退出的 Pod 仍可能继续占用 CPU、内存、IP、Volume 和连接,所以 kubectl get pod 看到的对象数以及实际资源峰值可能暂时超过13。容量规划不能只按数学上的 Surge 数量,还要考虑优雅终止耗时。

7.1 replicas=3时为什么百分比结果容易误判

yaml
maxSurge: 25%
maxUnavailable: 25%

结果:

text
maxSurge = ceil(3 × 0.25) = 1
maxUnavailable = floor(3 × 0.25) = 0

这意味着更新时允许最多四个 Pod,总体至少保持三个可用副本。不要凭“25%大约一个”同时套用两个字段,因为取整方向不同。

7.2 两个值为什么不能同时为0

如果既不允许额外创建 Pod,也不允许减少任何可用 Pod,发布就无法前进。API 会阻止无意义组合。实际值还要考虑配额、节点容量、调度约束和 Terminating Pod。

7.3 maxSurge需要真实额外容量

maxSurge: 1 只是允许多创建一个,不会凭空产生 CPU、内存、IP 或配额。如果集群只能容纳原有副本,新 Pod 可能 Pending,且 maxUnavailable: 0 又不允许先缩旧实例,发布会卡住。

解决方法是提前预留发布容量、临时扩容节点、调整安全的发布策略,而不是在事故中直接删除旧 Pod。

八、Readiness、minReadySeconds和progressDeadline

8.1 Readiness决定能否接流量和参与可用副本

新容器 Running 后,Readiness 成功才通常成为 Service Ready Endpoint。Readiness 失败会阻止滚动更新继续缩减旧可用副本。

探针必须反映“当前能否安全处理新请求”,不能只固定返回200,也不要把所有外部依赖都设计成一抖动就摘除全部实例。

8.2 minReadySeconds防止刚Ready就被视为稳定

yaml
minReadySeconds: 10

新 Pod 需要连续保持 Ready 至少10秒,Deployment 才把它视为 Available。它可捕获“启动后立即Ready、数秒后崩溃”的问题,但不能替代业务验证。

8.3 progressDeadlineSeconds不会自动回滚

yaml
progressDeadlineSeconds: 300

发布在期限内没有取得进展时,Deployment Condition 可能标记:

text
Progressing=False
Reason=ProgressDeadlineExceeded

Kubernetes Deployment Controller不会因为这个字段自动执行安全回滚。流水线或发布平台需要检测失败、冻结放量、保留证据并根据策略回滚或前滚。

查看:

bash
kubectl get deployment order-api -n commerce -o yaml
kubectl describe deployment order-api -n commerce
kubectl rollout status deployment/order-api -n commerce --timeout=5m

九、优雅终止全过程

Pod 被滚动更新、缩容、驱逐或删除时,不应立刻 kill -9。一般终止链路为:

mermaid
flowchart TD
    A["Pod收到删除请求并设置deletionTimestamp"] --> B["终止宽限期开始计时"]
    B --> C["执行容器preStop Hook"]
    C --> D["向容器主进程发送SIGTERM"]
    D --> E["应用停止接新请求并处理在途请求"]
    E --> F{"是否在宽限期内退出"}
    F -- "是" --> G["容器正常结束"]
    F -- "否" --> H["宽限期后SIGKILL强制终止"]

与此同时,控制面会更新 EndpointSlice 等服务状态,使终止中的 Pod 不再作为正常新流量目标。但 Endpoint 更新、负载均衡传播、preStop 和进程信号并非一个全局原子操作,不能假设“先绝对停止所有流量,再收到 SIGTERM”。

9.1 preStop占用宽限期

宽限期计时在 preStop 之前就开始。若:

yaml
terminationGracePeriodSeconds: 30
preStop: sleep 25

应用真正收到 SIGTERM 后可能只剩很少时间完成关闭。preStop 应短小、可控,不要用长时间 sleep 掩盖未实现的优雅关闭。

9.2 Java应用要正确处理SIGTERM

容器入口应使用 exec form,让 Java 成为容器主进程并收到信号:

dockerfile
ENTRYPOINT ["java", "-jar", "/app/order-api.jar"]

应用关闭过程通常包括:

  • 停止接受新请求。
  • 等待有界时间处理在途请求。
  • 停止消费新消息并提交已完成结果。
  • 关闭线程池、数据库连接池和Telemetry导出。
  • 在宽限期内退出。

9.3 长连接和异步消费者更复杂

WebSocket、SSE、MQ Consumer、批处理需要定义 Drain 协议。仅从 Service 摘除不能终止已有长连接;仅收到 SIGTERM 也不保证消息没有重复投递。消费者必须实现幂等和正确的 Offset/ACK 边界。

9.4 强制删除的风险

bash
kubectl delete pod <pod> --grace-period=0 --force

这会绕过正常等待,不保证外部系统中的写入、锁、连接和消息状态安全。只有明确评估风险且普通终止失效时才使用,并记录事故原因。

十、发布暂停、继续和回滚

10.1 暂停发布

bash
kubectl rollout pause deployment/order-api -n commerce

暂停后可积累对 Pod Template 的修改,Deployment 不继续触发新 Rollout;具体行为应以目标版本验证。暂停不等于停止现有业务,也不撤销已经创建的新 Pod。

继续:

bash
kubectl rollout resume deployment/order-api -n commerce

10.2 查看历史

bash
kubectl rollout history deployment/order-api -n commerce
kubectl rollout history deployment/order-api -n commerce --revision=3

为变更记录原因:

bash
kubectl annotate deployment/order-api -n commerce \
  kubernetes.io/change-cause='release git=8f03a1c image=sha256:... ticket=REL-1024' \
  --overwrite

现代发布系统更应在外部保存完整审计,不应只依赖一个可修改 Annotation。

10.3 回滚

bash
kubectl rollout undo deployment/order-api -n commerce --to-revision=3
kubectl rollout status deployment/order-api -n commerce --timeout=5m

回滚本质上是把历史 Pod Template 重新作为当前期望,再发起一次滚动更新。它不能自动撤销:

  • 已执行的数据库 DDL/DML。
  • ConfigMap/Secret独立变更。
  • Service、Ingress、NetworkPolicy变更。
  • 已发送消息、邮件、支付请求。
  • 新版本已经写入的数据格式。
  • 外部系统副作用。

因此商业发布必须采用兼容迁移、不可变制品、业务补偿和版本指标,而不是把 rollout undo 当万能时光机。

十一、Recreate策略

yaml
strategy:
  type: Recreate

模板更新时,Deployment 通常先缩掉旧副本,再创建新副本,适合无法同时运行新旧版本的特殊应用,但会带来停机窗口。

它不等于任何情况下集群里绝不会同时出现新旧 Pod:人工创建、删除过程、旧 Pod 长时间 Terminating 等边界仍需观察。商业无状态API优先设计为新旧兼容并使用 RollingUpdate。

十二、扩缩容与HPA冲突边界

手工扩容:

bash
kubectl scale deployment/order-api -n commerce --replicas=6

HPA 管理同一 Deployment 时,会持续修改 spec.replicas。如果 GitOps 清单又固定 replicas: 3 并频繁同步,两个控制方可能来回覆盖。

应明确字段所有者:

  • HPA 管副本数时,GitOps模板避免持续强制固定运行值,或使用平台约定。
  • 发布系统只修改镜像/模板,不顺手覆盖HPA结果。
  • 扩容后验证下游数据库、MQ、外部接口是否能承载更多并发。

扩容 Pod 不能解决所有瓶颈。数据库连接上限只有100时,从10个Pod扩到50个,每个Pod仍配置20连接,反而可能压垮数据库。

十三、PodDisruptionBudget的真实作用

示例:

yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: order-api
  namespace: commerce
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: order-api

PDB 主要限制通过 Eviction API 发起的自愿中断,例如节点 Drain。它不能阻止:

  • Node突然宕机。
  • 容器崩溃。
  • OOMKilled。
  • 直接删除Pod或Workload的所有路径。
  • Deployment滚动更新本身;滚动可用性由Deployment策略控制。

PDB太严格可能让节点维护无法 Drain。replicas: 1minAvailable: 1 时,没有额外副本就不能正常驱逐。高可用需要副本、拓扑分散、容量和PDB共同设计。

十四、Deployment发布故障Runbook

14.1 先看总体进度

bash
kubectl get deployment order-api -n commerce
kubectl describe deployment order-api -n commerce
kubectl rollout status deployment/order-api -n commerce --timeout=60s

比较:

  • metadata.generationstatus.observedGeneration
  • replicas、updatedReplicas、readyReplicas、availableReplicas、unavailableReplicas。
  • Progressing、Available Condition及Reason。

14.2 找新旧ReplicaSet

bash
kubectl get rs -n commerce -l app=order-api \
  -o custom-columns='NAME:.metadata.name,DESIRED:.spec.replicas,CURRENT:.status.replicas,READY:.status.readyReplicas,AVAILABLE:.status.availableReplicas,REVISION:.metadata.annotations.deployment\.kubernetes\.io/revision'

14.3 找卡住的新Pod

bash
kubectl get pod -n commerce -l app=order-api -o wide --show-labels
kubectl describe pod <new-pod> -n commerce
kubectl logs <new-pod> -n commerce -c order-api --tail=300 --timestamps
kubectl logs <new-pod> -n commerce -c order-api --previous --tail=300 --timestamps

按阶段路由:

现象重点层次
Pending且NODE为空Request、Taint、Affinity、拓扑、PVC
ContainerCreatingRuntime、CNI、CSI、ConfigMap/Secret
ImagePullBackOffDigest、Registry、Secret、DNS/TLS
CrashLoopBackOffprevious日志、退出码、配置、OOM
Running但NotReadyReadiness路径、依赖、端口、超时
Ready后又崩溃minReadySeconds、启动后负载、Probe、资源

14.4 为什么滚动更新卡住后旧Pod不减少

maxUnavailable: 0 时,新 Pod 未 Available,控制器不能继续缩减旧可用副本。这是保护可用性的正常行为,不是控制器“不会更新”。根因通常在新 Pod 的调度、启动或 Readiness。

十五、StatefulSet解决的到底是什么

StatefulSet 给每个副本稳定的序号身份,并可为每个序号绑定独立 PVC。典型名称:

text
mysql-0
mysql-1
mysql-2

这些 Pod 被重建后名称/Ordinal可以保持,配套 PVC 仍能关联同一序号。但 Pod UID、容器进程和 Pod IP 仍会变化。

适合:

  • 集群成员需要稳定名称。
  • 每个成员有独立持久卷。
  • 启动/终止顺序对系统有意义。
  • 应用自身理解复制、选主、分片和恢复。

不适合因为“服务用了数据库”就把业务API改成 StatefulSet。业务API访问外部数据库,自己仍通常是无状态 Deployment。

十六、Headless Service和稳定DNS

StatefulSet常配合 Headless Service:

yaml
apiVersion: v1
kind: Service
metadata:
  name: mysql
  namespace: data
spec:
  clusterIP: None
  selector:
    app: mysql
  ports:
    - name: mysql
      port: 3306

稳定 DNS 形式通常类似:

text
mysql-0.mysql.data.svc.cluster.local
mysql-1.mysql.data.svc.cluster.local

具体DNS可用性还受 Pod Ready、publishNotReadyAddresses、CoreDNS和集群域配置影响。稳定DNS不代表该成员一定健康,也不自动提供主从路由。

十七、StatefulSet完整结构

下面只展示身份与存储结构,不等于一套可直接投产的 MySQL 高可用方案。

yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  namespace: data
spec:
  serviceName: mysql
  replicas: 3
  podManagementPolicy: OrderedReady
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 0
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      terminationGracePeriodSeconds: 60
      containers:
        - name: mysql
          image: registry.example.com/mysql@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
          ports:
            - name: mysql
              containerPort: 3306
          volumeMounts:
            - name: data
              mountPath: /var/lib/mysql
          readinessProbe:
            exec:
              command: ["sh", "-c", "mysqladmin ping -h 127.0.0.1"]
            periodSeconds: 10
          resources:
            requests:
              cpu: "1"
              memory: 2Gi
            limits:
              memory: 4Gi
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: fast-block
        resources:
          requests:
            storage: 100Gi

这个示例没有配置初始化账号、TLS、复制、备份、恢复和选主,因此只能用于理解资源关系,不能声称是生产MySQL集群。

十八、StatefulSet创建与删除顺序

默认 OrderedReady 常见行为:

text
创建:mysql-0 Ready → mysql-1 Ready → mysql-2 Ready
缩容/删除:通常从最高Ordinal反向处理

如果 mysql-0 永远不 Ready,后续序号可能无法继续创建。这不是控制器坏了,而是有序策略在保护成员初始化顺序。

podManagementPolicy: Parallel 允许更并行地创建/终止,适合不依赖顺序的应用;不能只为“发布更快”给有严格成员顺序的系统改成 Parallel。

十九、volumeClaimTemplates和PVC生命周期

每个 Pod Ordinal通常得到独立 PVC,例如:

text
data-mysql-0
data-mysql-1
data-mysql-2

Pod mysql-1 重建后重新挂载 data-mysql-1。PVC/PV能保留磁盘,不等于数据一定安全:

  • 应用可能写坏数据。
  • 单盘可能损坏。
  • StorageClass回收策略可能不符合预期。
  • AZ故障可能无法在另一拓扑挂载。
  • 没有一致性备份就无法恢复逻辑错误。

缩容或删除 StatefulSet 时,PVC 默认常被保留以防数据误删;较新版本支持持久卷声明保留策略,但必须以目标集群版本和实际字段为准。删除前列出 StatefulSet、PVC、PV、StorageClass和备份,不要假设控制器替你决定数据命运。

不要轻易对 StatefulSet Pod 使用 --grace-period=0 --force。API对象被强制删除并不证明旧节点上的进程已经停止;控制器可能创建同名替代成员,而旧成员仍访问原有网络或存储,造成两个实例声称同一身份。对需要单实例写入的数据库,这可能引发脑裂和数据损坏。应先隔离/确认旧节点和进程,再按该中间件的成员恢复协议处理。

二十、StatefulSet更新策略和partition

RollingUpdate 通常按较高 Ordinal 向较低 Ordinal 更新,并等待前一成员 Ready。partition 可做分区更新:

yaml
rollingUpdate:
  partition: 2

在三个副本 0,1,2 中,通常只有 Ordinal 大于等于2的 Pod 使用新模板,可用于手工金丝雀。确认新成员数据格式、复制和业务指标正常后,再逐步降低 partition。

OnDelete 策略不会因模板变化自动替换已有 Pod,必须人工删除后才按新模板重建。它给予更多控制,也更容易出现版本漂移,需要严格 Runbook。

二十一、为什么StatefulSet不等于数据库高可用

StatefulSet提供的是编排原语,不会自动实现:

  • 数据复制。
  • Leader选举。
  • Quorum和一致性协议。
  • 故障成员重新加入。
  • 主从切换和客户端路由。
  • 备份、PITR和恢复验证。
  • Schema兼容与滚动升级。
  • 防止Split Brain。

这些必须由数据库本身、Operator或受控运维系统实现。把单机MySQL镜像改成 replicas: 3,三个实例只会各自拥有不同数据盘,不会自动成为同一数据库集群。

二十二、StatefulSet故障排查

bash
kubectl get statefulset,pod,pvc -n data
kubectl describe statefulset mysql -n data
kubectl get controllerrevision -n data
kubectl describe pod mysql-0 -n data
kubectl describe pvc data-mysql-0 -n data

常见现象:

现象可能原因
mysql-1不创建mysql-0未Ready且使用OrderedReady
Pod PendingPVC未绑定、拓扑、资源或调度约束
Pod重建后数据为空挂错PVC、应用数据目录不一致、初始化覆盖
更新停在某Ordinal新成员Readiness/复制/启动失败
删除STS后PVC还在默认数据保护行为,不是泄漏结论
DNS无法解析成员Headless Service、Selector、Ready、CoreDNS

不要为了让更新继续而直接把数据库 Readiness 改成固定成功,否则可能把未同步成员交给客户端。

二十三、DaemonSet如何保证节点覆盖

DaemonSet Controller 根据 Node、NodeSelector/Affinity、Taint/Toleration 等条件,为每个符合条件的 Node 创建一个 Pod。

mermaid
flowchart TD
    A["DaemonSet声明Pod模板和节点条件"] --> B["Controller观察Node集合"]
    B --> C{"Node是否符合条件"}
    C -- "是且没有Pod" --> D["为该Node创建Daemon Pod"]
    C -- "否但已有Pod" --> E["删除不应存在的Pod"]
    C -- "状态正确" --> F["保持观察"]

新增 Node 后,控制器会为其创建 Daemon Pod;Node 删除后对应对象被清理。并非绝对“所有节点”都有实例:不匹配 Label、Taint未容忍、资源不足、调度失败都会让覆盖不足。

二十四、DaemonSet商业Demo

日志采集 Agent 示例:

yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-log-agent
  namespace: observability
spec:
  selector:
    matchLabels:
      app: node-log-agent
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 10%
  template:
    metadata:
      labels:
        app: node-log-agent
    spec:
      serviceAccountName: node-log-agent
      tolerations:
        - operator: Exists
          effect: NoSchedule
      containers:
        - name: agent
          image: registry.example.com/node-log-agent@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              memory: 512Mi
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            runAsNonRoot: true
          volumeMounts:
            - name: varlog
              mountPath: /var/log
              readOnly: true
      volumes:
        - name: varlog
          hostPath:
            path: /var/log
            type: Directory

24.1 hostPath为什么危险

hostPath把节点文件系统目录暴露给容器。错误地挂载 /、容器运行时Socket、kubelet凭据并赋予写权限,可能导致节点级权限突破。应:

  • 只挂必要目录。
  • 尽可能只读。
  • 不默认 privileged。
  • 使用专用ServiceAccount和最小RBAC。
  • 对镜像、配置和升级做供应链审计。

24.2 toleration不是调度目标

Toleration只是允许 Pod进入带某Taint的节点,不保证一定选择这些节点。要限制只运行某类节点,还需 NodeSelector/Affinity。

24.3 DaemonSet升级风险

日志/CNI/安全Agent故障可能同时影响大量Node。使用小比例 maxUnavailable、节点分组金丝雀、健康指标和回滚。具体滚动字段能力受Kubernetes版本影响,升级前应在目标版本验证。

二十五、DaemonSet排查

bash
kubectl get daemonset node-log-agent -n observability
kubectl describe daemonset node-log-agent -n observability
kubectl get pod -n observability -l app=node-log-agent -o wide
kubectl get node --show-labels

关键计数:Desired、Current、Ready、Available、Unavailable、Misscheduled。某Node没有Pod时检查:

  • Node是否符合Selector/Affinity。
  • Taint是否被容忍。
  • Node是否可调度、Ready。
  • HostPort/资源是否冲突。
  • 镜像、CNI、Volume是否失败。

二十六、Job的完成语义

Deployment目标是持续拥有可用副本;Job目标是获得指定数量的成功完成。Pod 成功退出码通常为0,Job Controller记录成功;失败时根据策略创建或重启执行尝试。

mermaid
flowchart TD
    A["创建Job"] --> B["Job Controller创建Pod"]
    B --> C["Pod执行批任务"]
    C --> D{"容器是否成功退出"}
    D -- "是" --> E["增加Succeeded计数"]
    D -- "否" --> F["记录失败并按策略重试"]
    F --> G{"是否超过失败/期限边界"}
    G -- "否" --> B
    G -- "是" --> H["Job标记Failed"]
    E --> I{"是否达到完成条件"}
    I -- "否" --> B
    I -- "是" --> J["Job标记Complete"]

二十七、Job关键字段怎么配合

yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: asset-rebuild-20260715
  namespace: data-jobs
  labels:
    job: asset-rebuild
spec:
  completions: 20
  parallelism: 4
  backoffLimit: 3
  activeDeadlineSeconds: 3600
  ttlSecondsAfterFinished: 86400
  template:
    metadata:
      labels:
        job: asset-rebuild
    spec:
      restartPolicy: Never
      containers:
        - name: worker
          image: registry.example.com/asset-rebuild@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
          args: ["--batch-source=database", "--run-id=asset-rebuild-20260715"]
          resources:
            requests:
              cpu: 500m
              memory: 512Mi
            limits:
              cpu: "2"
              memory: 2Gi
字段含义
completions需要多少次成功完成
parallelism最多并行运行多少Pod
backoffLimitJob允许的失败重试边界,具体计数受策略/版本影响
activeDeadlineSeconds整个Job允许活跃的总时间,不是单个HTTP超时
ttlSecondsAfterFinished完成后多久由TTL机制清理Job及下级对象
restartPolicyJob Pod通常使用Never或OnFailure

27.1 completions和parallelism不是一回事

text
completions=20
parallelism=4

表示最多同时四个 Pod 工作,累计需要20次成功。它不是“创建20个Pod且永不重试”,失败后实际创建数量可能更多。

27.2 restartPolicy Never与OnFailure

  • Never:容器失败后 Pod 终止,Job Controller创建新Pod重试,失败实例日志和状态较清楚。
  • OnFailure:kubelet可能在同一Pod内重启失败容器,Pod名称不变但Restart Count增加。

排查时二者取证方式不同。对于批任务,Never 常更利于保留每次尝试证据,但应根据任务和版本策略选择。

27.3 activeDeadlineSeconds优先终止长时间任务

达到 Job 活跃期限后,仍运行的 Pod 会被终止并把 Job 标记失败。应用仍应实现自身网络/数据库超时,因为 Job总期限不能替代每一步超时。

27.4 TTL清理和日志保留

TTL清理会删除完成的 Job/Pod 对象。任务日志、结果、审计和失败原因必须提前写入外部系统;不要依赖一周后还能 kubectl logs

二十八、Job为什么必须幂等

分布式控制和节点故障下,某次工作可能已经对外成功,但成功状态没有及时写回;Controller可能再创建一次尝试。CronJob也可能重复创建。因此业务不能假设严格恰好一次。

商业幂等方法:

  • 为每次业务单元使用稳定 runId + shardId
  • 数据库建立唯一键/幂等记录。
  • 状态机使用条件更新。
  • 外部支付/通知使用对方幂等键。
  • 输出采用临时文件/表,完成后原子发布。
  • 重试前查询已完成状态。

错误做法:每次Pod启动都生成随机批次ID,然后直接插入结果。重试会被识别成新任务,造成重复扣费、重复通知或重复数据。

二十九、Indexed Job与分片

需要把任务拆成固定索引时,可使用 Indexed Completion Mode(能力和字段以目标版本为准):

yaml
spec:
  completionMode: Indexed
  completions: 20
  parallelism: 4

每个Pod可获得完成索引,用来处理对应分片。应用必须定义:

  • index怎样映射数据范围。
  • 数据增长期间边界是否稳定。
  • 单分片失败如何重试。
  • 是否允许不同分片并发写同一资源。
  • 汇总何时认为完整。

不要用 id % 20 直接处理不断变化且需要一致快照的数据,除非明确接受扫描期间新增/修改的语义。

三十、Job失败策略和排查

较新Kubernetes支持更精细的 Pod Failure Policy 等能力,可按退出码或Pod Condition决定忽略、计数或直接失败;具体字段成熟度必须按目标版本验证。

基础排查:

bash
kubectl get job,pod -n data-jobs -l job=asset-rebuild
kubectl describe job asset-rebuild-20260715 -n data-jobs
kubectl get pod -n data-jobs -l job-name=asset-rebuild-20260715
kubectl logs <job-pod> -n data-jobs --timestamps
kubectl get events -n data-jobs --sort-by=.metadata.creationTimestamp

检查:

  • Active、Succeeded、Failed数量。
  • Job Condition和FailureTarget/Failed等原因。
  • Pod退出码和日志。
  • 是否卡在调度、镜像、挂载或应用阶段。
  • 外部结果是否已部分写入。
  • 重跑是否安全。

失败Job不要不看结果就删除重建。先确认已经完成哪些分片和外部副作用,否则手工重跑会放大重复。

三十一、CronJob不是集群级精确定时器

CronJob Controller 根据 Schedule 周期创建 Job。它通常按控制循环检查,不承诺毫秒级准时,也不承诺严格只创建一次。控制面不可用、启动截止时间、并发策略和时钟都会影响实际创建。

mermaid
flowchart TD
    A["CronJob声明schedule和策略"] --> B["Controller计算应触发的时间点"]
    B --> C{"是否错过且仍在截止范围"}
    C -- "否" --> D["本次不创建或记录错过"]
    C -- "是" --> E["根据concurrencyPolicy检查现有Job"]
    E --> F{"允许创建吗"}
    F -- "否" --> G["跳过本次"]
    F -- "是" --> H["创建带计划时间语义的Job"]
    H --> I["Job独立执行、重试和完成"]

三十二、CronJob商业Demo

yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: daily-reconciliation
  namespace: finance
spec:
  schedule: "0 2 * * *"
  timeZone: "Asia/Shanghai"
  concurrencyPolicy: Forbid
  startingDeadlineSeconds: 900
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 5
  jobTemplate:
    spec:
      backoffLimit: 2
      activeDeadlineSeconds: 7200
      ttlSecondsAfterFinished: 604800
      template:
        spec:
          restartPolicy: Never
          containers:
            - name: reconciliation
              image: registry.example.com/reconciliation@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
              args:
                - "--mode=daily-reconciliation"
                - "--business-time-zone=$(BUSINESS_TIME_ZONE)"
              env:
                - name: BUSINESS_TIME_ZONE
                  value: "Asia/Shanghai"
              resources:
                requests:
                  cpu: 500m
                  memory: 1Gi
                limits:
                  cpu: "2"
                  memory: 4Gi

spec.timeZone 需要目标 Kubernetes 版本支持;老集群不能把不支持的字段当成已生效。示例应用还必须从受审计的调度记录/运行参数确定“本次应该处理的业务日期”,不能简单使用 Pod 实际启动时刻,否则延迟补跑可能处理错日期。实际平台可在创建 Job 时记录计划触发时间,并将其作为稳定 runId 的一部分。

三十三、Cron表达式和时区

text
0 2 * * *
│ │ │ │ │
│ │ │ │ └─ 星期
│ │ │ └─── 月
│ │ └───── 日
│ └─────── 小时
└───────── 分钟

它表示每天 02:00。必须明确时区、夏令时和业务日期。金融对账“自然日”可能按业务地区时区,而集群节点/控制面可能使用另一时区。

时区发生夏令时跳变时,某个本地时间可能不存在或出现两次。关键业务应定义遇到时区跳变的处理规则,并以幂等业务日期为准。

三十四、concurrencyPolicy三种策略

策略新触发到来且上一次未完成风险
Allow允许并行创建新Job两批可能竞争同一数据
Forbid跳过本次并发执行长任务可能导致后续计划被错过
Replace尝试替换当前Job旧任务外部副作用不会自动撤销

Forbid 只阻止同一个 CronJob 创建的 Job 重叠,不是跨集群全局锁。应用若部署在灾备双集群,两个 CronJob仍可能同时执行。

Replace 终止旧 Job 对象不等于数据库事务、外部支付或已发送消息被回滚,仍需幂等和补偿。

三十五、startingDeadlineSeconds和错过调度

控制面故障或 CronJob Suspend 后恢复,Controller会判断错过的计划是否还在允许启动的截止范围。

yaml
startingDeadlineSeconds: 900

表示错过计划后超过15分钟通常不再启动该次任务。设得太短可能因为短暂控制面抖动漏任务;无限补跑又可能在恢复后同时制造大量历史任务。要结合业务是否允许补跑、数据窗口和容量设计。

CronJob Controller按控制循环检查计划,并不是每秒都保证触发。某些版本中,如果 startingDeadlineSeconds 小于约10秒,可能因为小于控制器检查周期而无法按预期调度;具体周期和行为要以目标版本为准。关键任务不要设置脱离控制器时间粒度的极短截止时间。

CronJob名称还会被用于派生Job名称,控制器需要追加时间相关后缀。CronJob自身名称不能把DNS名称最大长度全部用满,常见限制是最多52个字符;提交前应通过目标集群服务端Dry Run验证。

三十六、Suspend不是取消已运行Job

bash
kubectl patch cronjob daily-reconciliation -n finance \
  --type=merge -p '{"spec":{"suspend":true}}'

Suspend 阻止后续计划创建新 Job,不会自动终止已经运行的 Job。恢复时是否补算错过时间受 Deadline 和控制器语义影响,操作前应查看活动 Job 和业务结果。

三十七、CronJob生产排查

bash
kubectl get cronjob daily-reconciliation -n finance -o wide
kubectl describe cronjob daily-reconciliation -n finance
kubectl get job -n finance --sort-by=.metadata.creationTimestamp
kubectl get pod -n finance -l job-name=<job-name>
kubectl logs job/<job-name> -n finance --timestamps

检查:

  • Schedule、TimeZone、Suspend。
  • LastScheduleTime、LastSuccessfulTime。
  • Active Job引用。
  • Event中是否错过、禁止并发或创建失败。
  • Job是否因Deadline、Backoff、资源、镜像、应用错误失败。
  • 业务幂等表中是否已有该业务日期结果。

37.1 没有创建Job

可能原因:

  • Cron表达式与时区理解错误。
  • CronJob被Suspend。
  • Forbid且上一个Job仍活跃。
  • 错过时间超过startingDeadline。
  • Controller/Control Plane异常。
  • 名称或对象校验问题。

37.2 创建了两个Job

不能只说“K8s Bug”。检查:

  • 是否有两个不同CronJob/集群。
  • 人工 create job --from=cronjob/...
  • 控制器恢复和重试边界。
  • Schedule/时区变化。
  • 前一个Job只是状态未及时写回。

无论平台原因如何,任务都应有业务幂等键。

三十八、一次性数据库迁移应该用Job吗

可以用 Job 承载执行环境,但迁移治理不能只靠 Job:

  • 迁移脚本必须版本化和审计。
  • 使用数据库迁移锁/版本表防止重复并发。
  • DDL要评估锁表、磁盘、复制延迟。
  • 采用 Expand → Migrate → Contract 保持新旧应用兼容。
  • Job失败后先判断数据库已经执行到哪一步。
  • 不要在每个应用Pod启动时都并发执行不可重入DDL。

高风险迁移通常需要发布审批、备份、监控和专用Runbook。

三十九、通用Workload配置清单

长期服务至少评估:

  • 不可变镜像Digest。
  • Request/Limit。
  • Startup/Readiness/Liveness。
  • terminationGracePeriod和优雅关闭。
  • ServiceAccount、RBAC和SecurityContext。
  • Config/Secret版本关联。
  • 拓扑分散和副本容量。
  • 日志、指标、Trace和版本Label。
  • 滚动更新策略和回滚边界。

批任务额外评估:

  • 业务幂等键。
  • 单步超时和总Deadline。
  • 重试与永久错误分类。
  • 并发和分片。
  • 部分成功后的恢复。
  • 结果和日志长期保存。
  • 清理策略。

四十、常见误区

  1. Deployment会原地修改Pod镜像:实际是创建新ReplicaSet和新Pod。
  2. apply成功就是发布成功:只证明对象被API接受。
  3. progressDeadlineSeconds会自动安全回滚:它主要标记进度失败。
  4. rollout undo能撤销数据库和消息:它主要回退Pod Template。
  5. preStop不占终止时间:宽限期在Hook前已经开始。
  6. PDB能防止所有Pod减少:它主要约束自愿Eviction。
  7. StatefulSet自动把三个MySQL组成集群:它只提供身份/存储编排原语。
  8. 删除StatefulSet一定删除磁盘:PVC生命周期需单独确认。
  9. DaemonSet绝对每个Node都有Pod:节点条件和失败可能导致缺失。
  10. Job失败重试不会重复副作用:必须业务幂等。
  11. CronJob严格恰好一次且准点:控制循环和分布式故障不保证。
  12. Forbid是跨集群全局锁:它只约束该CronJob的并发Job。

四十一、面试标准回答

Deployment、ReplicaSet和Pod什么关系

Deployment管理发布策略和ReplicaSet;每个ReplicaSet对应一个Pod Template版本并维持该版本副本;Pod是最终调度单位。模板改变时Deployment创建新ReplicaSet,按maxSurge/maxUnavailable扩新缩旧,而不是原地修改旧Pod。

maxSurge和maxUnavailable如何计算

maxSurge限制滚动期间超出期望副本的数量,百分比向上取整;maxUnavailable限制可用副本最多减少多少,百分比向下取整。replicas=3且两者25%时分别为1和0。配置还需要真实节点、配额和IP容量,否则新Pod仍可能Pending。

Deployment发布卡住为什么不自动回滚

新Pod未Ready或未满足minReadySeconds时,maxUnavailable可能阻止继续缩旧副本;progressDeadlineSeconds超时会把Condition标为ProgressDeadlineExceeded,但控制器不自动判断业务安全并回滚。发布平台要保留证据,根据版本指标选择回滚或前滚。

Pod优雅终止过程是什么

删除后deletionTimestamp和宽限期开始,kubelet执行preStop,再向主进程发SIGTERM;应用停止接新请求、处理在途工作并退出,超时后才SIGKILL。同时Endpoint状态会变化,但传播与信号不是全局原子顺序,所以应用必须实现Drain,preStop也会消耗宽限时间。

StatefulSet和Deployment区别

Deployment管理可替换无状态副本;StatefulSet为每个Ordinal提供稳定名称、DNS语义和独立PVC,并支持有序创建更新。StatefulSet只提供编排原语,不自动实现数据库复制、选主、Quorum、备份和故障转移。

Job怎样避免重复执行

Kubernetes重试、节点故障和状态写回延迟可能让同一业务单元再次执行,不能依赖平台严格恰好一次。使用稳定runId/shardId、数据库唯一键、条件状态更新、外部幂等键和原子结果发布;重跑前查询已完成状态。

CronJob三种并发策略是什么

Allow允许新旧Job并行;Forbid在上一Job未完成时跳过本次;Replace尝试用新Job替换旧Job。三者都不能回滚旧任务已产生的数据库、消息和外部副作用,也不是跨集群全局锁,任务仍需幂等。

四十二、学习实验与验收

实验一:观察新旧ReplicaSet

  1. 创建三副本Deployment。
  2. 记录Deployment Revision、ReplicaSet和Pod Template Hash。
  3. 更新镜像Digest。
  4. 持续观察新RS扩容、旧RS缩容。
  5. 只修改replicas,验证是否产生新RS。

实验二:制造滚动更新卡住

把新版本Readiness路径改错,并设置 maxUnavailable: 0。观察旧Pod为何保留、新Pod为何Running但NotReady、Deployment Condition如何变化。不要删除旧Pod。

实验三:验证优雅终止

应用记录收到SIGTERM、停止接流量、在途请求结束和进程退出时间。删除一个Pod,比较deletionTimestamp、EndpointSlice状态、preStop、SIGTERM和退出日志,验证宽限期是否足够。

实验四:StatefulSet身份和PVC

创建三副本测试StatefulSet,记录Pod名称、UID、IP、PVC;删除 app-1 后验证名称/PVC保持、UID/IP可能变化。缩容前后观察PVC,不执行数据删除。

实验五:Job重试与幂等

让某分片在外部写入后故意退出,观察Job重试;先用无幂等实现复现重复,再通过唯一键和runId修复。

实验六:CronJob并发策略

让任务运行时间超过Schedule间隔,分别验证Allow、Forbid、Replace的Job对象和业务结果,说明为什么Replace仍不能撤销外部副作用。

验收清单

  1. 能从Deployment找到新旧ReplicaSet、Revision和Template Hash。
  2. 能手算整数和百分比Surge/Unavailable边界。
  3. 能解释Readiness、minReadySeconds和ProgressDeadline关系。
  4. 能完整画出Pod优雅终止链路。
  5. 能列出rollout undo无法撤销的对象和副作用。
  6. 能解释StatefulSet稳定与不稳定的身份字段。
  7. 能解释Headless Service、Ordinal和PVC关系。
  8. 能审计DaemonSet缺失节点和Host权限风险。
  9. 能解释Job completions、parallelism、Backoff和Deadline。
  10. 能为批任务设计业务幂等和部分成功恢复。
  11. 能解释CronJob时区、错过计划和三种并发策略。
  12. 能根据Condition、Event、日志和指标完成Workload排障。

关联知识点