小请求可以在大限制下完成调度,超过节点可分配容量的请求则保持 Pending。

调度器计算资源请求,而不是瞬时使用率

Kubernetes 调度判断的是 Pod 在资源请求和其他放置约束下是否适合某个节点,不是简单查看当前 CPU 曲线,然后把任务塞进看起来最空闲的位置。请求为未来需求提供记账输入,实际使用率则是另一种观测。决定一个表面空闲的集群是否有能力容纳特定 Pod 之前,需要同时理解两者,不能让其中一个指标替代全部容量判断。 以准入后的真实配置为依据 对于普通应用容器,调度器在判断节点是否容纳 Pod 时,会计算它们的请求。初始化需求、Pod 额外开销以及受支持的 Pod 级资源配置,也可能影响有效请求。最终经过准入的 Pod 才是实际输入,包括默认值和修改结果,而不只是操作者记得提交的源文件。排查时应读取保存下来的对象,确认控制面实际看到什么。 Node allocatable 也不同于硬件总容量,它表示按照节点配置可以提供给 Pod 的资源量。其他工作负载即使暂时空闲,其资源请求仍然占用调度记账中的空间。不过,请求并不表示应用已经实际分配了同样多的物理内存,也不表示独占相应数量的 CPU 核心。放置记账、实时消耗和独占策略属于不同机制,不能混用“预留”一词解释。 限制通过执行栈约束运行中的工作负载,通常不会在调度检查时替代已经显式填写的较小请求。因此,一个容器的 CPU limit 可以大于单个节点 CPU 容量,同时仍然凭较小 request 完成调度。这个被接受的限制不会制造额外处理器,也不承诺进程一定能够达到填写的上限;它只是配置了允许使用的边界。 坏掉的样子 Pending 可能源于请求资源不足,也可能由污点、亲和性、存储、拓扑或其他条件阻塞。改变资源值之前,应查看 PodScheduled 条件和调度事件。降低请求无法修复不匹配的节点选择器或不可用的卷;如果原请求本来真实,盲目降低还可能把调度失败转化成运行期争用,并没有建立合理容量方案。 默认值让省略字段尤其容易误读。填写 limit 而不填 request,且没有准入机制提供另一默认请求时,Kubernetes 会把该资源限制复制为请求。因此,只设置一个很大的 limit,也可能形成很大的调度需求。应查看实际对象中的两个映射,并考虑 LimitRange 的影响,再解释放置失败,不能假定省略请求就等于完全没有记账成本。 ...

四月 2, 2028 · 3 分钟 · XB Field Notes
控制器清理旧任务,避免集群被已完成的工作塞满

Kubernetes CronJob 需要历史清理策略

CronJob 本身不跑你的任务:它创建一个 Job,Job 再创建 Pod。Pod 跑完之后,Job 和 Pod 都不会自动消失。Pod 是故意留着的,好让你事后还能读到它的日志;Job 留着,是为了让你还能看到这次运行的状态。 这份默认对一次运行是对的,对一万次运行就是错的。每一个已完成的 Job 都是 etcd 里的一个对象、每次列表响应里的一行,以及所有关心 Job 的控制器都会收到的一次 watch 事件。给这堆东西设边界的有两套彼此独立的机制:CronJob 控制器按数量裁的历史上限,和 TTL 控制器按时间裁的 TTL。 已完成的 Job 并不免费 已完成的 Job 会留住它的 Pod,而 Pod 只要还在,容器日志就还在。于是保留策略成了你和“命名空间堆满墓碑”之间唯一的东西。按小时调度跑三十天就是 720 次运行:720 个 Job 加 720 个 Pod,每个都有自己的名字、状态、owner reference,以及你每次 kubectl get jobs 输出里的一行。已完成的 Job 也不是完全静止的:它的 status conditions 一直留在 API 里,被每个 watcher 反复读取。单个对象的开销微不足道,但它是随时间线性增长的,所以它长期保持隐形,直到某天突然变成问题。 ...

二月 25, 2028 · 2 分钟 · XB Field Notes
用户任务表以所属账号执行,系统任务表则另外指定执行账号。

