1 K8s 工作负载要回答什么
订单服务已经能在容器里运行,接下来却出现了新的问题:负责接单的进程退出后由谁恢复?准备发布的新版本应该怎样接替旧版本?每天生成的对账文件由谁触发?如果把这些安排留在运维人员的记忆里,容器只是换了包装,交付仍然依赖人工盯守。Kubernetes,简称 K8s,用工作负载对象表达这些运行意图,让控制器持续检查实际状态并推动它接近期望状态。
这里的“工作负载”不是某台服务器上的固定进程,而是应用需要怎样运行的声明。订单 API 希望持续有 3 个副本接受请求,结算脚本希望完成后退出,对账任务希望按时间触发,它们的生命周期不同,需要选择不同的对象。选择之前先回答:进程是否长期运行、能否任意替换、副本是否需要稳定身份、失败后是否允许重做。这些问题比背诵对象名称更重要。
控制器通过反复观察和协调工作,不保证每次修改都立即完成。你声明需要 3 个副本,集群可能因为资源不足暂时只能运行 2 个;声明更新镜像,新容器也可能因配置错误迟迟不能就绪。期望状态是目标,状态字段、事件和指标才描述当前进展。学习 K8s 的关键,是同时阅读目标、实际状态以及阻碍两者收敛的证据。

