文章标题旁是标有 OBSERVE、DECIDE、ACT 的三个通用面板,以及箭头和圆形仪表。

Kubernetes Deployment 描述期望状态

与其描述步骤,不如描述终态。这条思想支撑起副本、滚动更新与回滚的一切机制。 声明规范胜过过程脚本 一份 Deployment 是「终态」的陈述,而不是一张命令清单:多少副本、哪个镜像、哪条 tag、什么标签。操作者写下「要什么」,控制器这位 reconcile 循环决定「怎么要」。区别在于资源所有权,而不是有没有使用命令行。kubectl run 通常创建独立 Pod,kubelet 可以重启其中容器,但这个 Pod 消失后,没有 ReplicaSet 负责补足一组替代对象。Deployment 同样可以通过一条命令创建,客户端退出之后,存储下来的规范仍会持续指导控制器。中断客户端并不能可靠说明 API 是否已经接纳请求;重试或诊断收敛之前,先查看实际存储的对象,区分请求传递状态与控制器执行状态。 由控制器填补差距 Deployment 控制器管理 ReplicaSet,再由副本集控制器维持 Pod 数量。容器崩溃时,kubelet 可以在同一个 Pod 内重启容器;删除或丢失 Pod 后创建替代对象,则是另一条协调路径。它们会配合工作,但不能都说成同一个控制器每隔几秒创建一个 Pod,否则无法区分实例身份、重启与替换。 协调是异步过程,依赖控制面正常以及容量足够。镜像拉取失败、配额、准入拒绝和存储不可用,都可能长期阻止收敛。应查看条件与事件,确认究竟哪一层受阻。期望副本数是系统追求的目标,不是真实就绪容量的证据,也不是恢复时间上限;控制器反复尝试不等于问题已经得到解决。 一次次发布都是经过步骤的 修改 Pod 模板会触发发布与修订版本,单纯缩放不会。控制器按照更新策略、就绪和可用性要求调整新旧 ReplicaSet 数量。流量由 Service 与对应数据路径另行处理,Deployment 控制器本身不是逐条迁移连接的代理。发布状态和请求成功需要分别观察,不能把模板变化自动等同于无中断流量切换。 回滚恢复先前 Pod 模板,不会恢复 Deployment 所有字段,更不会撤销数据库模式等外部状态。保留的 ReplicaSet 提供回滚历史,revisionHistoryLimit 默认十份,清理从发布完成之后开始。流水线重复应用未变化的模板,并不会只因执行了一次就生成新修订;版本库审计与集群修订解决的是不同范围的历史问题。 ...

八月 19, 2027 · 2 分钟 · XB Field Notes
文章标题旁是标有 OBSERVE、DECIDE、ACT 的三个通用面板,以及箭头和圆形仪表。

PVC 为 Pod 提供容器之外的持久存储

