CloudNet@ Team Study/EKS Workshop 4th Cohort

In-place EKS Cluster Upgrades 1.30 → 1.31

"Everything about infra" 2026. 5. 9. 18:43
포스팅은 CloudNet@팀 서종호(Gasida)님이 진행하시는
AWS EKS Workshop Study 내용을 참고하여 작성합니다.

 

이번 강의는 AWS 정영준님께서

EKS 클러스터 업그레이드의 모범 사례에 대해서

열강 해주셨는데요, 강의 내용을 기반으로 포스팅해보려 합니다.

 

 

 

01. EKS Control Plane Upgrade: 1.30 → 1.31

컨트롤 플레인 업그레이드 시 AWS 내부 동작

업그레이드 요청을 보내면 AWS가 내부적으로 컨트롤 플레인 교체를 자동으로 처리합니다.

이 과정은 사용자에게 노출되지 않지만 내부적으로는 Blue/Green 방식으로 동작합니다.

 

정상 업그레이드 흐름

  • 사전 점검 : EKS Upgrade Insights를 통해 Deprecated API 사용 여부, 컴포넌트 버전 skew 등 업그레이드를 블록할 수 있는 문제가 있는지 자동으로 확인합니다.
  • 신규 컨트롤 플레인 프로비저닝 : 새 버전(1.31)의 컨트롤 플레인 컴포넌트를 새로 프로비저닝합니다. API 서버 엔드포인트는 이 시점에 새 컨트롤 플레인으로 전환됩니다.
  • 기존 컨트롤 플레인 종료 : 새 컨트롤 플레인이 정상 동작을 확인한 후 기존 1.30 컨트롤 플레인 컴포넌트를 종료합니다.

업그레이드 실패 시 흐름

  • 장애 감지 : AWS가 업그레이드 진행 중 지속적으로 모니터링하다가 문제가 생기면 즉시 중단합니다.
  • 신규 컨트롤 플레인 종료 : 새로 프로비저닝된 1.31 컨트롤 플레인 컴포넌트를 종료합니다.
  • 기존 컨트롤 플레인 유지 : 기존 1.30 컨트롤 플레인이 그대로 유지되기 때문에 실행 중인 워크로드에 영향X
이 내부 메커니즘 덕분에 컨트롤 플레인 업그레이드는 실패해도 자동으로 롤백됩니다.
다만, 이건 컨트롤 플레인에 한정된 이야기입니다. 이후 노드그룹 업그레이드 단계에서 문제가 발생하면
자동 롤백이 되지 않으므로, 데이터 플레인 업그레이드 전 사전 검증이 더욱 중요합니다.

 

업그레이드 방법 비교

컨트롤 플레인 업그레이드를 트리거하는 방법은 4가지가 있습니다.

eksctl, aws console, aws cli, terraform이 있으며 이번 실습에서는 terraform을 사용합니다.

 

방법1. eksctl

eksctl upgrade cluster --name $EKS_CLUSTER_NAME --approve

--version 플래그로 대상 버전을 지정할 수 있지만, 허용되는 값은 현재 버전 또는

한 버전 높은 버전뿐입니다. 한 번에 두 개 이상 마이너 버전 업그레이드는 지원되지 않습니다.

 

방법2. AWS console

EKS 콘솔에서 클러스터 선택 → Upgrade now → 대상 버전 선택 후 Update.

 

방법3. aws cli

aws eks update-cluster-version \
  --region ${AWS_REGION} \
  --name $EKS_CLUSTER_NAME \
  --kubernetes-version 1.31

업그레이드 상태는 반환된 update ID로 확인할 수 있습니다.

aws eks describe-update \
  --region ${AWS_REGION} \
  --name $EKS_CLUSTER_NAME \
  --update-id <update-id>

 

방법4. Terraform (이번 실습 방법)

이번 실습 환경은 처음부터 Terraform으로 프로비저닝된 클러스터입니다.

클러스터 생성과 동일하게 variables.tf 수정 → terraform apply 흐름으로 업그레이드를 진행합니다.

 

eksctl이나 AWS CLI로 직접 업그레이드하면 실제 클러스터 상태와 Terraform state 사이에 버전 불일치가 생김.

