EKS Node Group 降規遷移:m7i.2xlarge → t3.xlarge 完整 Runbook、四次失敗根因、CNI/IPAM 診斷與 :50051 排查¶
文檔資訊
- 分類: tools
- 難度: intermediate
- 預估閱讀時間: 32 分鐘
- 標籤:
EKS,AWS,nodegroup,kubernetes,migration,downgrade,auto-mode,kubectl,eksctl,runbook,IAM,ASG,CloudFormation,NodeCreationFailure,CNI,aws-node,IPAM,Launch Template,hop limit,IRSA,Security Group
摘要¶
記錄 EKS Node Group 從 m7i.2xlarge 降規至 t3.xlarge 的完整遷移流程與四次失敗診斷過程。涵蓋五個診斷層次:1) Auto Mode 觸發非預期節點;2) NodeCreationFailure;3) CNI 初始化失敗(aws-node ½ Running);4) IPAM daemon 無法連線 gRPC port :50051;5) 進一步排查 hop limit、Security Group、IAM node role。新版重要修正:舊版假設 hop limit=1 為根因,但實測確認 hop limit=2,排除該假設,真正根因指向 aws-node 使用 node IAM role(非 IRSA)且可能缺少 AmazonEKS_CNI_Policy。
關鍵學習¶
-
EKS Auto Mode 開啟時縮減舊 Node Group 會觸發非預期 c 系列節點擴展,遷移前必須先關閉
-
aws-node Pod 顯示 ½ Running 並反覆 restart,表示 IPAM daemon 無法連線 gRPC port :50051
-
init container(aws-vpc-cni-init)成功不等於 CNI 正常——主容器 IPAM daemon 還需要 IMDS/EC2 API 存取
-
Readiness/Liveness probe 訊息 'timeout: failed to connect service ":50051" within 5s' 是精確失敗訊號
-
新修正:hop limit=2 確認不是根因;aws-node 使用 node IAM role(非 IRSA)時,AmazonEKS_CNI_Policy 是否附加在 Node Role 上至關重要
-
Security Group 正常(Outbound 全開、Inbound 含 SG 互通)不是本次失敗原因
-
Health.issues 空陣列只代表 EKS 控制平面無已知問題,真正失敗需查 CloudFormation StackEvents
-
CloudFormation JMESPath query 不能有換行,否則出現 parse error
-
eksctl get nodegroup 需要 eks:DescribeClusterVersions,developer IAM 通常缺少此權限
-
新 ng 的 aws-node 必須全部 2/2 Running(不是 ½)才能開始 Drain 舊節點
技術細節¶
環境資訊
- Cluster:
ps-eks-rc - Region:
ap-east-2 - 原始 Node Group:
ps-eks-rc-ng-private-managed-1(m7i.2xlarge, desired=2) - 目標 Node Group:
ps-eks-rc-ng-t3xlarge(t3.xlarge) - AMI Type: AL2023_x86_64_STANDARD
- aws-vpc-cni 版本: v1.20.4-eksbuild.1
五層失敗診斷鏈
第一層(錯誤方向):ASG Name: N/A → 懷疑 Subnet IP 不足 → 實際是 developer 缺少 ec2:DescribeInstances 權限導致顯示 N/A,非 ASG 未建立。
第二層(症狀確認):CloudFormation StackEvents 顯示 NodeCreationFailure: Unhealthy nodes in the kubernetes cluster,ResourceIds 確認 EC2 已建立(i-027d62de237b3fd01, i-0ac6c5e25d04eb56c),但節點無法健康加入叢集。
第三層(症狀定位):新 t3.xlarge 節點上的 aws-node Pod 呈 ½ Running 並反覆 CrashLoopBackOff(6次+),節點事件明確顯示 cni plugin not initialized,確認根因為 CNI 層問題。
第四層(精確失敗訊號):kubectl describe pod aws-node-q9vtr 完整 Events 顯示:
Warning Unhealthy kubelet Readiness probe failed: {"msg":"timeout: failed to connect service \":50051\" within 5s"}
Warning Unhealthy kubelet Liveness probe failed: {"msg":"timeout: failed to connect service \":50051\" within 5s"}
Normal Killing kubelet Container aws-node failed liveness probe, will be restarted
init container(aws-vpc-cni-init)正常完成(CNI binaries 安裝正常,CNI init container done),問題在主容器 IPAM daemon 啟動後卡在 Checking for IPAM connectivity... 無法連線本地 gRPC port :50051。
第五層(新對話排查結果):
- hop limit:實測確認兩台新節點 hop limit 均為 2(非 AWS 預設的 1),排除 hop limit 為根因
- Security Group:Outbound 全開(0.0.0.0/0),Inbound 含 SG 互通,排除 SG 問題
- IRSA vs node IAM role:aws-node ServiceAccount annotation 為 null,代表使用 node IAM role 而非 IRSA。這意味著 EC2 Node Role(
eksctl-ps-eks-rc-nodegroup-ps-eks--NodeInstanceRole-qOqz40a8sb3x)上是否有AmazonEKS_CNI_Policy附加是關鍵待確認項目
IAM 雙重限制
developer IAM user 缺少:eks:DescribeClusterVersions(eksctl 403)、ec2:DescribeInstances(無法查 ASG)、kubectl get nodes(Forbidden)。繞過方式:改用 aws eks list-nodegroups、透過 CloudFormation StackEvents 查失敗原因、透過 kubectl get pods -n kube-system 觀察 aws-node 狀態。
What Changed¶
舊版假設被推翻:hop limit 不是根因
舊版文檔的結論將 Launch Template hop limit(預設=1)列為最可能的根因,建議建立 nodegroup 時確認 hop limit=2。新對話通過實際查詢確認兩台新節點的 hop limit 均為 2,這個假設已被排除。
Security Group 確認正常
新對話取得了完整的 SG 規則:Outbound 全開(0.0.0.0/0)、Inbound 包含 SG 互通規則,Security Group 不是本次失敗原因。這縮小了排查範圍。
根因指向 IAM Node Role 政策
aws-node ServiceAccount annotation 為 null,確認沒有使用 IRSA,而是依賴 Node Instance Role 的 IAM 政策。IPAM daemon 啟動時需要呼叫 EC2 API 來獲取 ENI 資訊,若 Node Role 缺少 AmazonEKS_CNI_Policy,IPAM daemon 就會卡在啟動階段,導致 :50051 gRPC 服務無法就緒。這是目前最高可信度的根因假設,尚待確認 Node Role 的 Policy 附加狀態。
So What¶
這個五層診斷鏈讓工程師能系統性地排查 EKS node group 建立失敗,特別是 aws-node CNI 相關問題。舊版文檔中的 hop limit 假設是一個常見誤導——很多文章提到容器需要 hop limit=2,但實際上 eksctl 建立的 nodegroup 通常已設定正確值。真正容易被忽略的是 Node IAM Role 的 Policy 附加狀態,特別是 AmazonEKS_CNI_Policy,這在使用 node role(非 IRSA)時是必要條件。
此案例對未來遷移的最大價值:不要停留在「hop limit 可能有問題」就結束排查,要同時確認 hop limit、SG、IAM Policy 三個面向,才能精確定位根因。
Trade-offs¶
- 藍綠切換 vs 就地升級:藍綠方式安全但需短暫雙倍節點成本;就地升級省成本但風險高
- IRSA vs node IAM role:IRSA 更安全且符合最小權限原則,但 node IAM role 設定更簡單;使用 node role 時 AmazonEKS_CNI_Policy 必須附加在 role 上
- eksctl vs aws cli:eksctl 指令直覺但需較多 IAM 權限;純 aws cli 適合受限 developer 帳號
- Auto Mode 開/關:Auto Mode 方便日常彈性,手動 ng 操作時會干擾,遷移期間應關閉
- 等待 vs 主動診斷:建立後 5-10 分鐘即可檢查 aws-node probe 狀態,無需等待 20 分鐘超時
- hop limit 假設的代價:在未實測前就採納 hop limit 假設會浪費時間修改設定;應優先實測確認再行動
Try It Fast¶
# 環境設定
export AWS_PROFILE=liveops
export AWS_REGION=ap-east-2
export CLUSTER=ps-eks-rc
OLD_NG=ps-eks-rc-ng-private-managed-1
NEW_NG=ps-eks-rc-ng-t3xlarge
# 前置:確認 Auto Mode 已關閉,無 DELETING 中的 ng
aws eks list-nodegroups --cluster-name $CLUSTER --region $AWS_REGION
# ---- 建立前:驗證 Node Role 有 AmazonEKS_CNI_Policy ----
NODE_ROLE=$(aws eks describe-nodegroup --cluster-name $CLUSTER \
--nodegroup-name $OLD_NG --region $AWS_REGION \
--query 'nodegroup.nodeRole' --output text)
ROLE_NAME=$(basename $NODE_ROLE)
aws iam list-attached-role-policies --role-name $ROLE_NAME \
--query 'AttachedPolicies[].PolicyName'
# 確認輸出中包含 AmazonEKS_CNI_Policy
# Step 1:建立新 t3.xlarge node group
aws eks create-nodegroup \
--cluster-name $CLUSTER \
--nodegroup-name $NEW_NG \
--node-role $NODE_ROLE \
--subnets $(aws eks describe-nodegroup --cluster-name $CLUSTER \
--nodegroup-name $OLD_NG --region $AWS_REGION \
--query 'nodegroup.subnets' --output text) \
--instance-types t3.xlarge \
--scaling-config minSize=2,maxSize=8,desiredSize=2 \
--ami-type AL2023_x86_64_STANDARD \
--disk-size 100 \
--region $AWS_REGION
# ---- 關鍵早期診斷(建立後 5-10 分鐘執行)----
# 正常:2/2 Running;異常:1/2 Running 有 restart → CNI 主容器失敗
kubectl get pods -n kube-system -l k8s-app=aws-node -o wide
# 若出現 1/2 Running,依序排查:
# 1. 查 probe 失敗訊息(關鍵:是否有 ":50051" 錯誤)
kubectl describe pod -n kube-system <aws-node-pod-name> | grep -A 3 'Unhealthy\|Liveness\|Readiness'
# 2. 確認 init container 正常(應看到 "CNI init container done")
kubectl logs -n kube-system <aws-node-pod-name> -c aws-vpc-cni-init --tail=20
# 3. 若 init 正常但主容器連線 :50051 失敗,依序確認:
# 3a. 確認 hop limit(應為 2)
aws ec2 describe-instances --region $AWS_REGION \
--filters "Name=tag:eks:nodegroup-name,Values=$NEW_NG" \
--query 'Reservations[].Instances[].MetadataOptions.{Id:InstanceId,HopLimit:HttpPutResponseHopLimit}'
# 3b. 確認 aws-node ServiceAccount 是否有 IRSA annotation
kubectl get serviceaccount -n kube-system aws-node -o jsonpath='{.metadata.annotations}'
# 3c. 若 annotation 為 null(使用 node role),確認 Node Role 有 AmazonEKS_CNI_Policy
aws iam list-attached-role-policies --role-name $ROLE_NAME \
--query 'AttachedPolicies[].PolicyName'
# ---- CREATING 超時診斷(> 20 分鐘)----
# 查 CloudFormation stack(不需要 ec2 權限)
# 注意:JMESPath 必須寫在同一行,不能換行!
STACK_NAME=$(aws cloudformation list-stacks --region $AWS_REGION --query "StackSummaries[?contains(StackName,'t3xlarge')].StackName" --output text | head -1)
aws cloudformation describe-stack-events --stack-name $STACK_NAME --region $AWS_REGION --query "StackEvents[?ResourceStatus=='CREATE_FAILED'].{Resource:LogicalResourceId,Reason:ResourceStatusReason}" --output table
# Step 3:確認 aws-node 全部 2/2 Running 才繼續(不是 1/2)
kubectl get pods -n kube-system -o wide
# Step 4-5:Cordon + Drain 舊 node
for node in $(kubectl get nodes -l eks.amazonaws.com/nodegroup=$OLD_NG -o name); do kubectl cordon $node; done
for node in $(kubectl get nodes -l eks.amazonaws.com/nodegroup=$OLD_NG -o name); do kubectl drain $node --ignore-daemonsets --delete-emptydir-data; done
# Step 6-8:確認服務正常後刪舊 ng
kubectl get pods --all-namespaces | grep -v Running | grep -v Completed
aws eks delete-nodegroup --cluster-name $CLUSTER --nodegroup-name $OLD_NG --region $AWS_REGION
Recommendation¶
- 遷移前先關閉 EKS Auto Mode,避免縮舊 ng 時觸發非預期節點擴展
- 建立前先驗證 Node Role 有 AmazonEKS_CNI_Policy:
aws iam list-attached-role-policies --role-name <NODE_ROLE_NAME>,aws-node 使用 node role(非 IRSA)時這個 policy 是必要條件 - 確認無殘留的 DELETING ng 後再建立新 ng
- hop limit 無需特別設定:eksctl 建立的 nodegroup 通常已設定 hop limit=2,無需手動調整,但仍可以確認
- 建立後 5-10 分鐘即可早期診斷:
kubectl get pods -n kube-system -l k8s-app=aws-node -o wide - aws-node 診斷五層優先順序:½ Running → probe 訊息(:50051)→ init container 確認 → hop limit 實測 → IAM Node Role Policy
- init container 正常不等於 CNI 正常:aws-vpc-cni-init 完成只代表 binary 安裝,主容器 IPAM daemon 還需要 EC2 API 存取
- 新 ng 的 aws-node 必須全部 2/2 Running 才能開始 Drain 舊節點
- CREATING 超過 20 分鐘時,優先查 CloudFormation StackEvents(無需 ec2 權限)
- CloudFormation JMESPath query 不能換行,所有 --query 參數必須寫在同一行
- 使用 aws cli 而非 eksctl,developer 帳號通常缺少 eks:DescribeClusterVersions 權限
- Health.issues 空陣列不代表萬事 OK:真正的失敗在 CloudFormation StackEvents