Cron 系统任务表与用户任务表有不同契约

Cron 可以从不止一种任务表读取计划。用户任务表属于某个账号,而系统任务表能够为每一条记录指定运行账号。把一行原样复制到另一种表中,如果没有调整字段,cron 对命令起点的理解就会变化。排查看似正确的五字段日历表达式之前,应先确认正在编辑的到底是哪种任务表,而不是只检查分钟和小时。 账号列改变了解析边界 传统用户记录依次填写分钟、小时、月内日期、月份、星期,再接命令正文,由 crontab 命令安装给选定用户。Debian 的 /etc/crontab 和 /etc/cron.d 下文件,则在五个日历字段之后额外增加用户名。这个用户名属于系统表格式,并不是可以随意放进用户任务表的说明性字段;多写或少写都会改变后续解释。 月份限制仍然必须满足。当月内日期与星期都受到限制时,Debian/Vixie 的日期规则允许满足其中之一,但月份与星期并不是二选一。把表达式迁移到带秒字段或采用其他日期组合方式的调度产品前,应核对具体方言。表面上相似的空格布局,不能证明两个调度器提供相同语义。 系统任务文件还受所有权、权限、名称及末尾换行要求约束。Debian 中应采用 root 所有、组和其他用户不可写且名称被接受的文件,不要把任意备份后缀当成有效任务名。/etc/cron.daily 等周期目录通常通过发行版安排和 run-parts 被调用,不是每份脚本都独立当作 crontab 解析。实际运行时间需要查看本机已安装的调度安排。 坏掉的样子 在 root 用户自己的任务表中,五字段后再写 root,意味着让 shell 执行一个名为 root 的命令。任务表安装成功,不证明这个命令存在。反过来,系统表漏掉账号列,就会把命令文字当成用户名。审阅时需要同时检查解析边界与最终身份,尤其是任务会写文件或读取受保护数据时,不能仅凭“都是 root 配置的”推断执行账号。 任务环境同样是一份契约。不要默认交互别名、shell 启动文件或启动 cron 的父进程环境都会传给任务。需要的值应明确配置,同时选择合适的 PATH 和解释器。cron.d 文件中的环境也不应被认为自动继承 /etc/crontab 中的设置。验证时要采用真正的运行账号检查权限与输出,避免管理员手工执行成功掩盖低权限任务失败。 ...

一月 12, 2028 · 2 分钟 · XB Field Notes
让调度优先选择或强制要求某些节点标签的规则。

节点亲和把工作负载放到合适的节点

亲和性是放置匹配:可以"偏好"某个标签也可以"强制"它,再用反亲和把副本散开,换取更强的韧性。硬件从来不会长期均匀——这边一池 GPU,那边高速本地 NVMe,还有合规数据的隔离区——而工作负载悄悄依赖落到对的地方。没有显式规则,调度器只按空闲容量铺,需要 GPU 的 Pod 落到没有 GPU 的节点,在运行时而不是调度时才失败。 亲和把这次失败前移到它该在的位置。 placements 写成节点上的数据(标签)配 Pod 上的规则,可 review 的配置代替了"哪些节点是特殊的"部落知识。读 manifest 的人一眼看出工作负载必须跑在哪、为什么。 按标签匹配节点 亲和性规则读取节点标签,让你说明某个 Pod 应该、甚至必须落在带某个标签的节点上。把稳定的标签一次性打在节点上,剩下的就让调度器去匹配。accelerator=nvidia-a100、storage=nvme、zone=regulated 这类标签描述机器的持久属性;因为它们住在 node 对象上,换机器也不影响引用它的 Pod 规则——新硬件打同样的标签,老规则继续生效。 标签名要像 API 字段一样认真起,因为改一个被广泛引用的标签名,就是跨所有匹配它的 manifest 的迁移。约定小而有文档的词表,胜过每队自创 key:workload-class、hardware、compliance 覆盖大多数家底,再配半页约定文档,免得 GPU 标签拼出五种写法。像校验镜像 tag 一样在 CI 里校验标签——要求一个没有任何节点携带的标签的 Pod,是该死在流水线里的拼写错误,而不是凌晨三点的发布。 偏好还是强制 偏好型规则尽力而行,条件允许就把负载摊开;强制型规则则硬性拒绝那些满足不了标签的 Pod。只有放置真地有讲究才用强制,否则偏好能留给调度器更多弹性。成本模型很直白:强制拿可用性换正确——没有匹配节点有位子,Pod 就 Pending,也不去错的地方;偏好拿正确换可用——落到次优但活着的地方。 ...