이 경우 다음 terraform apply 시 Terraform이 현재 상태(1.31)와 코드상 desired 상태(1.30)의 차이를 감지하고
다운그레이드를 시도하면서 에러가 발생합니다.
처음부터 Terraform으로 일관되게 관리하는 것이 state drift를 방지하는 올바른 방법입니다.

 

📌 컨트롤 프레인 업그레이드 전 사전 준비:

 

1) 현재 파드 컨테이너 이미지 버전 스냅샷

업그레이드 전후 비교를 위해 현재 클러스터에서 실행 중인 모든 파드의 컨테이너 이미지 목록을 파일로 저장!

kubectl get pods --all-namespaces \
  -o jsonpath="{.items[*].spec.containers[*].image}" \
  | tr -s '[[:space:]]' '\n' | sort | uniq -c > 1.30.txt

저장 내용을 확인하면 EKS 관리형 컴포넌트와 워크로드 이미지 버전이 모두 포함되어 있습니다.

6  602401143452.dkr.ecr.us-west-2.amazonaws.com/amazon-k8s-cni:v1.21.1-eksbuild.7
6  602401143452.dkr.ecr.us-west-2.amazonaws.com/amazon/aws-network-policy-agent:v1.3.2-eksbuild.2
8  602401143452.dkr.ecr.us-west-2.amazonaws.com/eks/aws-ebs-csi-driver:v1.58.0
2  602401143452.dkr.ecr.us-west-2.amazonaws.com/eks/coredns:v1.11.4-eksbuild.32
6  602401143452.dkr.ecr.us-west-2.amazonaws.com/eks/kube-proxy:v1.30.14-eksbuild.28
...
1  public.ecr.aws/aws-containers/retail-store-sample-ui:0.4.0

이 파일이 기준점이 됩니다.

컨트롤 플레인 업그레이드 후 동일한 명령으로 1.31.txt를 만들어 diff로 비교할 예정입니다.

 

2) 서비스 가용성 모니터링 세팅

업그레이드 진행 중 UI 서비스가 중단 없이 응답하는지 확인하기 위해 반복 호출을 걸어둡니다.

컨트롤 플레인 업그레이드는 노드를 건드리지 않아 이론상 워크로드에 영향이 없어야 하는데,

실제로 그런지 눈으로 검증하는 과정입니다.

export UI_WEB=$(kubectl get svc -n ui ui-nlb \
  -o jsonpath='{.status.loadBalancer.ingress[0].hostname}'/actuator/health/liveness)

# 서비스 응답 상태 반복 확인
while true; do curl -s $UI_WEB; echo; date; sleep 1; echo; done

동시에 클러스터 메타데이터도 polling합니다.

version 필드가 1.30에서 1.31로 바뀌는 시점을 실시간으로 관찰할 수 있습니다.

while true; do
  date
  aws eks describe-cluster --name $EKS_CLUSTER_NAME \
    | egrep 'version|endpoint"|issuer|platformVersion'
  echo; sleep 2
done

업그레이드 전 초기 상태값을 미리 기록해둡니다.

특히 endpoint와 issuer 값이 업그레이드 후에도 동일하게 유지되는지가 핵심 관찰 포인트입니다.

"version": "1.30",
"endpoint": "https://54DD64FB555C83B9230C7D681410714E.sk1.us-west-2.eks.amazonaws.com",
"issuer": "https://oidc.eks.us-west-2.amazonaws.com/id/54DD64FB555C83B9230C7D681410714E"
"platformVersion": "eks.65",

 


Terraform으로 컨트롤 플레인 업그레이드 실행

Step1. Terraform 상태 동기화

Terraform 디렉토리로 이동해 현재 state를 최신으로 동기화합니다.

cd ~/environment/terraform
terraform state list

 

Step2. cluster_version 변경

variables.tf에서 cluster_version 값을 "1.30"에서 "1.31"로 변경합니다.

# variables.tf
variable "cluster_version" {
  description = "EKS cluster version."
  type        = string
  default     = "1.31"   # 1.30 → 1.31
}

 

Step3. terraform plan으로 변경 범위 확인

