
Terraform + Karpenter:EKS 1.34 升級到 1.35 的完整流程
如果 EKS 的控制平面、Managed Add-ons、NodePool 與 EC2NodeClass 都由 Terraform 管理,從 Kubernetes 1.34 升級到 1.35 可以拆成可觀測、可審查、可回滾的流程:先確認 Add-on 相容性,再升級 Control Plane 與 Add-ons,最後讓 Karpenter 以 Drift 完成 Data Plane 的節點輪替。
本文假設:叢集已使用 Terraform 管理 aws_eks_cluster 與 aws_eks_addon,工作節點由 Karpenter 建立,且部署流程能在升級期間暫停一般基礎設施變更。文中的 Add-on 版本是 1.35 的範例值;實際套用前,仍應以 AWS 在目標區域回傳的相容版本為準。
升級前先定義成功條件
升級不應只看 terraform apply 是否成功。建議在變更單中先記錄以下基線,完成後逐一比對:
- 控制平面升級完成,EKS API 可正常回應,且無未處理的 AWS health issue。
- 所有受 Karpenter 管理的節點都使用 Kubernetes 1.35 相容的 AMI 與作業系統。
- CoreDNS、kube-proxy、VPC CNI 的 Deployment/DaemonSet 全部 Ready。
- 應用程式的錯誤率、延遲、重啟次數與 Pending Pod 數量沒有超過既定 SLO。
- 舊節點完成 drain 並被移除;若升級中止,能依照變更單恢復到上一個已驗證版本。
先檢查目前版本與 Karpenter 資源:
kubectl version
kubectl get nodes -o wide
kubectl get nodepool,ec2nodeclass
kubectl get pods -n kube-system -o wide
1. 先檢查 Managed Add-ons 是否支援 1.35
不要先升級控制平面,再被動處理 Add-on 相容性。先查詢目標區域提供的版本,以及每個版本支援的 Kubernetes 版本:
for addon in coredns kube-proxy vpc-cni; do
echo "=== ${addon} ==="
aws eks describe-addon-versions \
--addon-name "${addon}" \
--kubernetes-version 1.35 \
--query 'addons[0].addonVersions[].{version:addonVersion,compatibility:compatibilities[].clusterVersion}' \
--output table
done
同時記錄目前叢集實際使用的版本:
for addon in coredns kube-proxy vpc-cni; do
aws eks describe-addon \
--cluster-name your-eks-cluster \
--addon-name "${addon}" \
--query 'addon.{name:addonName,version:addonVersion,status:status}' \
--output table
done
如果目前版本不支援 Kubernetes 1.35,必須先在叢集仍為 1.34 時,將 Add-on 升級到同時支援 1.34 與 1.35 的版本。不要直接把不相容的版本留到控制平面升級完成後才處理;若查不到同時支援兩個版本的版本,應依 AWS 公布的升級路徑先升到可支援 1.35 的中間版本。
這個階段只變更 Add-on 版本與必要設定,不要同時修改 NodePool 或 EC2NodeClass,讓問題容易定位。先執行 Terraform plan,確認只包含預期的 Add-on 變更,再分批套用並觀察 CoreDNS、kube-proxy 與 VPC CNI:
terraform plan -out=eks-135-addon-compatibility.tfplan
terraform apply eks-135-addon-compatibility.tfplan
kubectl get pods -n kube-system -o wide
2. 透過 Terraform 升級 EKS Control Plane
在 aws_eks_cluster 將 version 從 1.34 改成 1.35。其他網路、IAM、控制平面端點與 logging 設定保持不變,讓這次變更只包含版本升級。
resource "aws_eks_cluster" "cluster" {
name = "your-eks-cluster"
role_arn = aws_iam_role.cluster.arn
version = "1.35"
vpc_config {
subnet_ids = var.private_subnet_ids
endpoint_private_access = true
endpoint_public_access = true
}
}
先執行 plan,確認 Terraform 沒有把與版本無關的資源列為變更:
terraform plan -out=eks-135-control-plane.tfplan
terraform apply eks-135-control-plane.tfplan
實際時間會受區域與 AWS 當下狀態影響;不要把「約 6 到 7 分鐘」當成 SLA。升級期間持續觀察 EKS 控制台與 CloudTrail,完成後重新驗證 API:
aws eks describe-cluster \
--name your-eks-cluster \
--query 'cluster.{status:status,version:version,health:health}'
kubectl get --raw=/readyz?verbose
3. 透過 Terraform 更新 Managed Add-ons
控制平面完成後,確認 Add-on 已經處於可支援 1.35 的版本,再更新 CoreDNS、kube-proxy 與 VPC CNI。以下版本是 EKS 1.35 的範例設定:
resource "aws_eks_addon" "core_dns" {
cluster_name = aws_eks_cluster.cluster.name
addon_name = "coredns"
addon_version = "v1.13.1-eksbuild.1"
resolve_conflicts_on_update = "OVERWRITE"
}
resource "aws_eks_addon" "kube_proxy" {
cluster_name = aws_eks_cluster.cluster.name
addon_name = "kube-proxy"
addon_version = "v1.35.0-eksbuild.2"
resolve_conflicts_on_update = "OVERWRITE"
}
resource "aws_eks_addon" "vpc_cni" {
cluster_name = aws_eks_cluster.cluster.name
addon_name = "vpc-cni"
addon_version = "v1.21.1-eksbuild.1"
resolve_conflicts_on_update = "OVERWRITE"
}
resolve_conflicts_on_update = "OVERWRITE" 會以 Terraform 宣告的設定覆蓋 Add-on 上的衝突欄位。若 AWS provider 版本較舊,欄位可能仍是 resolve_conflicts;不要直接混用兩種欄位,應先確認目前 provider schema,再升級 provider 或沿用該版本支援的寫法。
套用前查詢目標區域實際可用的版本:
aws eks describe-addon-versions \
--addon-name coredns \
--kubernetes-version 1.35 \
--query 'addons[].addonVersions[].addonVersion'
aws eks describe-addon-versions \
--addon-name kube-proxy \
--kubernetes-version 1.35 \
--query 'addons[].addonVersions[].addonVersion'
aws eks describe-addon-versions \
--addon-name vpc-cni \
--kubernetes-version 1.35 \
--query 'addons[].addonVersions[].addonVersion'
完成後確認 Add-on 狀態是 ACTIVE,並檢查系統 Pod 是否有 CrashLoopBackOff、ImagePullBackOff 或長時間 Pending:
aws eks describe-addon --cluster-name your-eks-cluster --addon-name coredns
aws eks describe-addon --cluster-name your-eks-cluster --addon-name kube-proxy
aws eks describe-addon --cluster-name your-eks-cluster --addon-name vpc-cni
kubectl get pods -n kube-system -o wide
4. 讓 Karpenter 安全輪替 Data Plane
Karpenter 的價值在於依照 NodePool 與 EC2NodeClass 自動建立、封鎖與終止節點。不過要注意:控制平面版本變更本身,不保證會讓既有節點自動 Drift。必須讓 Karpenter 觀察到 NodePool/EC2NodeClass template 的有效差異,例如更新 AMI alias、AMI ID、UserData 或其他會反映到 NodeClaim 的欄位。
4.1 先確認 cgroup v2 與 AMI
EKS 1.35 升級前,應確認節點映像與工作負載支援 cgroup v2。AL2023 與 Bottlerocket 是常見選擇;若使用自訂 AMI,請在預備環境驗證 kubelet、container runtime、監控 agent 與安全 agent 的相容性。
如果使用 amiSelectorTerms 固定 AMI ID,必須把它更新成 EKS 1.35 相容 AMI。若使用可追蹤最新版本的 alias,則由 AWS/Karpenter 選取新 AMI,但仍應先在 staging 驗證而不是直接依賴自動漂移。
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: al2023
spec:
amiFamily: AL2023
amiSelectorTerms:
- alias: al2023@latest
role: KarpenterNodeRole-your-eks-cluster
subnetSelectorTerms:
- tags:
kubernetes.io/cluster/your-eks-cluster: owned
securityGroupSelectorTerms:
- tags:
kubernetes.io/cluster/your-eks-cluster: owned
---
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: general
spec:
template:
spec:
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: al2023
requirements:
- key: kubernetes.io/arch
operator: In
values: [amd64]
- key: karpenter.sh/capacity-type
operator: In
values: [on-demand]
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 1m
al2023@latest 適合由平台團隊統一控管的自動化環境;若變更審批需要完全可重現,請改用已驗證的 AMI ID,並在每次升級時明確更新。無論採用哪種方式,都要把變更送進 Terraform/Git review,避免手動修改造成 drift。
4.2 先建立避免服務中斷的條件
節點輪替是否會造成服務中斷,關鍵不在於「有沒有 Karpenter」,而在於新節點是否有足夠容量,以及工作負載是否允許安全驅逐。建議在正式輪替前完成以下設定:
- 預留額外容量:先確認 EC2 service quota、子網路 IP、Spot/On-Demand 配額與 NodePool 限制,至少要能同時容納新舊節點一段時間。若叢集沒有多餘容量,Karpenter 可能無法先建立新節點。
- 工作負載至少兩個副本:單一副本服務無法在 drain 期間保證不中斷;StatefulSet 則要先確認儲存系統、拓樸與重新掛載行為。
- 設定 PDB:讓 Kubernetes 一次只允許有限數量的 Pod 被驅逐。PDB 不應設成完全禁止驅逐,否則升級可能永遠卡住。
- 分散副本:使用
topologySpreadConstraints或反親和性,避免同一服務的所有副本集中在同一個節點或可用區。 - 先確認新節點 Ready:在允許舊節點 drain 前,確認新 NodeClaim 已註冊、
Ready=True,且必要的 DaemonSet 已完成部署。
一個三副本無狀態服務的最低可用性設定如下:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: api
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 3
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: api
containers:
- name: api
image: example.com/api:1.0.0
ports:
- containerPort: 8080
也可以在 NodePool 設定中限制同時進行的中斷數量,避免多個節點一次被替換:
disruption:
budgets:
- nodes: "10%"
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 1m
套用後先觀察新節點與服務狀態,再讓 Drift 逐步處理舊節點:
kubectl get nodeclaims -o wide
kubectl get nodes
kubectl get pdb -A
kubectl get pods -A -o wide
不要使用 kubectl delete node --force 或直接刪除 EC2 來加速流程,這會繞過正常的 eviction 與 PDB 保護。對於需要長時間處理的工作,才考慮使用 karpenter.sh/do-not-disrupt: "true",而且必須搭配明確的解除條件,不能把它套用到所有 Pod。
4.3 觸發並觀察 Drift
當 EC2NodeClass 或 NodePool 的 template 變更後,Karpenter 會將不符合新 template 的 NodeClaim 標示為 Drifted,並依照 disruption policy 建立新節點、封鎖舊節點、驅逐 Pod,最後終止舊節點。先確認新 NodeClaim 已取得正確 AMI,再允許大規模替換:
kubectl get nodeclaims -o wide
kubectl get nodes -L karpenter.sh/nodepool
kubectl get events -A --sort-by=.lastTimestamp | tail -50
kubectl logs -n kube-system -l app.kubernetes.io/name=karpenter --since=15m
升級期間要特別檢查 PodDisruptionBudget、單一副本服務、StatefulSet、DaemonSet 與使用本地 ephemeral storage 的工作負載。PDB 過度嚴格時,舊節點可能無法 drain;這時應修正工作負載的可用性設定,而不是強制刪除節點。
5. 升級後驗證
Kubernetes 與節點版本
kubectl version
kubectl get nodes \
-o custom-columns='NAME:.metadata.name,VERSION:.status.nodeInfo.kubeletVersion,OS:.status.nodeInfo.osImage,READY:.status.conditions[?(@.type=="Ready")].status'
確認所有應納管的節點都已是 v1.35.x,且沒有遺留的 1.34 節點。若仍有舊節點,先找出它是 Managed Node Group、手動節點還是未被 Karpenter 管理,再決定是否需要獨立輪替。
系統元件與應用程式
kubectl get pods -n kube-system -o wide
kubectl get ds,deploy -A
kubectl get nodes --no-headers | awk '$2 !~ /Ready/ {print}'
最後以實際流量做 smoke test,並在監控中確認 API latency、5xx、DNS resolution、Pod restart、Pending Pod 與 NodeNotReady 沒有異常升高。只有 kubectl get nodes 綠燈,不能代表升級已經完成。
6. 失敗處理與回滾策略
EKS Control Plane 的 minor version 升級通常不可直接降版,因此「回滾」應拆成兩層:
- 控制平面失敗:停止後續 Data Plane 變更,保留 AWS/EKS health、CloudTrail 與 Terraform plan,依 AWS 支援流程處理。不要用 Terraform 把
version改回 1.34 當作通用回滾手段。 - Add-on 或節點失敗:把已驗證的 Add-on 版本、AMI ID、EC2NodeClass 與 NodePool template 還原,停止新節點擴張,保留仍健康的舊節點,完成 workload smoke test 後再繼續替換。
每次升級都應保存以下證據:Terraform plan、Add-on describe 結果、NodeClaim/Node 狀態、Karpenter controller log、升級前後的 SLO 指標。這些資料能讓團隊判斷是控制平面、網路外掛程式、AMI、kubelet 還是工作負載本身造成異常。
結語
Terraform 負責把 EKS 版本與 Add-on 變更變成可審查的宣告式設定,Karpenter 則負責以受控方式替換 Data Plane。最重要的不是把三段指令一次執行完,而是明確定義 Drift 的觸發條件、確保 PDB 與容量足以支撐滾動更新,並用可量化的 SLO 與回滾證據判斷升級是否真的完成。