容器本性易失,所以要用 PersistentVolumeClaim 把真实存储钉到它身上,即使 Pod 被重建也不丢数据。这个拆分是刻意的:PersistentVolume 声明已经存在的容量,PersistentVolumeClaim 请求需要的容量,由控制器把两者配上。有了 StorageClass,声明还能通过 provisioner 自己创建卷,于是没人需要预先造好存储。 真正会咬人的是:声明与卷各自有独立的生命周期,由各自的规则管着。finalizer 决定声明什么时候才允许消失,回收策略决定卷之后会怎样。删除顺序搞错,就会得到一个卡在 Terminating 的声明;策略搞错,可能删掉本想保留的数据,也可能留下一份没人在用却仍在计费的存储。 声明、卷与绑定 绑定是一次「全部满足」而不是「最接近」。一块卷只有在容量不小于请求、访问模式包含所请求的模式、storageClassName 匹配,并且没有被别的声明占用时,才是候选。除了这几项,volumeMode(Filesystem 与 Block)也必须一致;而已经释放却还没清理的卷并不会变回可用的备件,它的 claimRef 仍指向上一个声明,所以新的声明根本看不到它。什么都不满足的声明会停在 Pending,并带一条指明究竟哪一项没满足的事件,这比一句笼统的失败有用得多;常见原因是要的访问模式该类交付不了,以及类名根本不存在。 accessModes 没有默认值,必须选一个,而多数单节点块存储 provisioner 实际能交付的就是 ReadWriteOnce。它同时是单节点的,所以副本分布在不同节点上的 StatefulSet 无法共用一份声明。类支持动态供应时,volumeBindingMode 决定时机:Immediate 在声明一出现就创建卷,可能把受拓扑限制的卷放到没有节点能用的可用区;WaitForFirstConsumer 则让声明一直 Pending,直到出现消费 Pod,再结合调度约束协调绑定或供应,然后才能完成放置;它不是先完成调度再随意创建卷。 删除顺序与 finalizer 顺序是 Pod、声明、卷,保护机制由 finalizer 实现。在 Pod 仍挂载着声明时删除声明,什么都不会被删掉:声明拿到删除时间戳、翻成 Terminating,然后停在那里,因为 kubernetes.io/pvc-protection 仍在它的 finalizer 列表里。只有再没有任何 Pod 引用这份声明时,控制器才会摘掉那个 finalizer,所以那个「永远 Terminating」的 PVC 可能仍被 Pod 引用,但控制器延迟和其他 finalizer 也需要调查,不能只看状态就排除控制面问题。 ...

八月 13, 2027 · 1 分钟 · XB Field Notes
文章标题旁是标有 OBSERVE、DECIDE、ACT 的三个通用面板,以及箭头和圆形仪表。

HPA 按负载缩放 Deployment 的副本

HorizontalPodAutoscaler 是反馈控制器,不是容量规划器,默认同步间隔为十五秒。根据配置,它读取资源、自定义或外部指标,再更新工作负载的 scale 子资源。基本计算为 desiredReplicas = ceil(currentReplicas * currentMetricValue / desiredMetricValue),之后还要考虑缺失数据、就绪状态、容忍度、稳定窗口及上下界,不能只靠这一行公式预测全部实际行为。 一份看起来正确的 HPA 对象,不证明容量必然及时到位。指标可用性、请求、副本上限、调度及应用启动都会影响结果。应分别查看建议副本数与真实就绪容量;提高期望数量不会制造新节点,也不能自动修好受阻发布。判断伸缩是否有效,必须跟踪从指标到应用接流量的整段过程。 比值与死区 CPU 利用率目标把实际 CPU 用量与相关请求比较,得到百分比,目标本身也是百分比。如果每个 Pod 只有一个容器,都请求 200m,目标为百分之六十,那么每 Pod 120m 是便于理解的简化等价值。它不是异构 Pod 或所有指标类型通用的分母;多个容器和不同请求会改变聚合结果。 默认容忍度为一附近的 0.1。应把它理解为控制死区,而不是承诺 132m 永不伸缩、133m 必然动作。指标聚合、整数利用率、就绪状态及实现中的算术都会影响边界。忽略其他修正时,三个副本以 160m 对比 120m 目标,会建议四个副本;舍入、速率策略与最终能否调度仍需分别观察,不能从一个毫核数字直接推出真实扩容时刻。 缺失指标与一票否决 缺失 Pod 指标采用保守处理,假定的贡献会削弱建议扩容或缩容的幅度。CPU 对尚未就绪的 Pod 还有额外处理,涉及可配置的就绪延迟与初始化窗口。这些规则围绕启动采样及就绪变化,不表示所有 Pod 都一律被忽略整整五分钟。诊断时应查看实际采样和状态时间,不能把默认窗口当成一个简单倒计时。 ...

八月 11, 2027 · 1 分钟 · XB Field Notes
文章标题旁是标有 OBSERVE、DECIDE、ACT 的三个通用面板,以及箭头和圆形仪表。

liveness 与 readiness 告诉 Kubernetes Pod 的状态