apply 전에 plan으로 어떤 리소스가 영향을 받는지 확인합니다.

terraform plan -no-color > plan-output.txt

plan 결과를 보면 aws_eks_cluster 리소스의 version 속성 변경이 확인됩니다.

중요한 점은 Terraform 파일에 노드그룹의 특정 AMI나 버전이 명시되어 있지 않은 경우,
컨트롤 플레인 버전 변경이 연쇄적으로 관련 Addon 업데이트까지 plan에 포함된다는 것입니다.
이번 실습에서는 컨트롤 플레인 업그레이드만 먼저 보는 단계이므로, plan 내용을 충분히 검토한 뒤 진행합니다.

 

Step4. terraform apply

terraform apply -auto-approve

업그레이드까지 약 10분정도 소요됩니다.

 

업그레이드 결과 확인

Step1. 컨트롤 플레인 버전 확인

aws eks describe-cluster --name $EKS_CLUSTER_NAME \
  | egrep 'version|endpoint"|issuer|platformVersion'
"version": "1.31",
"endpoint": "https://54DD64FB555C83B9230C7D681410714E.sk1.us-west-2.eks.amazonaws.com",
"issuer": "https://oidc.eks.us-west-2.amazonaws.com/id/54DD64FB555C83B9230C7D681410714E"
"platformVersion": "eks.X",

version이 1.31로 변경된 것을 확인할 수 있습니다.

 

여기서 중요한 관찰 포인트는 endpoint와 OIDC issuer가 업그레이드 전과 완전히 동일하다는 점입니다.

In-Place 업그레이드에서는 동일한 클러스터를 그대로 올리기 때문에 클러스터 식별자가 바뀌지 않습니다.
이 말은 IRSA를 사용하는 컴포넌트들(ALB Controller, EBS CSI Driver, Karpenter 등)의
IAM Role 신뢰 정책에 등록된 OIDC issuer가 그대로 유효하다는 의미입니다.
별도 재설정 작업 없이 4개의 IRSA 사용 앱이 업그레이드 전후 동일하게 동작합니다.

전 포스팅에서 Blue/Green 방식의 단점으로 다뤘던 OIDC issuer 재설정 문제가

In-Place에서는 발생하지 않는다는 것이 여기서 직접 확인됩니다.

 

kubectl로도 확인할 수 있습니다. 'kubectl get nodes'에는 EKS 컨트롤 플레인이 표시되지 않습니다.

AWS가 컨트롤 플레인을 완전히 관리하기 때문에 노드 목록에 나타나지 않습니다.

컨트롤 플레인 버전은 kubectl version의 Server Version으로 확인합니다.

kubectl version

Client Version: v1.31.0
Kustomize Version: v5.4.2
Server Version: v1.31.14-eks-40737a8

 

Step2. 파드 컨테이너 이미지 버전 비교

kubectl get pods --all-namespaces \
  -o jsonpath="{.items[*].spec.containers[*].image}" \
  | tr -s '[[:space:]]' '\n' | sort | uniq -c > 1.31.txt

diff 1.30.txt 1.31.txt

diff 결과 내용이 동일한 것을 확인할 수 있습니다.

컨트롤 플레인 업그레이드는 노드와 파드에 전혀 영향을 주지 않기 때문에 모든 컨테이너 이미지 버전이 그대로입니다.
kube-proxy가 아직 v1.30.14-eksbuild.28인 것도 이 단계에서는 정상입니다.
더보기

클러스터 버전이 1.31로 올랐는데 왜 kube-proxy나 coredns 이미지 버전이 그대로일까요?

 

이건 컨트롤 플레인 업그레이드의 범위를 정확히 보여주는 결과입니다.

컨트롤 플레인 업그레이드는 AWS가 관리하는 API 서버, etcd, 스케줄러, 컨트롤러 매니저를 교체하는 작업입니다.

노드 위에 떠 있는 파드들은 건드리지 않습니다.

 

kube-proxy는 각 노드에서 DaemonSet으로 실행되는 컴포넌트이고,

coredns는 EKS Addon으로 관리되는 Deployment입니다.

 

