4. EKS数据面节点管理
EKS的控制面由AWS托管,但数据面节点始终是平台工程师的主战场。节点如何创建、如何扩容、如何升级、如何退场,直接决定了集群的成本、稳定性和发布体验。本节围绕五种节点供给方式与生命周期管理展开,重点讲清楚“什么时候用哪种”“参数怎么配”“下线时如何不丢请求”。
一、三种节点供给方式的定位
托管节点组(Managed Node Group,MNG)由EKS负责节点的AMI选择、kubelet参数、启动模板校验、滚动升级与节点修复。你只需要指定实例类型、期望/最小/最大容量、子网、标签与污点,EKS会在后台维护一个Auto Scaling Group。它的优势是升级路径标准化:aws eks update-nodegroup-version 会按 maxUnavailable 或 maxUnavailablePercentage 分批替换节点,并自动打上 eks.amazonaws.com/nodegroup 标签。缺点是自定义能力受限,例如不能随意改用户数据脚本,只能通过启动模板注入有限的配置。
自管理节点组(Self-Managed Node Group)本质是你自己维护的EC2 Auto Scaling Group + 自定义AMI + 用户数据。它适合需要深度定制kubelet、挂载特殊存储、运行GPU驱动预装、或使用非标准AMI的场景。代价是升级、修复、节点加入都要自己写脚本和监控。典型用户数据会包含 /etc/eks/bootstrap.sh 调用,并传入集群名、API端点、kubelet额外参数。
Karpenter是AWS开源的节点自动扩缩器,它不依赖ASG,而是直接调用EC2 Fleet API按Pod需求实时创建实例。Karpenter通过 NodePool 与 EC2NodeClass 两个CRD描述“可以用哪些实例、放在哪些子网、用什么AMI、什么时候回收”。它的核心价值是:秒级响应、按需选择最便宜的实例类型、支持 consolidation(合并碎片)与 spot 中断处理。对于突发流量、批处理、AI推理等负载,Karpenter通常比Cluster Autoscaler更省成本。
二、节点AMI与启动模板
EKS节点AMI分为 EKS Optimized AMI(Amazon Linux 2 / AL2023 / Bottlerocket)与自定义AMI。托管节点组默认使用EKS Optimized AMI,版本随Kubernetes版本发布,AMI ID形如 ami-0xxxxxxxx,可通过 aws ssm get-parameter --name /aws/service/eks/optimized-ami/1.29/amazon-linux-2/recommended/image_id 查询。启动模板(Launch Template)用于固化实例类型、安全组、IAM实例配置文件、用户数据、块设备映射等。托管节点组支持在创建时指定启动模板,但EKS会覆盖部分字段(如AMI ID、用户数据中的bootstrap逻辑),因此自定义用户数据必须谨慎,避免与EKS注入的引导脚本冲突。
一个常见的启动模板用户数据片段如下,用于给kubelet追加参数并注册到指定集群:
#!/bin/bash
set -e
/etc/eks/bootstrap.sh my-cluster \
--kubelet-extra-args '--max-pods=58 --node-labels=workload=batch' \
--container-runtime containerd \
--apiserver-endpoint https://xxxx.gr7.us-west-2.eks.amazonaws.com
注意:托管节点组中,EKS会自动在用户数据前后拼接自己的引导逻辑,因此不要重复调用bootstrap.sh,否则节点可能注册失败。自管理节点组则完全由你控制这段脚本,可以加入GPU驱动安装、EFA配置、本地NVMe挂载等。
三、Karpenter的配置要点
Karpenter的 NodePool 定义容量约束与调度策略,EC2NodeClass 定义基础设施细节。一个典型配置示例(教学示例,非实测):
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
- key: node.kubernetes.io/instance-type
operator: In
values: ["m5.large","m5.xlarge","m6i.large","c5.large"]
nodeClassRef:
name: default
limits:
cpu: "1000"
memory: 1000Gi
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 30s
Karpenter会在Pod处于Pending时,根据资源请求、亲和性、污点容忍、拓扑分布等约束,计算最合适的实例类型并直接调用EC2 Fleet创建。它还会监听Spot中断通知(通过SQS),在实例被回收前主动驱逐Pod。与Cluster Autoscaler相比,Karpenter不需要预先定义节点组,扩容延迟通常从分钟级降到秒级。
四、节点生命周期与优雅下线
节点从创建到回收经历五个阶段:Provision → Ready → Cordon → Drain → Terminate。真正影响业务的是Cordon与Drain之间的窗口。Cordon将节点标记为不可调度,Drain则驱逐节点上的Pod。驱逐时,kubelet会向容器发送SIGTERM,并等待 terminationGracePeriodSeconds(默认30秒)。如果Pod定义了 preStop Hook,kubelet会先执行Hook再发送SIGTERM。若Pod属于某个Service,还需要确保Endpoint从kube-proxy/Ingress中摘除,否则可能出现短暂502。
优雅下线的关键参数与检查点:
- PodDisruptionBudget(PDB):限制自愿驱逐时同时不可用的Pod数量,例如
minAvailable: 2。Drain会尊重PDB,若驱逐会导致可用副本低于阈值,则阻塞并重试。 - terminationGracePeriodSeconds:应大于应用实际处理完一个请求的最长时间,通常设置为30~120秒。
- preStop Hook:常用
sleep 5等待Endpoint摘除,或调用应用的健康检查接口进入“拒绝新请求”状态。 - 节点排空超时:
kubectl drain --timeout=300s,超时后节点可能被强制终止,导致Pod异常。 - 节点终止处理器:AWS Node Termination Handler可监听ASG生命周期钩子或Spot中断,提前Cordon并Drain,避免请求丢失。
kubectl get nodes 与 kubectl get pdb -A 的状态。五、选型建议与常见排障
选型上,标准无状态服务优先托管节点组,减少运维负担;需要GPU、特殊内核或自定义AMI时选自管理节点组;突发流量、批处理、AI推理优先Karpenter。混合使用也很常见:核心服务跑托管节点组,弹性负载跑Karpenter。
常见问题包括:节点NotReady通常检查kubelet日志与安全组出站规则;Pod无法调度到新节点检查污点与标签;Drain卡住检查PDB与preStop超时;Karpenter不扩容检查NodePool的limits与实例类型是否满足Pod资源请求。排障时优先看 kubectl describe node 的Conditions与Events,再结合CloudWatch中的kubelet指标定位。