慢启动不等于进程已死:readiness 负责遮挡流量,liveness 只负责让进程活着。把两者搞混是最常见的健康检查错误。团队把同一个接口同时接进两种探针,于是每个变慢的依赖都变成重启循环——应用需要时间,存活探针偏不给,Pod 恰恰死在本来再忍一会儿就能活的关口。两种探针并存,因为"没就绪"和"已死"要的是相反的动作:一个要时间,一个要重启。 防混的心智模型是医院分诊。就绪探针是分诊护士,决定谁现在能看医生;存活探针是抢救车,只在病人心跳停了才推出来。没人会因为坐在候诊区就给人除颤。记住这个画面,探针配置几乎不用想就写对了。 Readiness 把流量挡在门外 连续 readiness 失败达到阈值,会让 Pod 失去普通 Service 路由资格,但 Pod 仍在运行,好让进程完成热身或自行恢复。端点条件与数据路径在失败、恢复两个方向都异步收敛;已有连接和 Pod 直连需要分别考虑。就绪失败不直接触发探针重启,却可能移除全部服务容量,不能无条件称为安全:缓存可以慢慢热,迁移可以跑完,下游依赖可以回来,全程不丢 Pod 的内存状态,也不付重启的代价。 就绪接口要围绕真正的可服务性设计,而不是进程存在性。检查那些决定这个 Pod 此刻能否正确回答的东西:结构迁移是否应用、必需的缓存是否装满、关键下游是否可达。只检查"服务能响应"的就绪探针,会把注定失败每个请求的 Pod 放进流量——比不检查还糟,因为负载均衡器是信任它的。但也别把所有依赖都塞进去:非关键的推荐服务挂了,就绪失败等于为一件装饰品扔掉主干流量。 Liveness 守护进程本身 liveness 探针连续失败达到阈值会终止容器,是否重启遵循重启策略,这是结束僵死状态的最后一招。它要处理的该是"进程卡死"这类问题,而不是容器只是暂时还没就绪。合格的目标很窄:死锁——此后任何请求都不会再成功;协程或线程泄漏到只有新进程能清;内存状态损坏到任何重试都救不回来。每种情况的共同点是不可逆:不重启就不可能恢复,而不只是恢复得慢。 这个门槛意味着很多服务应该只配极简的存活探针,甚至不配。把存活检查打到外部数据库上,每次数据库抖动都会变成全集群的重启风暴,还顺手毁掉本可以加速恢复的热缓存。优先用廉价的本地检查——主循环定时刷新的看门狗时间戳——不问进程之外的任何东西,只回答"这个进程是不是卡死了"。崩溃之后是否重启,取决于容器重启策略;存活探针则能在普通退出之前识别选定的不可恢复本地故障。 给探针留足余量 把 initialDelaySeconds 设到足够大,别把冷启动误判成崩溃;timeout 也要配合真实工作量。把两类探针分开,让慢的 Pod 由 readiness 护住,而不是被 liveness 误回收。启动慢的容器最好再加一个启动探针:它成功之前,存活与就绪探针都保持关闭,于是需要预热大缓存的版本升级能拿到几分钟而不是几秒,稳态的存活检查强度一点不减。 失败阈值按观测到的行为调,别在服务之间抄整数。检查周期乘以失败次数只是规划估计,不是真实反应时间保证。探针所处周期、超时、调度、终止宽限与重启退避都会改变时间线;就绪端点变化与已有连接也并非在同一瞬间全部完成。量出冷启动的分位耗时、依赖恢复时间、最坏的回收停顿,再定每个数字:比告警快,比噪声慢。 ...

七月 14, 2027 · 1 分钟 · XB Field Notes
文章标题旁是标有 OBSERVE、DECIDE、ACT 的三个通用面板,以及箭头和圆形仪表。

ConfigMap 文件与环境变量有不同生命周期