十二月 9, 2027 · 1 分钟 · XB Field Notes
调度下限与硬上限分别设定的 Pod。

requests 与 limits:Pod 的下限与上限

集群按 requests 做调度、却按 limits 强执行,所以只配一边的容器,留下的是半个契约。只配 requests 的 Pod 调度诚实、突发无上限,流量尖峰时饿死邻居;只配 limits 的 Pod 运行时被封顶、调度时却按零成本算,调度器把它密密塞,直到节点饱和。半个契约压力下一碰就碎;两个数字只有成对才成立。 把 requests 当订座、limits 当店规。订座决定哪台节点有位,店规决定客人闹起来怎么办。两个都按测量设,集群行为就可预测:装箱诚实、突发有笼、凌晨三点的惊喜稀少。订座只管进门那一刻,店规管的是之后每一次喧哗,缺了任何一边,另一边的承诺都会在压力下走样。 两个数字、两种职责 CPU 的 requests 承诺调度器在节点上预留的份额,limits 则是容器在 cgroup 开始节流前能顶到的上限。两者都要配:requests 决定落位,limits 决定天花板。CPU 可压缩,超 CPU limit 只是变慢——节流,不死;内存不可压缩,超内存 limit 进程被 OOM 杀,中间没有减速带。这个不对称决定一切合理的配额政策:CPU 上限可以远高于预订去吃突发,内存上限应该贴着实测工作集加余量。 不写自有带牙的默认,而且准入策略还可能改变最终结果。没有 CPU request 时,除非 LimitRange 或显式 limit 提供默认值,调度器会按零请求计算;Pod 可能被塞得过密,争用时拿不到稳定份额。没有内存 limit 时,进程可以一直长到节点承压,再由 kubelet 回收或驱逐工作负载。要检查准入后的 Pod,而不只是提交前的 YAML,因为命名空间默认值可能已经替你填好了数字。 ...

五月 27, 2027 · 1 分钟 · XB Field Notes
一种让带选中标签的 Pod 在拓扑上彼此远离的调度规则。

Pod 反亲和把同组峰值彼此分散

反亲和就是那条让副本彼此分开的调度规则。亲和说“把我放在这些 Pod 旁边”,反亲和说“把我放得离它们远一点”,而理由几乎总是故障域:三个副本要是都落在一台机器上,这台机器一挂就是彻底不可用。 矛盾全在同一个词上。required 让规则变成过滤器,一个无处可去的 Pod 会永远停在 Pending;preferred 让它变成打分,同一条规则就变成优雅降级。同一份 YAML 的形状,写哪一个词,决定了它是死锁还是软偏好。 一条规则 = 一个选择器加一个拓扑 这条规则有两部分。labelSelector 决定哪些 Pod 算作同类,topologyKey 指定用哪个节点标签来划分域。kubernetes.io/hostname 让每台节点各自成一个域;topology.kubernetes.io/zone 把节点按可用区分组,于是“分开”就变成了“分散到不同可用区”。namespaces 字段默认取 Pod 自己所在的命名空间,所以反亲和不会意外跨租户生效。 没有这个 topologyKey 标签的节点不属于任何域。文档明确提醒,反亲和要求节点标签一致,缺少这个键会导致意料之外的行为。实际表现是:没有标签的节点对这条规则完全隐形——既不会被滤掉,也不会被计入——于是它反而会聚起那些本该被分散开的副本。还有一个细节:已经存在的 Pod 身上带的 preferred 规则,在调度新 Pod 时会被忽略。只有待调度 Pod 自己的规则算数,所以给一个正在运行的负载补上分散规则,并不会重新洗牌已经落位的副本。 required 是上限,不是保证 required 反亲和是一个节点过滤器,因此它把负载的副本数上限压到“每个域一个”。九副本铺在六台节点上,会永远留下三个 Pending,无论还有多少空闲容量。铺在三个可用区上时,四副本会留下一个 Pending。每个被拒的 Pod 都报 FailedScheduling,自动扩缩容一直表示帮不上忙,而这一切从不报错。 ...

