Kubernetes 工作负载、滚动更新与任务调度
Pod 是 Kubernetes 的最小调度单位,但商业系统通常不直接管理裸 Pod,而是使用 Deployment、StatefulSet、DaemonSet、Job 和 CronJob 等工作负载控制器。它们不是几种不同的“启动命令”,而是针对不同运行语义设计的持续控制器。
本页围绕五个问题展开:应用是否长期运行、实例是否需要稳定身份、是否要求每个节点一个实例、任务是否以完成为目标、任务是否按时间周期创建。学习重点不是背对象名称,而是理解每个控制器创建什么下级对象、怎样判断完成、失败后如何重试、不合适的选型会造成什么后果。
学习目标
学完后应能:
- 解释为什么生产服务不应直接使用裸 Pod。
- 从 Deployment 追踪到 ReplicaSet 和 Pod,解释模板哈希与 Revision。
- 准确计算
maxSurge、maxUnavailable和滚动更新容量边界。 - 解释 Readiness、
minReadySeconds、progressDeadlineSeconds如何影响发布。 - 解释优雅终止、Endpoint 移除、preStop、SIGTERM、宽限期和 SIGKILL。
- 区分暂停、继续、回滚和重新发布,并说明它们不能撤销哪些副作用。
- 解释 StatefulSet 的序号身份、Headless Service、PVC和有序更新。
- 解释为什么 StatefulSet 不会自动让数据库获得复制、一致性和故障转移。
- 解释 DaemonSet 如何选择节点及其 Host 权限风险。
- 解释 Job 的 completions、parallelism、重试、完成条件与幂等要求。
- 解释 CronJob 的 missed schedule、并发策略、时区和重复执行边界。
- 根据状态、Condition、Event 和日志定位不同 Workload 的生产故障。
一、先根据运行语义选对象
| 工作负载 | 核心语义 | 商业场景 | 不适合 |
|---|---|---|---|
| Deployment | 一组可替换的无状态长期副本 | Spring Boot API、网关、查询服务 | 依赖固定实例身份和本地持久数据 |
| StatefulSet | 有序且稳定身份的长期副本 | 需要稳定Ordinal/DNS/PVC的集群成员 | 误以为它自动提供数据库高可用 |
| DaemonSet | 每个符合条件的Node一个Pod | 日志、监控、CNI、节点安全Agent | 普通按QPS水平扩缩的业务API |
| Job | 运行到成功完成 | 数据迁移、一次性批处理、补数 | 永不退出的常驻服务 |
| CronJob | 按计划创建Job | 对账、日报、周期扫描 | 要求毫秒精确或严格恰好一次 |
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死了重启它”,而是重新读取当前状态并补齐期望:
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的关系
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 没变。
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 commerceRevision 是 Deployment 发布历史概念,不等于 Git Commit、镜像 Tag 或数据库版本。商业发布记录应同时保存:
Deployment Revision
Git Commit
镜像Digest
配置版本
数据库迁移版本
共享库/Helm Chart版本
发布时间和操作者五、Deployment完整商业Demo
下面部署三个订单服务副本。Digest 为格式示例,实际使用 Registry 返回的真实值。
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。如果:
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。
六、一次滚动更新到底发生什么
更新镜像:
kubectl set image deployment/order-api -n commerce \
order-api=registry.example.com/order-api@sha256:<new-digest>控制链:
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如何计算
假设:
replicas: 10
maxSurge: 25%
maxUnavailable: 25%百分比计算边界:
maxSurge百分比向上取整:ceil(10 × 25%) = 3。maxUnavailable百分比向下取整:floor(10 × 25%) = 2。
因此滚动期间:
Pod总数上限 = replicas + maxSurge = 13
可用Pod下限 = replicas - maxUnavailable = 8这里的总数上限描述的是控制器允许创建的非终止新旧副本边界。已经进入 Terminating、但尚未真正退出的 Pod 仍可能继续占用 CPU、内存、IP、Volume 和连接,所以 kubectl get pod 看到的对象数以及实际资源峰值可能暂时超过13。容量规划不能只按数学上的 Surge 数量,还要考虑优雅终止耗时。
7.1 replicas=3时为什么百分比结果容易误判
maxSurge: 25%
maxUnavailable: 25%结果:
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就被视为稳定
minReadySeconds: 10新 Pod 需要连续保持 Ready 至少10秒,Deployment 才把它视为 Available。它可捕获“启动后立即Ready、数秒后崩溃”的问题,但不能替代业务验证。
8.3 progressDeadlineSeconds不会自动回滚
progressDeadlineSeconds: 300发布在期限内没有取得进展时,Deployment Condition 可能标记:
Progressing=False
Reason=ProgressDeadlineExceededKubernetes Deployment Controller不会因为这个字段自动执行安全回滚。流水线或发布平台需要检测失败、冻结放量、保留证据并根据策略回滚或前滚。
查看:
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。一般终止链路为:
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 之前就开始。若:
terminationGracePeriodSeconds: 30
preStop: sleep 25应用真正收到 SIGTERM 后可能只剩很少时间完成关闭。preStop 应短小、可控,不要用长时间 sleep 掩盖未实现的优雅关闭。
9.2 Java应用要正确处理SIGTERM
容器入口应使用 exec form,让 Java 成为容器主进程并收到信号:
ENTRYPOINT ["java", "-jar", "/app/order-api.jar"]应用关闭过程通常包括:
- 停止接受新请求。
- 等待有界时间处理在途请求。
- 停止消费新消息并提交已完成结果。
- 关闭线程池、数据库连接池和Telemetry导出。
- 在宽限期内退出。
9.3 长连接和异步消费者更复杂
WebSocket、SSE、MQ Consumer、批处理需要定义 Drain 协议。仅从 Service 摘除不能终止已有长连接;仅收到 SIGTERM 也不保证消息没有重复投递。消费者必须实现幂等和正确的 Offset/ACK 边界。
9.4 强制删除的风险
kubectl delete pod <pod> --grace-period=0 --force这会绕过正常等待,不保证外部系统中的写入、锁、连接和消息状态安全。只有明确评估风险且普通终止失效时才使用,并记录事故原因。
十、发布暂停、继续和回滚
10.1 暂停发布
kubectl rollout pause deployment/order-api -n commerce暂停后可积累对 Pod Template 的修改,Deployment 不继续触发新 Rollout;具体行为应以目标版本验证。暂停不等于停止现有业务,也不撤销已经创建的新 Pod。
继续:
kubectl rollout resume deployment/order-api -n commerce10.2 查看历史
kubectl rollout history deployment/order-api -n commerce
kubectl rollout history deployment/order-api -n commerce --revision=3为变更记录原因:
kubectl annotate deployment/order-api -n commerce \
kubernetes.io/change-cause='release git=8f03a1c image=sha256:... ticket=REL-1024' \
--overwrite现代发布系统更应在外部保存完整审计,不应只依赖一个可修改 Annotation。
10.3 回滚
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策略
strategy:
type: Recreate模板更新时,Deployment 通常先缩掉旧副本,再创建新副本,适合无法同时运行新旧版本的特殊应用,但会带来停机窗口。
它不等于任何情况下集群里绝不会同时出现新旧 Pod:人工创建、删除过程、旧 Pod 长时间 Terminating 等边界仍需观察。商业无状态API优先设计为新旧兼容并使用 RollingUpdate。
十二、扩缩容与HPA冲突边界
手工扩容:
kubectl scale deployment/order-api -n commerce --replicas=6HPA 管理同一 Deployment 时,会持续修改 spec.replicas。如果 GitOps 清单又固定 replicas: 3 并频繁同步,两个控制方可能来回覆盖。
应明确字段所有者:
- HPA 管副本数时,GitOps模板避免持续强制固定运行值,或使用平台约定。
- 发布系统只修改镜像/模板,不顺手覆盖HPA结果。
- 扩容后验证下游数据库、MQ、外部接口是否能承载更多并发。
扩容 Pod 不能解决所有瓶颈。数据库连接上限只有100时,从10个Pod扩到50个,每个Pod仍配置20连接,反而可能压垮数据库。
十三、PodDisruptionBudget的真实作用
示例:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: order-api
namespace: commerce
spec:
minAvailable: 2
selector:
matchLabels:
app: order-apiPDB 主要限制通过 Eviction API 发起的自愿中断,例如节点 Drain。它不能阻止:
- Node突然宕机。
- 容器崩溃。
- OOMKilled。
- 直接删除Pod或Workload的所有路径。
- Deployment滚动更新本身;滚动可用性由Deployment策略控制。
PDB太严格可能让节点维护无法 Drain。replicas: 1 且 minAvailable: 1 时,没有额外副本就不能正常驱逐。高可用需要副本、拓扑分散、容量和PDB共同设计。
十四、Deployment发布故障Runbook
14.1 先看总体进度
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.generation与status.observedGeneration。- replicas、updatedReplicas、readyReplicas、availableReplicas、unavailableReplicas。
- Progressing、Available Condition及Reason。
14.2 找新旧ReplicaSet
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
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 |
| ContainerCreating | Runtime、CNI、CSI、ConfigMap/Secret |
| ImagePullBackOff | Digest、Registry、Secret、DNS/TLS |
| CrashLoopBackOff | previous日志、退出码、配置、OOM |
| Running但NotReady | Readiness路径、依赖、端口、超时 |
| Ready后又崩溃 | minReadySeconds、启动后负载、Probe、资源 |
14.4 为什么滚动更新卡住后旧Pod不减少
当 maxUnavailable: 0 时,新 Pod 未 Available,控制器不能继续缩减旧可用副本。这是保护可用性的正常行为,不是控制器“不会更新”。根因通常在新 Pod 的调度、启动或 Readiness。
十五、StatefulSet解决的到底是什么
StatefulSet 给每个副本稳定的序号身份,并可为每个序号绑定独立 PVC。典型名称:
mysql-0
mysql-1
mysql-2这些 Pod 被重建后名称/Ordinal可以保持,配套 PVC 仍能关联同一序号。但 Pod UID、容器进程和 Pod IP 仍会变化。
适合:
- 集群成员需要稳定名称。
- 每个成员有独立持久卷。
- 启动/终止顺序对系统有意义。
- 应用自身理解复制、选主、分片和恢复。
不适合因为“服务用了数据库”就把业务API改成 StatefulSet。业务API访问外部数据库,自己仍通常是无状态 Deployment。
十六、Headless Service和稳定DNS
StatefulSet常配合 Headless Service:
apiVersion: v1
kind: Service
metadata:
name: mysql
namespace: data
spec:
clusterIP: None
selector:
app: mysql
ports:
- name: mysql
port: 3306稳定 DNS 形式通常类似:
mysql-0.mysql.data.svc.cluster.local
mysql-1.mysql.data.svc.cluster.local具体DNS可用性还受 Pod Ready、publishNotReadyAddresses、CoreDNS和集群域配置影响。稳定DNS不代表该成员一定健康,也不自动提供主从路由。
十七、StatefulSet完整结构
下面只展示身份与存储结构,不等于一套可直接投产的 MySQL 高可用方案。
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 常见行为:
创建:mysql-0 Ready → mysql-1 Ready → mysql-2 Ready
缩容/删除:通常从最高Ordinal反向处理如果 mysql-0 永远不 Ready,后续序号可能无法继续创建。这不是控制器坏了,而是有序策略在保护成员初始化顺序。
podManagementPolicy: Parallel 允许更并行地创建/终止,适合不依赖顺序的应用;不能只为“发布更快”给有严格成员顺序的系统改成 Parallel。
十九、volumeClaimTemplates和PVC生命周期
每个 Pod Ordinal通常得到独立 PVC,例如:
data-mysql-0
data-mysql-1
data-mysql-2Pod 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 可做分区更新:
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故障排查
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 Pending | PVC未绑定、拓扑、资源或调度约束 |
| Pod重建后数据为空 | 挂错PVC、应用数据目录不一致、初始化覆盖 |
| 更新停在某Ordinal | 新成员Readiness/复制/启动失败 |
| 删除STS后PVC还在 | 默认数据保护行为,不是泄漏结论 |
| DNS无法解析成员 | Headless Service、Selector、Ready、CoreDNS |
不要为了让更新继续而直接把数据库 Readiness 改成固定成功,否则可能把未同步成员交给客户端。
二十三、DaemonSet如何保证节点覆盖
DaemonSet Controller 根据 Node、NodeSelector/Affinity、Taint/Toleration 等条件,为每个符合条件的 Node 创建一个 Pod。
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 示例:
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: Directory24.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排查
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记录成功;失败时根据策略创建或重启执行尝试。
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关键字段怎么配合
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 |
backoffLimit | Job允许的失败重试边界,具体计数受策略/版本影响 |
activeDeadlineSeconds | 整个Job允许活跃的总时间,不是单个HTTP超时 |
ttlSecondsAfterFinished | 完成后多久由TTL机制清理Job及下级对象 |
restartPolicy | Job Pod通常使用Never或OnFailure |
27.1 completions和parallelism不是一回事
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(能力和字段以目标版本为准):
spec:
completionMode: Indexed
completions: 20
parallelism: 4每个Pod可获得完成索引,用来处理对应分片。应用必须定义:
- index怎样映射数据范围。
- 数据增长期间边界是否稳定。
- 单分片失败如何重试。
- 是否允许不同分片并发写同一资源。
- 汇总何时认为完整。
不要用 id % 20 直接处理不断变化且需要一致快照的数据,除非明确接受扫描期间新增/修改的语义。
三十、Job失败策略和排查
较新Kubernetes支持更精细的 Pod Failure Policy 等能力,可按退出码或Pod Condition决定忽略、计数或直接失败;具体字段成熟度必须按目标版本验证。
基础排查:
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。它通常按控制循环检查,不承诺毫秒级准时,也不承诺严格只创建一次。控制面不可用、启动截止时间、并发策略和时钟都会影响实际创建。
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
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: 4Gispec.timeZone 需要目标 Kubernetes 版本支持;老集群不能把不支持的字段当成已生效。示例应用还必须从受审计的调度记录/运行参数确定“本次应该处理的业务日期”,不能简单使用 Pod 实际启动时刻,否则延迟补跑可能处理错日期。实际平台可在创建 Job 时记录计划触发时间,并将其作为稳定 runId 的一部分。
三十三、Cron表达式和时区
0 2 * * *
│ │ │ │ │
│ │ │ │ └─ 星期
│ │ │ └─── 月
│ │ └───── 日
│ └─────── 小时
└───────── 分钟它表示每天 02:00。必须明确时区、夏令时和业务日期。金融对账“自然日”可能按业务地区时区,而集群节点/控制面可能使用另一时区。
时区发生夏令时跳变时,某个本地时间可能不存在或出现两次。关键业务应定义遇到时区跳变的处理规则,并以幂等业务日期为准。
三十四、concurrencyPolicy三种策略
| 策略 | 新触发到来且上一次未完成 | 风险 |
|---|---|---|
| Allow | 允许并行创建新Job | 两批可能竞争同一数据 |
| Forbid | 跳过本次并发执行 | 长任务可能导致后续计划被错过 |
| Replace | 尝试替换当前Job | 旧任务外部副作用不会自动撤销 |
Forbid 只阻止同一个 CronJob 创建的 Job 重叠,不是跨集群全局锁。应用若部署在灾备双集群,两个 CronJob仍可能同时执行。
Replace 终止旧 Job 对象不等于数据库事务、外部支付或已发送消息被回滚,仍需幂等和补偿。
三十五、startingDeadlineSeconds和错过调度
控制面故障或 CronJob Suspend 后恢复,Controller会判断错过的计划是否还在允许启动的截止范围。
startingDeadlineSeconds: 900表示错过计划后超过15分钟通常不再启动该次任务。设得太短可能因为短暂控制面抖动漏任务;无限补跑又可能在恢复后同时制造大量历史任务。要结合业务是否允许补跑、数据窗口和容量设计。
CronJob Controller按控制循环检查计划,并不是每秒都保证触发。某些版本中,如果 startingDeadlineSeconds 小于约10秒,可能因为小于控制器检查周期而无法按预期调度;具体周期和行为要以目标版本为准。关键任务不要设置脱离控制器时间粒度的极短截止时间。
CronJob名称还会被用于派生Job名称,控制器需要追加时间相关后缀。CronJob自身名称不能把DNS名称最大长度全部用满,常见限制是最多52个字符;提交前应通过目标集群服务端Dry Run验证。
三十六、Suspend不是取消已运行Job
kubectl patch cronjob daily-reconciliation -n finance \
--type=merge -p '{"spec":{"suspend":true}}'Suspend 阻止后续计划创建新 Job,不会自动终止已经运行的 Job。恢复时是否补算错过时间受 Deadline 和控制器语义影响,操作前应查看活动 Job 和业务结果。
三十七、CronJob生产排查
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。
- 重试与永久错误分类。
- 并发和分片。
- 部分成功后的恢复。
- 结果和日志长期保存。
- 清理策略。
四十、常见误区
- Deployment会原地修改Pod镜像:实际是创建新ReplicaSet和新Pod。
apply成功就是发布成功:只证明对象被API接受。progressDeadlineSeconds会自动安全回滚:它主要标记进度失败。rollout undo能撤销数据库和消息:它主要回退Pod Template。- preStop不占终止时间:宽限期在Hook前已经开始。
- PDB能防止所有Pod减少:它主要约束自愿Eviction。
- StatefulSet自动把三个MySQL组成集群:它只提供身份/存储编排原语。
- 删除StatefulSet一定删除磁盘:PVC生命周期需单独确认。
- DaemonSet绝对每个Node都有Pod:节点条件和失败可能导致缺失。
- Job失败重试不会重复副作用:必须业务幂等。
- CronJob严格恰好一次且准点:控制循环和分布式故障不保证。
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
- 创建三副本Deployment。
- 记录Deployment Revision、ReplicaSet和Pod Template Hash。
- 更新镜像Digest。
- 持续观察新RS扩容、旧RS缩容。
- 只修改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仍不能撤销外部副作用。
验收清单
- 能从Deployment找到新旧ReplicaSet、Revision和Template Hash。
- 能手算整数和百分比Surge/Unavailable边界。
- 能解释Readiness、minReadySeconds和ProgressDeadline关系。
- 能完整画出Pod优雅终止链路。
- 能列出rollout undo无法撤销的对象和副作用。
- 能解释StatefulSet稳定与不稳定的身份字段。
- 能解释Headless Service、Ordinal和PVC关系。
- 能审计DaemonSet缺失节点和Host权限风险。
- 能解释Job completions、parallelism、Backoff和Deadline。
- 能为批任务设计业务幂等和部分成功恢复。
- 能解释CronJob时区、错过计划和三种并发策略。
- 能根据Condition、Event、日志和指标完成Workload排障。