修改 ConfigMap 首先改变的是 API 对象。运行中的应用是否改变行为,还取决于 Kubernetes 如何交付数据,以及应用如何读取它。环境变量注入、普通卷投射、subPath 挂载和已经打开的文件,分别拥有不同生命周期。排查时应找到真正影响业务的消费者,而不是把所有配置传递方式都看成同一种机制。 区分交付与消费 容器环境中的 ConfigMap 引用会在容器启动时解析。更新对象不会重写已有进程的环境,也不会改写同一容器后续 exec 进程所使用的配置环境。替换 Pod 并引用所需版本,可以建立新的环境快照,但这属于生命周期动作,不是修改 data 字段之后自然发生的结果。配置变更记录与进程替换记录应能够对应。 普通 ConfigMap 卷则不同,kubelet 可以在之后把新数据投射进去。延迟受到同步周期和变更发现策略影响,因此 API 修改成功与文件发生变化不是同时发生的事件。投射更新也不会自动调用应用的重新加载函数。只在启动时解析一次配置的程序,即使文件已经变化,也可能一直使用先前读入的值。 打开文件描述引用的是已经打开的文件,而不是每次都重新解析原路径。路径背后的文件被替换,不会迫使旧描述符自动跟随新文件。因此,关闭并重新打开投射路径的进程可能读取新内容,另一个保留的描述符却仍读取旧文件。即使文件系统视图已经刷新,应用自己的缓存还可能再增加一层滞后。 把投射内容复制到可写位置,又会创建一个独立快照。副本不是指向 ConfigMap 的实时链接。如果启动脚本采用这种复制方式,更新这个副本也需要单独实现,不能期待源文件变化自动传播到另一份普通文件。 坏掉的样子 通过 subPath 挂载的 ConfigMap 不会获得普通方式的自动更新。为了方便只挂一个文件,实际上可能改变了配置更新契约。不能因为某个完整目录投射刷新成功,就承诺所有来自 ConfigMap 的文件都会刷新。两个容器对同一个对象表现不一致时,应查看各自真实 volumeMount 定义,而不是只比较 ConfigMap 名称。 监听配置变化需要明确的应用协议。监听器必须处理所用文件系统和库暴露的替换行为;读到新字节之后,验证与真正采用仍然是另外两步。分别读取多个键也可能与版本切换发生竞争。应用应定义怎样加载一致的一组配置、怎样拒绝无效值,以及怎样报告自己目前真正使用的版本,避免把检测到变化直接当成已经生效。 否则,一次成功编辑可能制造分裂视图:API 说 green,重新打开的投射路径也返回 green,进程却继续按 blue 工作。直接重启全部进程也许能暂时掩盖差异,却没有解释原因。应先比较对象、交付路径和应用状态,再选择符合预期契约的重新加载或替换方式,才能让后续配置变更保持可预测。 ...

七月 1, 2027 · 3 分钟 · XB Field Notes
浅色网格上的标题为‘三类探针,不同动作’,旁侧是观察、判断、行动三个步骤。

探针告诉平台何时出手

