节点供给与自动伸缩
HPA 创建了 40 个新 Pod,全部停在 Pending。Cluster Autoscaler 没有扩节点,值班人员于是手工把节点组上限从 30 调到 100,仍然没有变化。真正原因是 Pod 要求一个节点组模板中不存在的 zone 标签;无论上限多大,复制同样的模板都不可能得到可行节点。另一次事故中,Karpenter 已创建云实例,但节点 bootstrap 无法访问私有镜像仓库,NodeClaim、实例和 Node 三层状态被分别判成了“成功”“运行中”和“未就绪”。
节点自动伸缩不是按平均 CPU 买机器。它从 kube-scheduler 认定不可调度的 Pod 出发,验证某种节点是否满足 request、污点、亲和、拓扑、存储和设备约束,再经过云 API、配额、实例库存、操作系统启动、kubelet 注册、CNI/CSI 与 DaemonSet,最终把容量交回调度器。任何一层都可能让扩容停住。
触发条件是不可调度需求,不是利用率曲线
节点伸缩器观察的是没有可行节点的 Pod。节点平均 CPU 很高但没有 Pending Pod,通常不会触发扩容;节点利用率很低但某个 Pod 需要大内存、GPU、特定 zone 或 PVC 拓扑,仍可能触发新容量。request 和调度约束因此是需求合同。
kubectl get pod -A --field-selector=status.phase=Pending -o wide
kubectl get event -A --sort-by=.lastTimestamp | grep -E 'FailedScheduling|NotTriggerScaleUp|TriggeredScaleUp'
kubectl get node -L topology.kubernetes.io/zone,node.kubernetes.io/instance-type
kubectl describe pod -n '<namespace>' '<pending-pod>'先从 FailedScheduling 还原候选节点为何全部被过滤,再看伸缩器是否找到可行节点模型。直接增加最大节点数只能解除上限,不能修复错误标签、卷拓扑、端口冲突或单 Pod 大于最大实例的问题。
Cluster Autoscaler 以节点组模板做模拟
Cluster Autoscaler 把候选节点组的模板放进调度模拟,选择可容纳 Pending Pod 的组并调整规模;缩容阶段再判断节点是否低利用且 Pod 可安全迁移。节点组、云 provider 和 Kubernetes minor 必须匹配目标发行线。
Pending Pod
-> 节点组模板是否满足调度约束
-> expander 在可行节点组中选择
-> 调整组 desired size
-> 云平台创建实例
-> Node 注册并 Ready本地存储、不可驱逐 Pod、PDB、系统关键 Pod、DaemonSet 开销和 scale-down 延迟会阻止缩容。扩容和缩容要分别设置证据与告警,不能把“节点数没有下降”一律当成故障。Cluster Autoscaler 1.36 文档记录了 provider、版本和配置入口。
Karpenter 从约束集合选择实例
Karpenter 使用 NodePool 表达共享约束和预算,NodeClass 表达 provider 资源,NodeClaim 表达一次具体容量请求。它可以扩大实例类型、zone 和容量类型的候选集合,再由 provider 创建节点。Pod 最终仍由 kube-scheduler 放置,Karpenter 不是调度器替代品。
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: general
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: [on-demand, spot]
limits:
cpu: "500"NodePool 约束过宽会扩大云权限和费用,过窄会回到固定节点组的供应瓶颈。Consolidation、Drift 与 expiration 能回收浪费,也可能引发批量换机;必须结合 disruption budget、PDB、应用退出时间和 Spot 中断处理。Karpenter Concepts提供当前对象模型。
供给时间线横跨云与集群
一次扩容至少记录:首次 Unschedulable、伸缩器决策、云 API 请求、实例创建、kubelet 注册、Node Ready、系统 DaemonSet Ready、目标 Pod 调度、Pod Ready 和业务恢复。云实例 Running 只证明虚拟机存在,不证明 kubelet、网络、存储和镜像链路可用。
kubectl get node -w
kubectl get nodeclaim -A -o wide
kubectl get event -A --sort-by=.lastTimestamp
kubectl get daemonset -A
kubectl describe node '<new-node>'反向实验可以把隔离节点池上限设为当前值,或使用不存在的实例要求。预期伸缩器明确报告上限、无可用类型、配额或权限失败;恢复后要确认 Pending Pod 获得容量,并核对实验节点、磁盘、公网地址和负载均衡附件全部回收。
启动、排空和整合是同一生命周期
节点创建后通常带 startup taint,直到网络、存储、安全传感器和必要 DaemonSet 就绪才允许业务进入。缩容时先 cordon、drain,再等待 Pod 在其他节点 Ready,最后终止实例。跳过任一步都会把容量节省变成业务中断。
PDB 只约束自愿驱逐的可用副本,不保证目标节点有足够资源;terminationGracePeriodSeconds 也不能替应用完成连接摘除、checkpoint 和队列确认。GPU、本地盘和超大 Pod 的重新放置尤其需要预先证明候选容量存在。
选型比较控制权,而不是只比速度
固定托管节点池适合边界明确、审批稳定的容量;Cluster Autoscaler 适合节点组模型;Karpenter 适合需要在多规格和容量类型中动态选择的云环境。平台托管的 Node Auto Provisioning 则换取更少运维面,同时接受云平台能力、可观测性和退出限制。
同一套实验比较扩容 P50/P95、可行实例覆盖、zone 容错、Spot、缩容扰动、云 IAM、版本升级、故障证据和退出成本。不得把某次库存充足时的最快结果写成永久结论,也不能假设 AWS、Azure 和其他 provider 具有同等成熟度。
权限、配额和费用必须设硬边界
节点伸缩器通常拥有读取全局 Pod、Node、PDB 与存储信息的权限,还可调用云实例、网络、角色和标签 API。使用专用身份和最小策略,限制可用 subnet、安全组、实例角色、规格、zone、容量类型与总 CPU/内存;审计日志不得泄露启动脚本、注册 Token 和用户数据。
NodePool/节点组上限、云账号配额和预算告警是三道不同边界。上限保护控制器,配额保护云资源,预算只在费用产生后报警。预留容量、负优先级占位 Pod 和预拉镜像能缩短恢复时间,但有持续成本;架构决策要把恢复 SLO 与空闲费用放在同一张表里。
升级、迁移与退出先恢复静态容量
升级前核对 Kubernetes minor、CRD、provider、弃用参数和节点镜像;先在小节点池验证扩容、排空、整合与回滚。Cluster Autoscaler 与 Karpenter 迁移期间不能共同管理同一容量池,应该按工作负载或节点池划分边界,保留静态最小容量。
退出时先把目标负载迁回稳定节点池,冻结 consolidation 与 scale-down,确认所有 Pod Ready,再删除 NodePool、NodeClass、NodeClaim 或伸缩器。最后核销 ClusterRole、webhook、云 IAM、实例、磁盘、弹性 IP 和持续费用。只有当 Pending、Node Ready、业务恢复和云资产四组证据都闭合,节点弹性才算真正可运营。
值班手册还要保存一条不依赖自动控制器的恢复路径:确认关键工作负载最低副本、手工恢复经过审批的节点池最小值、验证系统 DaemonSet 和业务 readiness,再逐步解除冻结。自动伸缩器升级失败、云 API 限流或 CRD 不可用时,这条静态容量路径决定团队能否在错误预算内止血。它应按季度演练,并核对执行身份、配额和镜像仍然有效。
