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更省成本。

EKS 数据面节点供给与生命周期全景 EKS 控制面 API Server / Scheduler / CA 托管节点组 MNG EKS 管理 ASG + AMI 滚动升级 / 自动修复 启动模板注入有限 适合:标准无状态服务 自管理节点组 自建 ASG + 自定义 AMI bootstrap.sh 用户数据 升级/修复全自管 适合:GPU / 特殊内核 Karpenter NodePool + EC2NodeClass EC2 Fleet 直创实例 Consolidation / Spot 适合:突发 / 批处理 / AI Kubernetes 节点池(Node) kubelet · containerd · CNI · CSI · 标签 / 污点 / 亲和性 节点生命周期与优雅下线 Provision 创建 / 加入集群 Ready 承载 Pod Cordon 禁止新 Pod 调度 Drain 驱逐 + PDB 等待 Terminate 回收实例 关键:terminationGracePeriodSeconds · PDB · preStop Hook · 节点排空超时

二、节点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,避免请求丢失。
托管节点组升级时,EKS会自动执行Cordon与Drain,但不会等待PDB无限期阻塞。若PDB配置过严,升级可能卡住。建议为关键服务设置合理的PDB,并在升级窗口内观察 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指标定位。

节点下线不是“删实例”这么简单。没有PDB、没有preStop、没有足够宽限期的服务,在滚动升级或Spot回收时几乎必然出现请求失败。把优雅下线当作发布流程的一部分来设计,而不是事后补救。