EKS 节点组管理

节点组类型

Managed Node Groups(推荐)

什么是 Managed Node Groups:

特点:
├─ AWS 完全托管节点生命周期
├─ 自动化的节点更新和打补丁
├─ 基于 Auto Scaling Groups
├─ 集成 EKS 控制平面
├─ 一键创建和管理
└─ 自动注册到集群

管理的内容:
├─ EC2 实例创建和终止
├─ Auto Scaling Group 配置
├─ 节点健康检查
├─ 滚动更新
├─ AMI 自动更新
└─ 集群加入配置

用户控制的内容:
├─ 实例类型选择
├─ 磁盘大小
├─ SSH 密钥
├─ 标签和污点
├─ 启动模板
└─ 扩展策略

节点组网络配置

子网选择(重要):

推荐配置:
├─ 节点组使用 3 个私有子网
│   ├─ us-east-1a: 10.0.11.0/24
│   ├─ us-east-1b: 10.0.12.0/24
│   └─ us-east-1c: 10.0.13.0/24
│
├─ 节点部署在私有子网的优势:
│   ├─ 无公网 IP,安全性更高
│   ├─ 通过 NAT Gateway 访问互联网
│   ├─ 内部服务间直接通信
│   └─ 符合企业安全要求
│
└─ 出站流量路径:
    ├─ 节点 → NAT Gateway → Internet Gateway → 互联网
    ├─ 用于:拉取镜像、下载包、API 调用
    └─ 每个 AZ 独立 NAT Gateway(高可用)

为什么不使用公有子网:
├─ 节点直接暴露公网(安全风险)
├─ 需要分配公网 IP(成本增加)
├─ 不符合企业安全策略
└─ 增加攻击面

节点实例类型选择

实例类型分类

按用途分类:

1. 通用型(推荐大多数场景)
   t3 系列(突发性能):
   ├─ t3.medium:2 vCPU, 4GB RAM, 最多 17 Pod
   ├─ t3.large:2 vCPU, 8GB RAM, 最多 35 Pod
   └─ t3.xlarge:4 vCPU, 16GB RAM, 最多 58 Pod
   
   m5 系列(平衡型):
   ├─ m5.large:2 vCPU, 8GB RAM, 最多 29 Pod
   ├─ m5.xlarge:4 vCPU, 16GB RAM, 最多 58 Pod
   └─ m5.2xlarge:8 vCPU, 32GB RAM, 最多 58 Pod
   
   适用场景:
   ├─ Web 应用
   ├─ 微服务
   ├─ 开发/测试环境
   └─ 混合工作负载

2. 计算优化型
   c5 系列:
   ├─ c5.large:2 vCPU, 4GB RAM, 最多 29 Pod
   ├─ c5.xlarge:4 vCPU, 8GB RAM, 最多 58 Pod
   └─ c5.2xlarge:8 vCPU, 16GB RAM, 最多 58 Pod
   
   适用场景:
   ├─ CPU 密集型应用
   ├─ 批处理
   ├─ 科学计算
   └─ 游戏服务器

3. 内存优化型
   r5 系列:
   ├─ r5.large:2 vCPU, 16GB RAM, 最多 29 Pod
   ├─ r5.xlarge:4 vCPU, 32GB RAM, 最多 58 Pod
   └─ r5.2xlarge:8 vCPU, 64GB RAM, 最多 58 Pod
   
   适用场景:
   ├─ 缓存
   ├─ 内存数据库
   ├─ 大数据分析
   └─ 实时处理

4. 存储优化型
   i3 系列(NVMe SSD):
   ├─ i3.large:2 vCPU, 15GB RAM, 475GB NVMe
   └─ i3.xlarge:4 vCPU, 30GB RAM, 950GB NVMe
   
   适用场景:
   ├─ 数据库
   ├─ NoSQL
   ├─ 高 IOPS 需求
   └─ 日志处理

5. GPU 加速型
   p3 系列(深度学习):
   ├─ p3.2xlarge:8 vCPU, 61GB RAM, 1x V100
   └─ p3.8xlarge:32 vCPU, 244GB RAM, 4x V100
   
   g4 系列(图形渲染):
   ├─ g4dn.xlarge:4 vCPU, 16GB RAM, 1x T4
   └─ g4dn.2xlarge:8 vCPU, 32GB RAM, 1x T4