本课以订单 API 为例:API 使用 Deployment 保持副本,使用 Service 提供访问入口,使用专用 ServiceAccount 表达进程身份,使用外部数据库保存订单,并输出日志与指标。ServiceAccount 的权限还需要权限规则约束,它不会自动成为数据库账号。数据库凭据应通过受控方式提供,不能直接写进镜像。这里先建立职责边界,具体授权、网络转发与存储管理分别在对应主题继续展开。
需要特别区分容器恢复与业务恢复。控制器能补回消失的副本,却无法判断重复提交是否生成了 2 张订单,也无法自动修复错误的数据迁移。应用仍要处理幂等、超时和数据一致性。平台负责按声明维持运行条件,应用负责这些条件之上的业务含义。把这条边界说清楚,才能避免把“已经运行”误当作“已经交付”。
2 Pod、Deployment 与副本控制
Pod 是 K8s 部署和调度容器的基本单位,通常把 1 个主要业务容器放在 1 个 Pod 中。确实需要紧密协作的辅助容器也可以与它同处 1 个 Pod,共享网络环境,并通过声明的卷共享文件。不能因为应用属于同一个项目,就把 API、数据库和批处理脚本全部装进同一个 Pod;它们通常具有不同的伸缩节奏、故障边界和数据生命周期。
Pod 内的容器可以被节点上的组件重启,但 Pod 本身不是永久身份。节点故障或 Pod 被删除后,替代者通常是新 Pod,拥有新的标识和可能不同的地址。因此,调用方不应把某个 Pod 地址写死在配置里,持久数据也不应依赖容器可写层。可以替换的运行实例、稳定的服务入口、独立保存的数据,需要分别设计。
Deployment 适合管理通常可相互替换的长期运行副本。它通过 ReplicaSet 维持指定数量的 Pod,更新模板时再逐步替换旧副本。可以把这条关系理解为:Deployment 管发布版本和推进策略,ReplicaSet 管某个模板对应的副本数量,Pod 承载具体容器。日常修改应落在 Deployment 的声明上,直接改它管理的某个 Pod,往往会在重建时丢失效果。
假设订单 API 的期望副本数为 3。删除其中 1 个 Pod 后,控制器发现实际数量不足,会创建替代副本;它不是把原来的进程搬回来。替代副本仍然要经历调度、拉取镜像、启动和就绪检查。如果剩余实例已经满载,新副本还未就绪,用户仍然可能遇到延迟增加。副本控制提供恢复机制,恢复耗时和剩余容量仍然需要测量。
标签把对象连接起来。Deployment 的选择器决定它管理哪些 Pod,Service 的选择器决定访问入口对应哪些 Pod,两者用途不同。发布时可用稳定的应用标签标识订单 API,并让版本信息作为额外标签存在。若把 Service 的选择器误设成只匹配旧版本,Deployment 即使完成更新,入口也可能找不到可用实例。标签设计属于交付契约,不能随意改名。
滚动更新允许新旧版本在一段时间内同时存在。maxSurge 控制更新期间允许额外创建多少 Pod,maxUnavailable 控制允许有多少期望副本暂时不可用。例如 3 个副本配合额外 1 个、不可用 0 个,可先启动新副本,达到可用条件后再缩减旧副本。它需要额外资源,也要求新旧版本能同时处理请求、兼容当前数据格式。副本数量策略不能替代兼容性设计。
3 Job、CronJob 与有状态负载边界
长期接收请求和执行到完成,是不同的运行目标。Deployment 管理的服务通常应该持续运行;Job 则关注任务是否达到指定的成功完成条件。导出某天订单的脚本执行成功后应退出,若把它放进持续维持运行的工作负载中,退出反而可能触发重复启动。先决定“应该一直活着”还是“做完就结束”,再选择对象,能避免很多看似奇怪的重启问题。
Job 可以在失败后按配置重新尝试,也可以限制重试次数和执行时长,但不能承诺业务动作恰好发生 1 次。脚本可能已经把文件上传成功,却在报告成功前退出;平台再次执行时就会遇到已有结果。对账任务应使用明确的业务日期和结果标识,检测已经完成的处理,或以安全方式覆盖同一份结果。重试是运行能力,幂等是业务设计,两者需要配合。
CronJob 根据计划创建 Job,适合定时对账、清理和生成报表。计划时间不等于严格的实时执行承诺,控制器暂停、资源不足或任务过长都可能使执行延后。需要明确时区、错过执行时允许补跑的范围,以及上次任务未结束时的处理方式。并发策略可以允许重叠、跳过新的执行,或替换尚未结束的执行;选择依据应是任务语义,而不是默认值是否方便。
例如每日生成订单汇总,允许 2 个任务同时写相同目标文件可能互相覆盖。选择禁止同一 CronJob 的任务重叠能降低冲突,却不能替代数据库里的防重复措施,因为人工补跑或其他任务仍可能处理同一批数据。更稳妥的做法是让每次执行记录业务日期、任务标识和完成状态,异常后能判断应该重试、跳过还是人工确认。
有状态负载关心的不只是副本数量,还可能关心稳定名称、卷的绑定以及启动顺序。StatefulSet 为这类需求提供相应机制,但不会自动把数据库变成具备高可用能力的系统。副本之间如何复制、谁能写入、故障时怎样恢复,仍取决于数据库自身协议和运维方案。持久卷保留数据也不等于已经有备份,错误删除、逻辑损坏和跨区域恢复仍需独立安排。
入门交付可以让无状态订单 API 使用 Deployment,把数据库作为独立的数据服务管理;确实需要在集群内运行有状态组件时,再评估 StatefulSet、存储能力、备份恢复与维护责任。判断无状态的标准也不是“有没有访问数据库”,而是某个 API 副本消失后,另一个副本能否接替它处理请求,而不需要取回前者独占的本地状态。会话、上传中间文件和本地队列都需要纳入这个判断。
4 动手:追踪一次滚动更新的就绪条件
下面在已有测试集群和 training 命名空间中观察更新。示例假定你已具备该命名空间的操作权限,并有可用的订单 API 镜像;镜像地址用占位符表示,需要替换成实验环境中的实际地址。服务演示 /startup、/ready、/live 这 3 个接口,实际应用必须提供对应行为。不要把不存在的探针接口原样用于自己的应用,否则实验只会展示检查失败。
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-api
namespace: training
spec:
replicas: 3
minReadySeconds: 10
progressDeadlineSeconds: 300
selector:
matchLabels:
app: order-api
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: order-api
spec:
containers:
- name: api
image: registry.example/order-api:v1
ports:
- containerPort: 8080
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "1"
memory: 512Mi
startupProbe:
httpGet:
path: /startup
port: 8080
periodSeconds: 5
failureThreshold: 24
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet:
path: /live
port: 8080
periodSeconds: 10
failureThreshold: 3
把清单保存为 order-api.yaml 后执行以下命令。观察时把 Pod、ReplicaSet 和 Deployment 放在同一条时间线上,不要只盯着最后的成功提示。
kubectl apply -f order-api.yaml
kubectl -n training get deployment,replicaset,pods -l app=order-api
kubectl -n training rollout status deployment/order-api --timeout=180s
kubectl -n training set image deployment/order-api api=registry.example/order-api:v2
kubectl -n training get pods -l app=order-api -w