이 둘은 컨트롤 플레인과 별개로 동작하기 때문에 이 단계에서는 여전히 1.30 기준 이미지를 사용하고 있습니다.

Addon 업그레이드는 다음 섹션에서 별도로 진행합니다.

 

Step3. 파드 재생성 여부 확인

kubectl get pod -A

모든 파드의 AGE가 업그레이드 이전과 동일합니다.

컨트롤 플레인 업그레이드 과정에서 파드가 재시작되거나 재생성된 케이스는 없습니다.
모니터링으로 걸어둔 UI 서비스 curl도 업그레이드 10분 내내 {"status":"UP"} 응답이 끊기지 않은 것을 확인할 수 있습니다.

 


 

02. EKS Add-on Upgrade

왜 Addon을 별도로 업그레이드해야 하는가?

컨트롤 플레인 업그레이드가 끝난 시점에 클러스터는 어정쩡한 상태입니다.

API 서버는 1.31인데 노드 위에서 돌고 있는 kube-proxy는 여전히 v1.30.14입니다. CoreDNS도 마찬가지입니다.

 

EKS Addon은 컨트롤 플레인과 독립적으로 버전이 관리됩니다.

컨트롤 플레인을 올린다고 해서 자동으로 따라 올라가지 않습니다.

 

각 Addon은 K8s 버전별 호환 버전이 따로 정의되어 있고, 컨트롤 플레인 업그레이드 후 해당 버전에 맞는

Addon 버전으로 명시적으로 올려줘야 합니다.

 

이번 실습에서 업그레이드하는 대상은 네 가지입니다.

Add-on 역할
CoreDNS 클러스터 내부 DNS 서비스
kube-proxy 각 노드의 네트워크 규칙 관리 (iptables/ipvs)
VPC CNI 파드에 VPC IP 할당
EBS CSI Driver EBS 볼륨 프로비저닝 및 마운트

 

현재 설치된 Add-on 상태 확인

eksctl get addon --cluster $CLUSTER_NAME

NAME                    VERSION                 STATUS  ISSUES  UPDATE AVAILABLE
aws-ebs-csi-driver      v1.58.0-eksbuild.1      ACTIVE  0       v1.59.0-eksbuild.1
coredns                 v1.11.4-eksbuild.32     ACTIVE  0
kube-proxy              v1.30.14-eksbuild.28    ACTIVE  0       v1.31.14-eksbuild.9, ...
vpc-cni                 v1.21.1-eksbuild.7      ACTIVE  0

컨트롤 플레인이 1.31로 올라간 시점에서 UPDATE AVAILABLE 컬럼을 보면 kube-proxy와

EBS CSI Driver에 업그레이드 가능한 버전이 표시됩니다.

EKS가 컨트롤 플레인 버전을 기준으로 호환 가능한 Addon 버전을 자동으로 감지해 알려주는 것입니다.

 

1.31 호환 버전 확인

eksctl get addon 출력에서 제안해주는 버전 외에도, EKS API로 특정 K8s 버전에서 사용 가능한

Addon 버전 목록을 직접 조회할 수 있습니다.

 

1) CoreDNS

aws eks describe-addon-versions \
  --addon-name coredns \
  --kubernetes-version 1.31 \
  --output table \
  --query "addons[].addonVersions[:10].{Version:addonVersion,DefaultVersion:compatibilities[0].defaultVersion}"
  
-------------------------------------------
|          DescribeAddonVersions          |
+-----------------+-----------------------+
| DefaultVersion  |        Version        |
+-----------------+-----------------------+
|  False          |  v1.11.4-eksbuild.33  |
|  False          |  v1.11.4-eksbuild.32  |
|  False          |  v1.11.4-eksbuild.28  |
|  False          |  v1.11.4-eksbuild.24  |
|  False          |  v1.11.4-eksbuild.22  |
|  False          |  v1.11.4-eksbuild.20  |
|  False          |  v1.11.4-eksbuild.14  |
|  False          |  v1.11.4-eksbuild.10  |
|  False          |  v1.11.4-eksbuild.2   |
|  False          |  v1.11.4-eksbuild.1   |
+-----------------+-----------------------+