实例选型决策树

如何选择实例类型:

问题 1:预算如何?
├─ 预算紧张 → t3 系列(突发性能,性价比高)
└─ 预算充足 → 继续

问题 2:主要负载类型?
├─ Web/API 服务 → m5 系列(通用平衡)
├─ CPU 密集 → c5 系列(计算优化)
├─ 内存密集 → r5 系列(内存优化)
├─ 存储密集 → i3 系列(NVMe SSD)
└─ GPU 加速 → p3/g4 系列

问题 3:Pod 密度需求?
├─ 高密度(>30 Pod/节点)→ xlarge 及以上
├─ 中密度(15-30 Pod)→ large
└─ 低密度(<15 Pod)→ medium

问题 4:可用性要求?
├─ 高可用(99.95%+)→ On-Demand
├─ 成本优先(可中断)→ Spot
└─ 混合(推荐)→ 70% On-Demand + 30% Spot

推荐配置示例:
├─ 小型集群:3-6x t3.large
├─ 中型集群:6-12x m5.xlarge
├─ 大型集群:12-30x m5.2xlarge
└─ 混合:基线 On-Demand + 弹性 Spot

每种实例类型的 Pod 数量

ENI 和 IP 限制:

实例类型    ENI数  每ENI IP数  最大Pod数
─────────────────────────────────────
t3.medium    3      6          17
t3.large     3      12         35
t3.xlarge    4      15         58
t3.2xlarge   4      15         58

m5.large     3      10         29
m5.xlarge    4      15         58
m5.2xlarge   4      15         58
m5.4xlarge   8      30         234

c5.large     3      10         29
c5.xlarge    4      15         58
c5.2xlarge   4      15         58

r5.large     3      10         29
r5.xlarge    4      15         58
r5.2xlarge   4      15         58

公式:
最大 Pod 数 = (ENI 数 × 每 ENI IP 数) - ENI 数 + 2

说明:
├─ 每个节点保留一些 IP 用于节点本身
├─ -ENI 数:每个 ENI 占用一个主 IP
├─ +2:额外的 Pod 地址池
└─ 实际可用 Pod 数略少于理论值

实战:创建节点组

单节点组配置

#!/bin/bash
# create-node-group.sh

set -e

source eks-vpc-config.env

NODE_GROUP_NAME="general-nodes"
INSTANCE_TYPES=("t3.large" "t3.xlarge")
DISK_SIZE=50
MIN_SIZE=3
MAX_SIZE=10
DESIRED_SIZE=6

echo "================================================"
echo "创建 EKS Managed Node Group"
echo "================================================"

# 1. 创建节点组
echo "1. 创建节点组:$NODE_GROUP_NAME"
aws eks create-nodegroup \
  --cluster-name $CLUSTER_NAME \
  --nodegroup-name $NODE_GROUP_NAME \
  --scaling-config \
    minSize=$MIN_SIZE,\
maxSize=$MAX_SIZE,\
desiredSize=$DESIRED_SIZE \
  --disk-size $DISK_SIZE \
  --subnets ${PRIVATE_SUBNET_IDS[0]} ${PRIVATE_SUBNET_IDS[1]} ${PRIVATE_SUBNET_IDS[2]} \
  --instance-types ${INSTANCE_TYPES[0]} ${INSTANCE_TYPES[1]} \
  --ami-type AL2_x86_64 \
  --node-role $EKS_NODE_ROLE_ARN \
  --labels \
    role=general,\
environment=production,\
nodegroup=$NODE_GROUP_NAME \
  --tags \
    Name=$NODE_GROUP_NAME,\
Environment=production,\
ManagedBy=eks \
  --capacity-type ON_DEMAND \
  --update-config maxUnavailable=1 \
  --region $REGION

echo "   ✓ 节点组创建请求已提交"
echo "   ⏳ 等待节点组创建完成(约 5-10 分钟)..."

# 2. 等待节点组就绪
aws eks wait nodegroup-active \
  --cluster-name $CLUSTER_NAME \
  --nodegroup-name $NODE_GROUP_NAME \
  --region $REGION

echo "   ✓ 节点组已就绪"

# 3. 验证节点
echo ""
echo "2. 验证节点..."
kubectl get nodes -o wide
kubectl get nodes -o json | jq -r '.items[] | "\(.metadata.name): \(.status.nodeInfo.instanceType), \(.status.capacity.pods) max pods"'