Running 表示 Pod 已进入运行阶段,不能据此认定请求可被正确处理。配置 startupProbe 后,启动检查成功之前不会执行该容器的就绪与存活检查;启动检查持续失败也会触发容器重启。readinessProbe 判断它此刻是否适合接流量,失败通常使其退出正常服务端点,不会直接重启容器。livenessProbe 判断是否需要通过重启恢复容器。把这 3 个目的分开,才能解释每种失败的实际影响。
新 Pod 就绪后,还要连续保持就绪达到 minReadySeconds,Deployment 才把它计为可用副本。这个等待可以过滤短暂的就绪抖动,但不代表平台已经完成业务验收。progressDeadlineSeconds 用来报告发布长期没有取得进展,超过期限不会自动修复应用或自动回滚。客户端 --timeout 只是本次等待命令的截止时间,命令结束也不意味着后台更新停止。
可以让 v2 的就绪接口在测试中持续返回失败,观察新 Pod 运行但不就绪、旧副本仍然保留的状态。此时查看新 Pod 的事件和日志,再恢复正确镜像。kubectl describe pod <新Pod名> 能帮助定位探针失败、拉取失败或调度等待,kubectl logs <新Pod名> -c api 用于检查应用解释。命令中的名称应来自前面的实际输出,不要把占位符直接执行。
必要时可以执行 kubectl -n training rollout undo deployment/order-api 发起回滚,但必须先确认历史版本仍兼容当前配置和数据库结构。回滚恢复的是先前的 Pod 模板,不会撤销已写入的数据,也不能保证找回被覆盖的外部配置。实验的成果应是能解释“卡在什么条件、证据是什么、怎样安全恢复”,而不只是得到 1 行更新成功。
5 资源请求、限制与 QoS 取舍
资源请求 requests 主要帮助调度器判断节点是否有足够的可分配资源容纳 Pod,也参与资源竞争时的分配判断。资源限制 limits 则约束容器可以使用的资源。CPU 的 250m 表示 0.25 个 CPU 的请求量,内存的 256Mi 表示 256 MiB。调度通常依据请求做放置判断,不能把“当前监控显示很空闲”当作新 Pod 一定能被调度的依据。
CPU 和内存的超限后果不同。CPU 达到限制时通常表现为受到节流,应用可能仍在运行却响应变慢;内存达到限制可能触发 OOM 终止,容器随后是否重启取决于运行策略。看到请求变慢时只增加探针容忍度,可能掩盖 CPU 限制过紧;看到重启就增加副本,也可能让相同的内存问题扩散到更多实例。必须先把资源指标与事件、退出原因放在一起看。
请求设得过高,会让可调度副本变少,并增加容量成本;设得过低,调度器可能把过多负载放到同一节点,使高峰时争用加剧。限制也不是越紧越好,它应允许合理的启动与短时波动。订单 API 可以从稳定负载、流量高峰和冷启动这 3 类测量开始,记录 CPU、内存、延迟和错误率,再决定请求与限制,而不是从其他服务复制 1 组数值。
滑块把重点放在请求量与发布容量的关系上:假设 1 个节点可分配给本实验的预算为 4 个 CPU、4 GiB 内存,且没有其他约束,改变每个 Pod 的请求后,理论容纳量由 CPU 和内存中更紧的条件决定。这只是资源装箱的教学模型,真实调度还会受到其他 Pod、节点数量、亲和性和存储等条件影响。即使平时刚好装下 3 个副本,也可能没有位置容纳更新时额外的 1 个。
QoS 是 K8s 根据资源声明形成的服务质量分类,包括 Guaranteed、Burstable 和 BestEffort。按本课使用的容器资源声明方式,所有容器的 CPU、内存请求与限制都齐全且分别相等时,可形成 Guaranteed;存在请求或限制但不符合该条件时通常为 Burstable;完全未声明这些资源时为 BestEffort。辅助容器也会影响分类,不能只看主要业务容器。
QoS 可以帮助理解节点压力下的资源处理,但不是业务优先级,也不承诺某类 Pod 永远不会被驱逐。节点压力、优先级和实际使用等因素都会影响结果。入门阶段应先保证资源声明有测量依据、关键服务留有故障和更新余量,再进一步研究驱逐细节。单纯为了获得某个分类把请求抬到峰值,可能牺牲大量可调度容量,却没有解决应用本身的内存增长。
6 常见坑:把 Ready 当业务可用
Ready 是平台依据已配置条件得出的状态,不是订单成功率的同义词。若 /ready 永远返回成功,即使数据库凭据失效,Pod 仍可能保持就绪;若接口检查了太多非关键依赖,某个报表系统变慢又可能让全部 API 副本退出服务。探针的价值取决于它检查的内容。设计前需要明确:哪些能力缺失意味着这个实例此刻确实不能承担正常流量。
也不要把所有下游依赖放进存活检查。数据库短暂抖动时,重启 API 容器通常无法修复数据库,反而会丢弃连接、触发冷启动并产生更多重连。存活检查应主要识别需要重启才能恢复的本地失效,例如处理循环持续卡死;就绪检查可以表达临时接流量能力,但仍需避免把共享依赖故障放大成全部实例同时退出。具体边界要根据是否能降级、是否能服务其他请求来确定。