CoreDNS는 마이너 버전(v1.11.4) 자체는 변경되지 않고, eksbuild 번호만 올라갑니다.

현재 eksbuild.32에서 eksbuild.33으로 업그레이드합니다.

 

2) kube-proxy

aws eks describe-addon-versions \
  --addon-name kube-proxy \
  --kubernetes-version 1.31 \
  --output table \
  --query "addons[].addonVersions[:10].{Version:addonVersion,DefaultVersion:compatibilities[0].defaultVersion}"
  
--------------------------------------------
|           DescribeAddonVersions          |
+-----------------+------------------------+
| DefaultVersion  |        Version         |
+-----------------+------------------------+
|  False          |  v1.31.14-eksbuild.9   |
|  False          |  v1.31.14-eksbuild.6   |
|  False          |  v1.31.14-eksbuild.5   |
|  False          |  v1.31.14-eksbuild.2   |
|  False          |  v1.31.13-eksbuild.2   |
|  True           |  v1.31.10-eksbuild.12  |
|  False          |  v1.31.10-eksbuild.8   |
|  False          |  v1.31.10-eksbuild.6   |
|  False          |  v1.31.10-eksbuild.2   |
|  False          |  v1.31.9-eksbuild.2    |
+-----------------+------------------------+

kube-proxy는 컨트롤 플레인 마이너 버전과 맞춰야 합니다. 현재 v1.30.14에서 v1.31.x 계열로 올려야 합니다.

DefaultVersion: True인 버전이 AWS가 해당 K8s 버전에서 권장하는 기본 버전입니다.

이번 실습에서는 최신 버전인 v1.31.14-eksbuild.9를 사용합니다.

DefaultVersion: True 와 최신 버전이 다를 수 있습니다.
운영 환경이라면 Default 버전을 먼저 적용해 안정성을 확인한 후 최신 버전으로 올리는 단계적 접근이 안전합니다.
이번 실습에서는 최신 버전을 바로 적용합니다.

 

VPC CNI, EBS CSI Driver도 동일한 방식으로 확인합니다.

각 Addon의 공식 호환성 문서에서도 버전을 확인할 수 있습니다.

 

Manage CoreDNS for DNS in Amazon EKS clusters - Amazon EKS

Help improve this page To contribute to this user guide, choose the Edit this page on GitHub link that is located in the right pane of every page. Manage CoreDNS for DNS in Amazon EKS clusters CoreDNS is a flexible, extensible DNS server that can serve as

docs.aws.amazon.com

 

Manage kube-proxy in Amazon EKS clusters - Amazon EKS

An earlier version of the documentation was incorrect. kube-proxy versions v1.28.5, v1.27.9, and v1.26.12 aren’t available. If you’re self-managing this add-on, the versions in the table might not be the same as the available self-managed versions.

docs.aws.amazon.com

 

Assign IPs to Pods with the Amazon VPC CNI - Amazon EKS

To upgrade to VPC CNI v1.12.0 or later, you must upgrade to VPC CNI v1.7.0 first. We recommend that you update one minor version at a time.

docs.aws.amazon.com

 


Terraform으로 Add-on 업그레이드 실행

Step1. 업그레이드 전 모니터링 세팅

Addon 업그레이드는 롤링 방식으로 파드를 교체합니다.

업그레이드 진행 중 kube-dns와 kube-proxy 파드 상태를 실시간으로 관찰합니다.

while true; do
  date
  kubectl get pod -n kube-system -l 'k8s-app in (kube-dns, kube-proxy)'
  echo; sleep 2
done

동시에 UI 서비스 무중단 여부도 계속 확인합니다. CoreDNS 업그레이드 중 DNS 해석이 중단되면

서비스에 영향이 있을 수 있어서, 실제로 영향이 없는지 눈으로 확인합니다.

 

Step2. addons.tf 수정 후 terraform apply

호환 버전 확인이 끝나면 terraform/addons.tf에서 각 Addon의 버전을 업데이트합니다.