echo ""
echo "================================================"
echo "节点组创建完成!"
echo "================================================"
echo "节点组名称:$NODE_GROUP_NAME"
echo "实例类型:${INSTANCE_TYPES[@]}"
echo "节点数量:$DESIRED_SIZE (最小 $MIN_SIZE, 最大 $MAX_SIZE)"
echo "磁盘大小:${DISK_SIZE}GB"
echo "子网:私有子网(3个 AZ)"
echo "出站流量:通过 NAT Gateway"
echo "================================================"

多节点组配置(生产推荐)

#!/bin/bash
# create-multiple-node-groups.sh

set -e

source eks-vpc-config.env

echo "================================================"
echo "创建多个 EKS 节点组"
echo "================================================"

# 1. 通用型节点组(On-Demand)
echo "1. 创建通用型节点组(On-Demand)..."
aws eks create-nodegroup \
  --cluster-name $CLUSTER_NAME \
  --nodegroup-name general-on-demand \
  --scaling-config minSize=3,maxSize=10,desiredSize=6 \
  --disk-size 50 \
  --subnets ${PRIVATE_SUBNET_IDS[0]} ${PRIVATE_SUBNET_IDS[1]} ${PRIVATE_SUBNET_IDS[2]} \
  --instance-types t3.large m5.large \
  --ami-type AL2_x86_64 \
  --node-role $EKS_NODE_ROLE_ARN \
  --labels \
    role=general,\
capacity-type=on-demand \
  --tags \
    Name=general-on-demand,\
CostCenter=baseline \
  --capacity-type ON_DEMAND \
  --update-config maxUnavailable=1 \
  --region $REGION

echo "   ✓ 通用型节点组创建完成"

# 2. Spot 实例节点组(成本优化)
echo ""
echo "2. 创建 Spot 实例节点组..."
aws eks create-nodegroup \
  --cluster-name $CLUSTER_NAME \
  --nodegroup-name general-spot \
  --scaling-config minSize=0,maxSize=20,desiredSize=3 \
  --disk-size 50 \
  --subnets ${PRIVATE_SUBNET_IDS[0]} ${PRIVATE_SUBNET_IDS[1]} ${PRIVATE_SUBNET_IDS[2]} \
  --instance-types t3.large t3.xlarge m5.large m5.xlarge \
  --ami-type AL2_x86_64 \
  --node-role $EKS_NODE_ROLE_ARN \
  --labels \
    role=general,\
capacity-type=spot \
  --taints \
    key=spot,value=true,effect=NoSchedule \
  --tags \
    Name=general-spot,\
CostCenter=flexible \
  --capacity-type SPOT \
  --update-config maxUnavailable=1 \
  --region $REGION

echo "   ✓ Spot 节点组创建完成"

# 3. 计算密集型节点组
echo ""
echo "3. 创建计算密集型节点组..."
aws eks create-nodegroup \
  --cluster-name $CLUSTER_NAME \
  --nodegroup-name compute-intensive \
  --scaling-config minSize=0,maxSize=10,desiredSize=2 \
  --disk-size 100 \
  --subnets ${PRIVATE_SUBNET_IDS[0]} ${PRIVATE_SUBNET_IDS[1]} ${PRIVATE_SUBNET_IDS[2]} \
  --instance-types c5.xlarge c5.2xlarge \
  --ami-type AL2_x86_64 \
  --node-role $EKS_NODE_ROLE_ARN \
  --labels \
    role=compute,\
capacity-type=on-demand \
  --taints \
    key=compute-intensive,value=true,effect=NoSchedule \
  --tags \
    Name=compute-intensive,\
Workload=cpu-bound \
  --capacity-type ON_DEMAND \
  --update-config maxUnavailable=1 \
  --region $REGION

echo "   ✓ 计算密集型节点组创建完成"

# 4. 等待所有节点组就绪
echo ""
echo "4. 等待所有节点组就绪..."
aws eks wait nodegroup-active \
  --cluster-name $CLUSTER_NAME \
  --nodegroup-name general-on-demand \
  --region $REGION &

aws eks wait nodegroup-active \
  --cluster-name $CLUSTER_NAME \
  --nodegroup-name general-spot \
  --region $REGION &