探针结果是控制循环使用的证据,不是平台对应用健康的直接理解。Kubernetes 不会从 healthz 这个路径名称推断你希望怎样恢复服务;真正决定下一步的是探针类型、结果、阈值与容器生命周期。把每次失败都描述成“重启 Pod”,会抹掉直接影响可用性的关键区别。 三个问题,对应不同动作 启动探针为初始化提供独立的检测政策。配置后,在它成功之前,存活和就绪探针都不会执行。启动探针成功后,在当前容器生命周期内便不再持续检查;如果容器重新启动,则需要再次通过启动阶段,不能把上一次成功沿用到新生命周期。 存活检查用于识别适合通过重启当前容器恢复的状态。达到失败阈值后,kubelet 终止受到影响的容器,并应用对应的重启政策。这不是保证重建整个 Pod、改变其身份或修复外部数据库,也不意味着容器之间的依赖与在途工作都能自动安全恢复。 就绪失败则让容器继续运行,并持续检查是否恢复。它影响通常按就绪状态选择目标的 Service 流量,但不是防火墙,也不会取消已经通过连接到达的请求。直接访问实例的客户端和现有连接,仍需要自己的生命周期处理,不能把就绪状态误读成网络阻断保证。 阈值统计连续观察结果 failureThreshold 为三,不是从启动以来累计失败三次就动作,而是连续失败达到三次;一次成功会打断这段失败序列。就绪恢复可以要求多次连续成功,而启动和存活探针的 successThreshold 必须为一。阅读结果轨迹之前,应先明确进入失败和恢复成功两个方向的阈值。 超时、初始延迟、执行周期和终止宽限期同样影响实际表现。阈值乘周期可以辅助估算,但不能当成端到端故障切换的精确秒表;调度、探针执行和路由传播都参与其中。容器尚未就绪时,就绪探针还可能比配置周期更频繁地执行,以便更快恢复就绪。 实验选择四次启动失败、三次存活失败、两次就绪失败,并要求连续两次就绪成功才能恢复。这组数字只是为了看清不同计数器的边界,不是生产推荐。真实门槛应结合启动时间、检查成本、错误恢复方式以及误判代价确定,不能照搬实验参数。 检查机制决定你拿到了什么证据 HTTP 检查使用文档定义的 200 至 399 成功范围。即使响应正文写着“内部错误”,返回 200 也不会因为人类读出了坏消息,就自动变成失败探针。应沿实际探测路径检查认证和重定向,而不是默认平台与交互式浏览器具有完全相同的访问结果。 TCP 探针证明的是连接可以打开,不是查询或事务能够完成。exec 检查则需要镜像里确实存在相应命令,并且退出状态有明确含义;频繁创建进程还会消耗节点资源。应该选择能够回答当前问题的低成本机制,而不是认为检查越复杂、调用越多就越可靠。 就绪成功并不是存活执行的前提。没有启动探针隔离时,初始化较长的应用可能还没来得及就绪,就因为存活失败反复被终止。相反,启动容忍窗口过大,也会推迟发现真正无法完成的启动。两边都需要依据实际启动分布和恢复目标权衡。 探针失败与 Unknown 结果不是同一种状态。Kubernetes 文档规定 Unknown 不改变容器状态,之后继续检查。本例只接受成功与失败两个值,故意没有第三种结果,所以无法预测传输错误会如何分类。实际排查应检查 kubelet 事件和部署中的探针处理机制,先确认错误属于哪类,再解释是否应计入连续失败。 坏掉的样子 把外部依赖可用性直接作为存活条件,可能在共享故障期间不断重启健康进程;另一个独立线程上始终绿色的处理器,又可能遗漏业务工作线程的死锁。重启并不天然便宜,它会中断在途工作、重复初始化,也可能进一步增加故障依赖的压力。 ...

六月 18, 2027 · 3 分钟 · XB Field Notes
标题卡配有三个相连面板,标注 OBSERVE、DECIDE 和 ACT。

资源限制约束运行,请求参与放置