观测至少连接 3 类证据:平台上看期望副本、可用副本、待调度和重启;实例上看请求延迟、错误、CPU、内存及探针结果;业务上看创建订单是否成功、重复订单是否增加、处理是否积压。日志里保留发布版本与请求标识,才能把某次错误关联到新旧版本。单个绿色状态不足以覆盖这些问题,业务验证也不应只发送 1 个总会成功的健康请求。
还有 1 个容易忽视的时间差:实例被移出正常服务端点后,访问路径中的各处状态传播和已有连接结束都需要时间。进程收到终止信号时,应停止接收新工作,并在终止宽限期内完成或安全中止已有请求。若容器立刻退出,正在写订单的请求可能失败;若无限等待,发布又可能迟迟不能结束。需要用真实请求测试优雅退出,而不是仅观察 Pod 最终消失。
因此,发布验收可以同时要求平台更新完成、业务成功率稳定、延迟没有明显退化,并观察覆盖启动与常见请求的时间窗口。阈值应来自服务目标和历史测量。某次更新后可用副本数正确,但下单错误率上升,仍应判定发布没有达到预期。这个结论不否定 Ready 的作用,而是把它放回“实例可以接收流量”的有限职责内。
7 案例:探针过严导致雪崩重启
某次活动开始后,订单 API 的数据库访问从平时几十毫秒升到几百毫秒。团队此前把 /live 实现成“完成数据库查询才返回成功”,并将超时设为 1 秒、检查周期设为 5 秒、连续失败阈值设为 3。共享依赖稍有持续抖动,多个副本便在相近时间达到失败阈值。容器重启没有修复数据库,却让原本还能处理部分请求的实例一起进入启动阶段。
新启动的实例需要加载配置、建立连接和预热,短期内可用容量进一步下降。剩余实例承担更多请求,数据库收到更多重连和重复尝试,探针又更容易超时。这个循环的危险不在于某次失败,而在于恢复动作也增加了系统压力。如果客户端和应用还都没有限制地重试,同一笔业务就可能产生更多请求,使局部延迟演变成持续故障。