aws eks wait nodegroup-active \
  --cluster-name $CLUSTER_NAME \
  --nodegroup-name compute-intensive \
  --region $REGION &

wait

echo "   ✓ 所有节点组已就绪"

# 5. 验证节点
echo ""
echo "5. 验证所有节点..."
kubectl get nodes -L role,capacity-type -o wide

echo ""
echo "================================================"
echo "多节点组创建完成!"
echo "================================================"
echo ""
echo "节点组配置:"
echo "  1. general-on-demand: 基线工作负载(3-10 节点)"
echo "  2. general-spot: 弹性工作负载(0-20 节点,节省 70%)"
echo "  3. compute-intensive: CPU 密集型(0-10 节点)"
echo ""
echo "所有节点组使用私有子网:"
echo "  - 10.0.11.0/24 (AZ-A)"
echo "  - 10.0.12.0/24 (AZ-B)"
echo "  - 10.0.13.0/24 (AZ-C)"
echo ""
echo "出站流量通过 NAT Gateway"
echo "================================================"

使用启动模板(高级配置)

#!/bin/bash
# create-node-group-with-launch-template.sh

set -e

source eks-vpc-config.env

echo "================================================"
echo "使用启动模板创建节点组"
echo "================================================"

# 1. 创建启动模板
echo "1. 创建 EC2 启动模板..."

# 用户数据脚本(自定义节点初始化)
cat > userdata.sh << 'EOF'
#!/bin/bash
set -o xtrace

# 配置节点标签
/etc/eks/bootstrap.sh ${CLUSTER_NAME} \
  --kubelet-extra-args '--node-labels=custom=true,environment=production'

# 自定义配置
echo "vm.max_map_count=262144" >> /etc/sysctl.conf
sysctl -p

# 安装额外工具
yum install -y amazon-ssm-agent
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
EOF

# 创建启动模板
LT_ID=$(aws ec2 create-launch-template \
  --launch-template-name eks-custom-node-template \
  --launch-template-data '{
    "BlockDeviceMappings": [{
      "DeviceName": "/dev/xvda",
      "Ebs": {
        "VolumeSize": 100,
        "VolumeType": "gp3",
        "Iops": 3000,
        "Throughput": 125,
        "DeleteOnTermination": true,
        "Encrypted": true
      }
    }],
    "MetadataOptions": {
      "HttpTokens": "required",
      "HttpPutResponseHopLimit": 2
    },
    "TagSpecifications": [{
      "ResourceType": "instance",
      "Tags": [
        {"Key": "Name", "Value": "eks-custom-node"},
        {"Key": "ManagedBy", "Value": "launch-template"}
      ]
    }],
    "UserData": "'$(base64 -w 0 userdata.sh)'"
  }' \
  --region $REGION \
  --query 'LaunchTemplate.LaunchTemplateId' \
  --output text)

echo "   ✓ 启动模板 ID: $LT_ID"

# 2. 使用启动模板创建节点组
echo ""
echo "2. 创建节点组(使用启动模板)..."
aws eks create-nodegroup \
  --cluster-name $CLUSTER_NAME \
  --nodegroup-name custom-nodes \
  --scaling-config minSize=2,maxSize=8,desiredSize=4 \
  --subnets ${PRIVATE_SUBNET_IDS[0]} ${PRIVATE_SUBNET_IDS[1]} ${PRIVATE_SUBNET_IDS[2]} \
  --node-role $EKS_NODE_ROLE_ARN \
  --launch-template id=$LT_ID,version='$Latest' \
  --labels \
    custom=true,\
template=enabled \
  --tags \
    Name=custom-nodes \
  --update-config maxUnavailable=1 \
  --region $REGION

echo "   ✓ 节点组创建完成"

# 等待就绪
aws eks wait nodegroup-active \
  --cluster-name $CLUSTER_NAME \
  --nodegroup-name custom-nodes \
  --region $REGION

rm -f userdata.sh

echo ""
echo "================================================"
echo "自定义节点组创建完成!"
echo "使用了启动模板进行高级配置"
echo "================================================"

节点组更新和维护

更新节点组

#!/bin/bash
# update-node-group.sh

set -e

source eks-vpc-config.env

NODE_GROUP_NAME="general-on-demand"

echo "================================================"
echo "更新节点组"
echo "================================================"