eks_addons = {
  coredns = {
    addon_version = "v1.11.4-eksbuild.33"     # 1.31 권장 버전
  }
  kube-proxy = {
    addon_version = "v1.31.14-eksbuild.9"     # 1.31 권장 버전
  }
  vpc-cni = {
    most_recent = true
  }
  aws-ebs-csi-driver = {
    addon_version            = "v1.59.0-eksbuild.1"   # 1.31 권장 버전
    service_account_role_arn = module.ebs_csi_driver_irsa.iam_role_arn
  }
}

vpc-cni는 most_recent = true로 설정되어 있어 별도 버전 지정 없이

항상 최신 호환 버전으로 자동 업데이트됩니다.

 

step3. plan으로 변경 범위를 먼저 확인

cd ~/environment/terraform/
terraform plan -no-color | tee addon.txt

plan 출력에서 각 Addon의 버전 변경이 확인됩니다.

# module.eks_blueprints_addons.aws_eks_addon.this["coredns"] will be updated in-place
~ resource "aws_eks_addon" "this" {
    ~ addon_version = "v1.11.4-eksbuild.32" -> "v1.11.4-eksbuild.33"
      id            = "myeks:coredns"
  }

# module.eks_blueprints_addons.aws_eks_addon.this["kube-proxy"] will be updated in-place
~ resource "aws_eks_addon" "this" {
    ~ addon_version = "v1.30.14-eksbuild.28" -> "v1.31.14-eksbuild.9"
      id            = "myeks:kube-proxy"
  }

내용 확인 후 apply합니다. 약 1~2분 소요됩니다.

terraform apply -auto-approve

 

업그레이드 결과 확인

Step1. 파드 교체 확인

kubectl get pod -n kube-system -l 'k8s-app in (kube-dns, kube-proxy)'

NAME                       READY   STATUS    RESTARTS   AGE
coredns-58cc4d964b-7867s   1/1     Running   0          71s
coredns-58cc4d964b-qb8zc   1/1     Running   0          71s
kube-proxy-2f6nv           1/1     Running   0          61s
kube-proxy-7n8xj           1/1     Running   0          67s
kube-proxy-7p4p2           1/1     Running   0          70s
kube-proxy-g2vvp           1/1     Running   0          54s
kube-proxy-ks4xp           1/1     Running   0          64s
kube-proxy-kxvh5           1/1     Running   0          57s

AGE가 1분 내외로 짧아진 것이 보입니다. 이전 파드가 종료되고 새 버전 파드로 롤링 교체가 완료된 것입니다.

 

Step2. 이미지 버전 변경 확인

kubectl get pods --all-namespaces \
  -o jsonpath="{.items[*].spec.containers[*].image}" \
  | tr -s '[[:space:]]' '\n' | sort | uniq -c
  
2  602401143452.dkr.ecr.us-west-2.amazonaws.com/eks/coredns:v1.11.4-eksbuild.33
6  602401143452.dkr.ecr.us-west-2.amazonaws.com/eks/kube-proxy:v1.31.14-eksbuild.9
...

컨트롤 플레인 업그레이드 직후 저장해둔 1.31.txt와 비교하면 이번에는

coredns와 kube-proxy 이미지 버전 변경이 실제로 diff에 잡힙니다.

컨트롤 플레인 업그레이드 때와 달리 이번 단계는 실제로 노드 위의 파드를 교체하는 작업이기 때문입니다.

 

Step3. Addon 상태 최종 확인

eksctl get addon --cluster $CLUSTER_NAME

모든 Addon의 UPDATE AVAILABLE 컬럼이 비어 있고 STATUS가 ACTIVE이면

업그레이드가 정상적으로 완료된 것입니다.

 

이 시점에서 클러스터 상태 정리

컨트롤 플레인(1.31)과 Addon(1.31 호환 버전) 업그레이드가 모두 완료됐습니다.
하지만 노드그룹은 아직 1.30 버전 AMI를 사용하고 있습니다.
다음 단계에서 데이터 플레인, 즉 노드그룹을 1.31로 올립니다.

 


긴 글 읽어주셔서 감사합니다.

 

이번 포스팅에서는 제어부인 Control Plane과 Add-on 업그레이드 진행을 해보았고,

다음 포스팅에서는 Node Group 업그레이드 포스팅을 진행할 예정입니다.