排查从时间关联开始:检查错误率抬升是否先于重启,重启是否集中在同一版本,事件是否记录存活检查失败。对已重启容器,可以用 kubectl -n training logs <Pod名> -c api --previous 查看前次容器日志,再结合退出原因判断。若事件是 OOMKilled,就应转向内存证据;若 Pod 还在 Pending,则先看调度事件。不能只凭“重启次数很多”就断言都是探针问题。
止损要切断已经证实的放大环节。这个案例中,可先暂停继续推广有问题的配置,在确认容量和依赖承受能力后,发布改正后的探针:存活只检查本地运行能力,启动阶段交给启动探针,临时接流量能力由就绪检查表达。同时限制重试总量,为下游调用设置合理超时;需要降级时让应用明确返回可解释的结果。扩容是否有效,也要看数据库是否还有余量。
恢复后,用受控的依赖延迟实验验证:依赖变慢时不再出现成片重启,请求能在设定超时内结束,依赖恢复后实例可重新承担流量。再测试本地进程确实失效的情形,确认存活检查仍能触发恢复。只把阈值调大而不检查语义,可能让真正卡死的容器长期占位。案例的经验是让每种自动动作针对它能够修复的故障,并用观测证明动作有帮助。
8 把工作负载拓扑写进交付方案
交付方案应让接手者看见“哪些对象共同提供服务”。对订单系统,可以画出入口到 Service、再到 Deployment 管理的 API Pod 的路径,标注 API 访问数据库和身份凭据的位置,并把 CronJob 创建 Job 的对账路径放在旁边。拓扑不仅是对象清单,还应写明哪些组件共享依赖、哪些可以独立更新,以及故障时请求会流向哪里。
工作负载部分至少说明对象选择依据、期望副本数、镜像版本标识、资源预算、更新策略、探针语义和退出行为。配置中的数字要能对应证据,例如 3 个副本来自容量与故障测试,启动检查容忍范围来自冷启动测量。不可变的镜像摘要有助于定位实际运行内容;只记录容易被覆盖的标签,会增加复现问题的难度。环境差异也应通过受控配置表达。
数据与任务部分要标出持久状态的归属、任务重复执行的处理方式、备份恢复责任和数据库变更顺序。身份部分说明每类工作负载使用哪个 ServiceAccount、需要哪些权限,以及如何获取外部依赖的凭据。观测部分则把发布版本、平台状态和业务指标关联起来。这样排障人员看到某个失败任务时,能够找到负责人、输入范围和已经产生的结果。
最后写清发布与恢复的操作边界:谁观察进度、什么信号触发停止推广、允许回到哪个版本、哪些数据变化不能回滚。检查容量时应计入滚动更新的额外副本,验证故障时也要考虑替代 Pod 的启动时间。如果这些约束没有写入方案,即使每个对象的 YAML 都能通过提交,也很难证明整套服务已经具备可重复交付的条件。
9 从K8s 工作负载走向K8s 网络(C30)
到这里,我们已经能解释运行实例由谁维持、更新如何推进、任务为什么需要不同对象。但副本能恢复并不代表访问路径自然正确。Pod 更换后地址可能变化,Service 怎样选择后端,集群外请求怎样进入,名称怎样解析,哪些访问应该被阻止,这些都是下一课要回答的网络问题。工作负载提供可被访问的实例,网络把调用者与这些实例连接起来。
进入 C30 前,保留本课建立的几条边界:标签是服务发现的重要输入,Ready 会影响普通 Service 的就绪后端选择,容器声明端口不会自动创建外部入口,副本增加也不会自动解除数据库瓶颈。下一课会沿着 1 次请求从调用者到订单 API 的路径,区分名称、虚拟入口、具体实例与访问规则。带着路径去理解网络对象,比单独记忆对象列表更容易定位故障。
例如更新后 3 个 API Pod 都已就绪,入口却持续报错,可以先核对 Service 选择器与新 Pod 标签,再查看对应 EndpointSlice 中的地址、端口与就绪条件。若选择器匹配不到副本,应修正对象间的关联;若后端存在,但目标端口仍指向旧版监听端口,就要核对应用实际监听位置。探针可能通过另一个管理端口检查成功,因此 Pod 就绪与业务端口可访问仍需分别验证。保留更新前后的 Pod 名称、标签和后端记录,能判断问题来自副本替换还是访问配置。
可以先自查:新版本处于 Running 但未 Ready,应先检查什么?CronJob 重试后出现重复文件,应修改哪层逻辑?更新需要额外 1 个 Pod 却一直 Pending,应检查哪类容量?如果能分别指向探针及应用证据、任务幂等、调度资源与事件,就已经掌握本课的基本方法。还需要保留最后的提醒:平台的运行状态、网络的可达性、业务的正确性,必须通过各自的证据连接起来。
放进方案里
订单 API 采用 Deployment,常态运行 3 个可替换副本,通过 Service 提供稳定访问入口。订单数据保存到独立数据库,Pod 本地文件仅用于可丢失的临时内容。每日对账使用 CronJob 创建 Job,按业务日期标识结果,重复执行前检查完成记录,避免重试生成重复产物。若以后引入需要稳定身份的组件,应单独评估 StatefulSet、存储恢复能力和维护责任。
发布采用滚动更新,额外副本上限为 1,允许不可用副本数为 0,并为新增实例预留资源。每个 API 容器初始请求为 250m CPU、256Mi 内存,限制为 1 个 CPU、512Mi 内存;这些数值仅作为测试起点,依据冷启动、常态和高峰测量调整。探针分别表达启动完成、当前接流量能力和本地失效,存活检查不依赖数据库查询;就绪连续保持 10 秒后才计为可用。
工作负载使用专用 ServiceAccount,权限限定在实际需要的范围,数据库凭据通过受控配置提供。观测同时记录可用副本、调度等待、探针失败、容器重启、请求延迟和下单成功率,并关联镜像版本。发布验收除了检查更新完成,还要观察业务请求和优雅退出;错误率持续偏离目标时停止推广,确认兼容性后发起回滚。
交付前在测试环境验证副本丢失、启动变慢和数据库延迟,保留事件、日志及业务指标作为证据。恢复手册明确负责人、停止条件、可用历史版本和不可撤销的数据变化。数据库备份、恢复演练与任务幂等分别安排责任人,不以 Pod 重建替代数据恢复。下一步补齐访问路径、名称解析和访问控制,使运行拓扑与网络拓扑可以相互核对。
验收时主动删除 1 个 API Pod,记录替代副本达到可用的耗时,并核对期间的下单成功率是否满足约定目标;同一业务日期的对账任务执行 2 次后,只能存在 1 份有效结果,附完成记录与产物校验值。
10 小结:下一步K8s 网络(C30)
Pod 承载实例,Deployment 维持长期服务副本,Job 与 CronJob 表达完成任务和定时执行,有状态组件还需要身份与数据管理。探针、资源声明和滚动策略共同决定恢复过程,但业务成功必须另有证据。下一课沿请求路径理解 K8s 网络,把“实例已经就绪”连接到“调用确实到达正确后端”。