三月 18, 2027 · 1 分钟 · XB Field Notes
一个节点级偏好配合一个 Pod 级容忍,共同决定调度准入。

污点会排斥那些不打算容忍它的 Pod

污点是一块“闲人勿进”的牌子,容忍度则是通行证,于是你可以要求预算型、GPU 或竞价节点只接纳匹配的负载。 给专用者的标记 污点(taint)把节点标成「闲人勿进」,而它本质上是一块牌子,不是一个动作:光打污点并不会把已经运行的东西搬走。新来的 Pod 只要没带匹配的容忍度就绕开,容忍度才是那张「通行证」。这正是你为一个子集群留出专属硬件的方式:GPU 节点、竞价节点、带许可证的机器,只接纳确实需要的负载,而其它 pod 仿佛这批节点不存在。由于它默认按范围生效,一套集群里可以同时住着通用池和专用池,而不必靠一堆标签去捋。 效果决定行为 真正把「牌子」变成「行为」的是污点的 effect 字段。NoSchedule 只挡住新 Pod,对已在运行的不动,是你不想再往里面塞容量时的默认选择。PreferNoSchedule 是软偏好,倾向于、但不禁止,适合折价的池而非硬约束。NoExecute 则会把已经绑上去的 Pod 也驱离,它也常常被拿去「把一个节点排空」:给节点打上 NoExecute,调度器会主动把其上的运行移走。挑哪种 effect,取决于你这一刻真正要的是「拦住」还是「清空」,因为软排除与硬排除是两种截然不同的保证。 配对即约定 容忍度与污点要靠 key、运算符与 effect 三者完全对上才算匹配,因此这层配对是明文、是可以被审查的契约。一份容忍度表达的是「在我能扛住这种污点的节点上生活」,它既不表达偏爱、也不会强制去哪台机器,因此一个带容忍度的 Pod 仍可能落在别处。这正是为什么一个集群既能有一批 GPU 节点、又能有一批通用节点而不互相摸错:放不下的 Pod 就不会被放到那里,而专用负载稳当地落在它们需要的硬件上。把这份配对当成正式的约定去写,哪种代价、为什么要加,到期日是什么,否则契约就会慢慢变成一锅标签粥。 坏掉的样子 最贵的故障是过宽的容忍度污染了专用池。一条写着 operator: Exists 却不带值的容忍度,或一份被复制进公共图表的容忍度,会匹配该键下的所有取值,于是普通发布突然有资格落到图形处理器或竞价节点上。预留池被廉价负载占满,而它真正要服务的专用负载反而长期等待调度。这恰好与守护进程集的故障互为镜像:那边是缺了容忍度导致专用节点没有覆盖,这边是多了容忍度导致专用节点失去保护。请把容忍度当作权限通配符来审计,公共模板里必须写清键值与效果三者。 第二种故障是把本该用 NoSchedule 的地方写成了 NoExecute。前者只拦截新负载,后者会把没有匹配容忍度的运行中负载直接驱离,还可能在 tolerationSeconds 倒计时结束后动手。一个维护污点若选错效果,几秒钟就能排空整组生产负载,而从示例里顺手复制的六十秒容忍倒计时,会把一次预留变成一场无人排期的延迟驱逐。与亲和不同,亲和从不驱逐,而污点侧的效果字段自带破坏力,因此维护污点先在单台节点演练,看过负载分布再向整池推广。 ...

二月 25, 2027 · 2 分钟 · XB Field Notes