Kubernetes 的资源 request 与 limit 属于不同决策。请求参与调度和资源共享,限制则配置运行时及操作系统支持的执行约束。把 limit 当成调度器预留的容量,会同时混淆这两个机制。有效的资源策略需要说明放置需求、可以接受的争用,以及运行中的工作负载到达边界后应该发生什么,不能只在清单上填写一组看起来合理的数字。 CPU 与内存具有不同失败方式 在典型 Linux 环境中,CPU 限制会形成带宽约束,繁忙控制组可能被节流,而进程仍然存活。内存限制依赖内存记账和回收;受约束的分配无法满足时,可能触发控制组 OOM 处理。内核不会像推迟 CPU 使用那样,把所有超限内存申请都温和地放慢。两种资源因此需要不同的容量指标和故障判断方式。 内存执行机制具有反应性,不能描述成恰好达到某个字节就调用应用回调,也不能保证使用量绝对不会短暂超过配置值。应用可能收到分配失败,OOM 处理也可能终止进程。应先观察真实终止原因、容器状态和可用的内存事件,再给故障命名,避免仅因为配置了内存限制,就把所有异常退出都解释成越界。 限制覆盖的不只是应用打算保存的业务数据。运行时开销、其他进程,以及被记账的缓存或其他内存,都可能占用控制组额度。容器限制为六十四 MiB,并不意味着它能在已有开销之外,再安全创建同样大小的数据结构。内存支持的卷也可能贡献内存消耗,表现为文件存储并不会让这部分成本消失。 坏掉的样子 退出码 137 对应常见的 SIGKILL 终止表示,本身不足以证明 OOM。应与容器的 OOMKilled 原因以及相关运行时或内核证据结合。节点压力驱逐又是另一种机制,需要检查 Pod 级状态和驱逐决策,不能把一个容器超过自身控制组预算,与 kubelet 为缓解节点压力而移除 Pod 混成同一个过程。 终止后发生什么,由重启策略继续决定。采用 Always 的泄漏程序可能不断重启,而根因始终存在。采用 Never 时,已终止容器保持终止,Pod 可以进入失败状态。限制只约束一种资源维度,既不会修复应用,也不保证邻居在失败发生前完全没有经历争用。是否影响业务延迟,还需要对应观测,不能由限制字段直接推断。 ...

六月 14, 2027 · 3 分钟 · XB Field Notes
在配置的更新预算内逐步替换 Pod 的 Deployment。

滚动更新分批换上新版本

滚动更新是一种受控替换,不是服务绝不中断的承诺。它的价值在于引入新副本时可以保留部分旧副本;用户能否平稳完成请求,还取决于资源余量、健康检查是否可信,以及应用如何退出。本文讨论已经选择滚动更新之后怎样调节节奏、怎样观察过程;姊妹篇讨论更靠前的问题:两个版本到底能不能同时运行。 浪涌与缺口 十个期望副本,配 maxSurge: 2,更新目标上限是十二,不是十一。maxUnavailable: 1 给控制器的可用性预算是九个。这不是「每一波最多删除一个旧 Pod」:只要已经有足够的新副本可用,控制器就能继续撤掉多个旧副本。正在终止的 Pod 还可能保留一段时间并继续消耗资源,因此十二也不是所有 Pod 对象或进程数量的绝对上限。 百分比必须先换算整数:surge 向上取整,unavailable 向下取整。三个副本使用默认的百分之二十五,实际就是一个 surge、零个 unavailable。这里有一个边界例外:surge 为零、非零的 unavailable 百分比却向下取成零时,控制器会把有效 unavailable 调整为一,让替换能够推进。因此,不能从这个换算得到的零,推断更新一定不会主动减少可用副本。 两个参数都不会替你预留节点资源。选择速度之前,要把资源请求量与可调度容量、命名空间配额、放置约束以及旧 Pod 的退出耗时一起检查。账面上多出的两个位置,并不一定真有节点能承接;新 Pod 全部 Pending 时,调大 surge 也不会凭空生出机器。 健康是换班的门槛 首先确认清单中确实配置了就绪探针。就绪检查应该回答「这个实例现在能否处理预期的流量」。只检查端口监听可能漏掉应用故障;检查必要的后端依赖可能更准确,但共享依赖短暂失败也可能让全部副本一起退出就绪状态。是否应该停止接流量,要根据业务的降级能力决定,而不是机械地把所有依赖都塞进探针,更不能一看到失败就放宽条件。 minReadySeconds 要求新 Pod 持续就绪一段时间,之后才计入 Deployment 的可用副本。它不会把 Service 流量也延迟到这段时间结束。启动慢的容器可以用 startup probe,避免还在初始化就被存活检查重启。这些检查仍不能证明订单处理正确,也不能证明实例能持续承受生产负载。 ...

六月 7, 2027 · 2 分钟 · XB Field Notes