跳轉到

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

  1. 將完整操作流程寫成自動化腳本,不要手動執行「擴充 node → label → taint」——這三個動作之間的時間差會讓 CA 有機會砍掉新 node(第一次失敗的教訓)
  2. 使用雙重 CA 保護:PDB(minAvailable=1)+ safe-to-evict=false annotation;只靠 PDB 時 CA 仍週期性嘗試 drain,影響 QA 測試穩定性
  3. 同步更新 CI/CD 部署腳本(DeployServer.groovy),確保後續 deploy 自動帶入 nodeSelector 與 annotation,否則隔離效果在下次 CI/CD 觸發後失效
  4. PDB YAML 必須存為獨立檔案再 apply,避免 heredoc 模式縮排問題;metadata 下的 name/namespace 需 2-space 縮排,selector 下的 matchLabels 嚴格對齊
  5. label key 必須三者一致:node label、nodeSelector(或 NodeAffinity)、PDB selector 都用 site=qa-test;不一致會讓 PDB Current 顯示 0,形同虛設
  6. kubectl describe pdb 驗證 Current 欄位 > 0,確認 PDB selector 正確命中 pod,不是只看 PDB 存在
  7. 不要調整 Node Group minSize 來保護 qa node,讓 CA 自由伸縮,靠 safe-to-evict + PDB 機制阻擋——調整 minSize 會破壞 CA 的動態伸縮能力
  8. 操作腳本統一放置於 base_helm/AWS_EKS/ 目錄,腳本建立時自動清除 \r\n Windows 換行符
  9. **監控 CA 日誌**確認 CA 確實因 safe-to-evict 跳過 qa-test node,而非其他原因

本文檔由 Semi-Brain 自動生成

Session ID: 6faf05ec-5823-4354-9fcd-f9c48bb322ca

分析信心度: 96%