# 1. 更新节点数量
echo "1. 更新节点数量..."
aws eks update-nodegroup-config \
  --cluster-name $CLUSTER_NAME \
  --nodegroup-name $NODE_GROUP_NAME \
  --scaling-config minSize=4,maxSize=15,desiredSize=8 \
  --region $REGION

# 2. 更新标签
echo ""
echo "2. 更新标签..."
aws eks update-nodegroup-config \
  --cluster-name $CLUSTER_NAME \
  --nodegroup-name $NODE_GROUP_NAME \
  --labels addOrUpdateLabels={version=v1.28,updated=true} \
  --region $REGION

# 3. 更新 AMI(滚动更新)
echo ""
echo "3. 触发滚动更新(更新 AMI)..."
aws eks update-nodegroup-version \
  --cluster-name $CLUSTER_NAME \
  --nodegroup-name $NODE_GROUP_NAME \
  --region $REGION

echo ""
echo "================================================"
echo "节点组更新已触发"
echo "更新过程:逐个替换节点(滚动更新)"
echo "================================================"

# 4. 监控更新进度
echo ""
echo "4. 监控更新进度..."
while true; do
  STATUS=$(aws eks describe-nodegroup \
    --cluster-name $CLUSTER_NAME \
    --nodegroup-name $NODE_GROUP_NAME \
    --region $REGION \
    --query 'nodegroup.status' \
    --output text)
  
  if [ "$STATUS" == "ACTIVE" ]; then
    echo "   ✓ 更新完成"
    break
  elif [ "$STATUS" == "UPDATING" ]; then
    echo "   ⏳ 更新中... ($(date '+%H:%M:%S'))"
    sleep 30
  else
    echo "   ⚠️  状态异常: $STATUS"
    break
  fi
done

节点耗尽和维护

#!/bin/bash
# drain-and-maintain-node.sh

# 1. 标记节点不可调度
echo "标记节点不可调度..."
kubectl cordon ip-10-0-11-100.ec2.internal

# 2. 耗尽节点(驱逐 Pod)
echo "耗尽节点..."
kubectl drain ip-10-0-11-100.ec2.internal \
  --ignore-daemonsets \
  --delete-emptydir-data \
  --force \
  --grace-period=120

# 3. 执行维护操作
echo "执行维护..."
# 例如:重启节点、更新配置等

# 4. 恢复节点调度
echo "恢复节点调度..."
kubectl uncordon ip-10-0-11-100.ec2.internal

节点组最佳实践

推荐配置

生产环境节点组配置:
├─ 基线节点组(On-Demand)
│   ├─ 实例类型:m5.large 或 t3.large
│   ├─ 数量:min=3, desired=6, max=10
│   ├─ 子网:3个私有子网
│   ├─ 用途:关键工作负载
│   └─ 成本:稳定可预测
│
├─ 弹性节点组(Spot)
│   ├─ 实例类型:多种混合(提高可用性)
│   ├─ 数量:min=0, desired=3, max=20
│   ├─ 子网:3个私有子网
│   ├─ 污点:spot=true:NoSchedule
│   ├─ 用途:无状态、可中断工作负载
│   └─ 成本:节省 70%
│
└─ 专用节点组(可选)
    ├─ 实例类型:根据需求(c5/r5/i3)
    ├─ 数量:按需配置
    ├─ 子网:3个私有子网
    ├─ 污点:专用标记
    ├─ 用途:特殊工作负载
    └─ 成本:优化配置

网络配置:
✅ 所有节点组使用私有子网
✅ 每个 AZ 独立 NAT Gateway
✅ 跨 AZ 均匀分布节点
✅ 合理规划 IP 地址空间
✅ 使用 VPC Endpoints 降低成本

安全配置:
✅ 使用 IAM 角色(不要硬编码凭证)
✅ 启用 IMDSv2(元数据服务)
✅ 最小权限原则
✅ 定期更新 AMI
✅ 配置 Pod Security Standards

扩展配置:
✅ 配置 Cluster Autoscaler
✅ 设置合理的最小/最大值
✅ 配置 Pod Disruption Budgets
✅ 使用多实例类型(Spot)
✅ 监控节点利用率

成本优化:
✅ 混合 On-Demand 和 Spot
✅ 使用 Savings Plans
✅ 右调优实例类型
✅ 监控未使用资源
✅ 定期清理僵尸资源

节点组管理完成,下一步配置网络和存储!