EKS QA 環境 Node 隔離完整實戰紀錄:Taint/Toleration + PDB + safe-to-evict 雙重保護(含兩次嘗試、最終遷移驗證與 CI/CD 腳本整合)¶
文檔資訊
- 分類: architecture
- 難度: advanced
- 預估閱讀時間: 25 分鐘
- 標籤:
EKS,Kubernetes,Cluster Autoscaler,PDB,Node Isolation,Taint,Toleration,QA Environment,eksctl,safe-to-evict,NodeSelector,Migration,CI/CD,DeployServer
摘要¶
在 EKS ps-eks cluster 上為 qa-test 站點建立完整資源隔離的架構設計與兩次實際操作紀錄。第一次嘗試(ip-192-168-124-249)因為 Taint 未及時設定而被 CA 刪除;第二次(ip-192-168-103-174)透過自動化腳本成功完成,所有 5 個 qa-test pods 完全遷移並隔離。最終加入 safe-to-evict=false annotation 作為雙重保護,並調整 DeployServer.groovy 確保後續部署自動套用 qa-test 專屬配置。
關鍵學習¶
-
新 node 起來後必須「立即」加 Taint,否則 CA 可能在判斷低利用率後直接縮減(第一次失敗的原因)
-
CA 保護需要雙重機制:PDB(阻止 eviction)+ safe-to-evict=false annotation(告訴 CA 連嘗試都不要)
-
nodeSelector 是比 NodeAffinity 更簡單的遷移手段:patch deployment 後 rolling update 自動將 pod 移到目標 node
-
eksctl scale nodegroup 是擴充節點數的正確指令(eksctl update nodegroup 在 v0.209.0 完全不支援 --cluster 旗標)
-
PDB YAML 必須存為獨立檔案再 apply,heredoc 模式容易因縮排問題(tab/space 混用)導致 YAML 解析失敗
-
Windows 環境下編寫的 shell script 含 \r\n 換行符,腳本建立時需自動執行 sed -i 's/\r$//' 清除
-
qa-test node 的資源利用率(cpu 19%、memory 7%)遠低於 CA 縮減門檻,若無 safe-to-evict 保護必定被縮減
-
Pending pods 如果之前已有 nodeSelector 但 node 消失,會一直 Pending 直到自然過期,舊的 Running pod 不受影響可繼續服務
-
CI/CD 部署腳本(DeployServer.groovy)需同步更新,確保後續 qa-test 站點的 deploy 自動帶入 nodeSelector 與 annotation
-
操作腳本統一放置於 base_helm/AWS_EKS/ 目錄
技術細節¶
環境規格
- Cluster:
ps-eks,Node Group:ng-private-managed-1,Instance Type:m5.2xlarge - 執行前狀態:min=2,max=8,desired=3,共 3 nodes
- qa-test pods:game(x2)、global(x1)、ps-func-web(x1)、sink(x1) 共 5 個,分散於 3 nodes
第一次嘗試失敗原因(ip-192-168-124-249)
手動執行 eksctl scale nodegroup --nodes=4 擴充 node(28 秒內 Ready),加了 site=qa-test label 但「沒有及時設定 Taint」。CA 偵測到這台 node 資源利用率極低(只有 daemonset pods),在 Taint 設定前就將其縮減刪除。這揭示了一個關鍵操作時序要求:擴充 node → label → Taint 三個動作必須連貫執行,不能有間隔。
第二次成功執行流程(ip-192-168-103-174)
setup_qa_test_node.sh 依序執行:
1. eksctl scale nodegroup --cluster=ps-eks --name=ng-private-managed-1 --nodes=4
2. 等待新 node 進入 Ready 狀態(約 18 秒)
3. 立即加 label:kubectl label node <node> site=qa-test
4. 立即加 Taint:kubectl taint nodes <node> dedicated=qa-test:NoSchedule(關鍵:必須在 CA 縮減前完成)
5. Apply PDB(存為 YAML 檔,避免 heredoc 縮排問題)
migrate_qa_test_pods.sh 依序執行:
1. 找出所有 qa-test deployments(共 4 個)
2. Patch nodeSelector:kubectl patch deployment -n ps <name> -p '{"spec":{"template":{"spec":{"nodeSelector":{"site":"qa-test"}}}}}'
3. Patch safe-to-evict annotation:cluster-autoscaler.kubernetes.io/safe-to-evict: "false"
4. Rolling update 自動觸發,新 pod 調度到新 node,舊 pod 依序終止
CA 雙重保護機制
方案 A(主動防禦): safe-to-evict=false annotation
→ CA 在分析 node 時直接跳過帶有此 annotation 的 pod
→ CA 不會嘗試 drain 此 node
方案 B(被動防禦): PDB minAvailable=1
→ 若 CA 仍嘗試 drain,eviction 請求被 PDB 拒絕
→ drain 失敗,CA 退縮
只有 PDB 時,CA 仍會週期性嘗試 drain(短暫 disruption 風險);加上 safe-to-evict=false 後,CA 連嘗試的動作都不會發生。
最終驗證結果(final_verification.sh)
Node: ip-192-168-103-174.ap-southeast-1.compute.internal
qa-test pods 位置:
總 pods: 5 / 在新 node: 5 / 在舊 nodes: 0 ✅
nodeSelector 驗證:
game-qa-test-ps-server: {"site":"qa-test"} ✅
global-qa-test-ps-server: {"site":"qa-test"} ✅
ps-func-web-qa-test-ps-web: {"site":"qa-test"} ✅
sink-qa-test-ps-server: {"site":"qa-test"} ✅
safe-to-evict annotation: false ✅
PDB: minAvailable=1, Allowed disruptions=4 ✅
Node 資源利用:
cpu: 1580m (19%) ← CA 縮減候選(無保護必被砍)
memory: 2282Mi (7%) ← CA 縮減候選(無保護必被砍)
CI/CD 腳本整合(新增,舊文檔未涵蓋)
遷移完成後,需同步更新 DeployServer.groovy(lines 85-91),加入 qa-test 專屬配置,確保後續透過 CI/CD pipeline 部署的 qa-test 站點自動帶入:
- nodeSelector: site=qa-test(確保新部署的 pod 調度到專屬 node)
- safe-to-evict=false annotation(確保 CA 保護延續)
若不更新部署腳本,下次 CI/CD 觸發的 deploy 會覆蓋 nodeSelector,導致 qa-test pod 重新散佈到所有 nodes,隔離失效。
What Changed¶
第一次嘗試失敗後的修正
第一次手動執行時,新 node ip-192-168-124-249 在 label 設定後未及時加 Taint,約數分鐘後被 CA 偵測為低利用率 node 並刪除(kubectl describe node 回傳 NotFound)。確認核心問題在於操作時序:擴充 → label → Taint 三步驟不能分開手動執行,必須自動化連貫完成。
新增 CI/CD 腳本整合(相對舊文檔的主要新增內容)
舊版文檔以節點隔離操作為終點。新對話確認還需更新 DeployServer.groovy(lines 85-91),加入 qa-test 專屬配置。這是確保隔離狀態持久化的關鍵步驟——若 CI/CD 部署腳本未更新,任何後續的重新部署都會清除 nodeSelector,使隔離完全失效。
操作腳本完整清單確認
透過 4 支自動化腳本完成全流程:check_qa_test_status.sh(診斷)、setup_qa_test_node.sh(建立專屬 node)、migrate_qa_test_pods.sh(遷移 pods)、final_verification.sh(最終驗證)。所有腳本統一放置於 base_helm/AWS_EKS/ 目錄,且建立時自動清除 Windows \r\n 換行符。
So What¶
這份文檔記錄了從設計討論到兩次實際執行(含一次失敗)再到 CI/CD 整合的完整過程,可直接作為 SOP 使用。
關鍵價值: qa-test node 的資源利用率(cpu 19%、memory 7%)遠低於 CA 縮減門檻,若沒有雙重保護(PDB + safe-to-evict),這台 node 必然在數分鐘到數小時內被 CA 自動刪除——這不是假設,第一次嘗試已經驗證了這個場景。
CI/CD 整合的重要性(新文檔補充): 手動 patch nodeSelector 只是一次性操作。若不同步更新 DeployServer.groovy,每次 CI/CD 觸發的部署都會覆蓋手動設定,使隔離失效。這使得「隔離設計」必須在 Infrastructure 層(node label/taint)和 Application 層(deployment spec)兩個層次同時持久化。
Trade-offs¶
- Taint 方案 vs 獨立 Node Group:Taint 方案管理簡單、成本低,但 node 仍屬同一 ASG,instance type 無法差異化;獨立 Node Group 可以用更小的機型節省成本,但增加管理複雜度
- PDB 單獨保護 vs PDB + safe-to-evict 雙重保護:只有 PDB 時 CA 仍週期性嘗試 drain(有短暫 disruption 風險);雙重保護下 CA 直接跳過此 node,但 safe-to-evict=false 會讓 CA 完全無法回收此 node,即使 qa-test 站點完全閒置也不縮減
- nodeSelector vs NodeAffinity:nodeSelector 語法簡單易於 patch,但只支援完全相等比較;NodeAffinity 支援 In/NotIn 等運算子,語義更明確但 YAML 更複雜
- label key 選擇(site vs dedicated):
site=qa-test語意清晰,與既有 pod label 一致;dedicated=qa-test是 Kubernetes 社群慣例,更能表達「專屬資源」語意;兩者功能相同,但 Taint key、nodeSelector key、PDB selector key 三者必須完全一致 - 手動執行 vs 自動化腳本:手動執行在「擴充 node → 立即加 Taint」之間有時間差,導致 CA 有機會縮減;自動化腳本消除時間差,確保操作原子性
- 只更新 node 配置 vs 同步更新 CI/CD 腳本:只更新 node 配置是臨時狀態,下次 deploy 會被覆蓋;必須同步更新 DeployServer.groovy 才能使隔離持久化
Try It Fast¶
# 0. 確認腳本在正確位置
ls base_helm/AWS_EKS/
# check_qa_test_status.sh setup_qa_test_node.sh migrate_qa_test_pods.sh final_verification.sh
# 1. 建立專屬 node 並設定保護(擴充 node group + label + taint + PDB)
./setup_qa_test_node.sh
# 關鍵:腳本自動執行「擴充 → label → taint」,不能分開手動操作
# 2. 遷移 qa-test pods 到新 node
./migrate_qa_test_pods.sh
# 透過 nodeSelector + safe-to-evict patch,觸發 rolling update
# 3. 最終驗證
./final_verification.sh
# 確認:5/5 pods 在新 node + nodeSelector 已配置 + PDB Current>0
# --- 關鍵指令備忘 ---
# 擴充節點數(注意:是 scale 不是 update,eksctl v0.209.0 update 不支援 --cluster)
eksctl scale nodegroup \
--cluster=ps-eks \
--name=ng-private-managed-1 \
--nodes=4
# 為新 node 加 label 和 taint(兩個動作必須連貫執行,不能間隔)
NODE="ip-192-168-103-174.ap-southeast-1.compute.internal"
kubectl label node $NODE site=qa-test
kubectl taint nodes $NODE dedicated=qa-test:NoSchedule
# 驗證 PDB(確認 Current > 0,代表 selector 正確命中 pod)
kubectl describe pdb qa-test-pdb -n ps
# 清除 Windows 換行符(腳本建立時需自動處理,手動時用此指令)
sed -i 's/\r$//' /path/to/script.sh
# 確認 CI/CD 腳本已更新(DeployServer.groovy lines 85-91 含 qa-test 專屬配置)
grep -n 'qa-test\|nodeSelector\|safe-to-evict' DeployServer.groovy
Recommendation¶
- 將完整操作流程寫成自動化腳本,不要手動執行「擴充 node → label → taint」——這三個動作之間的時間差會讓 CA 有機會砍掉新 node(第一次失敗的教訓)
- 使用雙重 CA 保護:PDB(minAvailable=1)+ safe-to-evict=false annotation;只靠 PDB 時 CA 仍週期性嘗試 drain,影響 QA 測試穩定性
- 同步更新 CI/CD 部署腳本(DeployServer.groovy),確保後續 deploy 自動帶入 nodeSelector 與 annotation,否則隔離效果在下次 CI/CD 觸發後失效
- PDB YAML 必須存為獨立檔案再 apply,避免 heredoc 模式縮排問題;metadata 下的 name/namespace 需 2-space 縮排,selector 下的 matchLabels 嚴格對齊
- label key 必須三者一致:node label、nodeSelector(或 NodeAffinity)、PDB selector 都用
site=qa-test;不一致會讓 PDB Current 顯示 0,形同虛設 - 用
kubectl describe pdb驗證 Current 欄位 > 0,確認 PDB selector 正確命中 pod,不是只看 PDB 存在 - 不要調整 Node Group minSize 來保護 qa node,讓 CA 自由伸縮,靠 safe-to-evict + PDB 機制阻擋——調整 minSize 會破壞 CA 的動態伸縮能力
- 操作腳本統一放置於 base_helm/AWS_EKS/ 目錄,腳本建立時自動清除 \r\n Windows 換行符
- **監控 CA 日誌**確認 CA 確實因 safe-to-evict 跳過 qa-test node,而非其他原因