In-Place Upgrade
이 문서에서는 v1.30 → v1.31 In-Place 업그레이드의 전체 과정을 다룹니다. Control Plane 업그레이드부터 시작하여 Add-on, Managed Node Group, Karpenter, Self-managed, Fargate까지 모든 data plane 유형의 업그레이드 절차를 실습 기반으로 정리합니다. 실습은 Amazon EKS Upgrades Workshop에서 제공하는 v1.30 클러스터를 기반으로 진행합니다.
Lab Environment
워크숍 클러스터에는 웹 스토어 샘플 애플리케이션이 배포되어 있습니다. 고객이 카탈로그를 탐색하고 장바구니에 상품을 담아 결제하는 과정을 모델링한 앱으로, UI, Catalog, Cart, Checkout, Orders, Assets 컴포넌트로 구성됩니다. 각 컴포넌트는 서로 다른 data plane 유형에서 실행되며, 업그레이드 실습 시 해당 노드 그룹의 동작을 직접 관찰할 수 있습니다.
| Node Group | Type | App | Scheduling |
|---|---|---|---|
initial |
Managed Node Group | UI, Catalog, Cart | Default scheduling |
blue-mng |
Managed Node Group | Orders + MySQL | label type=OrdersMNG, taint dedicated=OrdersApp:NoSchedule, 단일 AZ |
| Karpenter nodes | Karpenter | Checkout | label team=checkout, taint dedicated=CheckoutApp:NoSchedule |
default-selfmng |
Self-managed Node Group | Carts | label node.kubernetes.io/lifecycle=self-managed |
| Fargate profile | Fargate | Assets | Fargate profile fp-profile → namespace assets |
Accessing the Lab Environment
모든 애플리케이션은 ArgoCD를 통해 배포되며, AWS CodeCommit 리포지토리를 GitOps 소스로 사용합니다. 실습 시작 전 아래 설정을 완료합니다.
# GitOps 리포지토리 복제
cd ~/environment
git clone codecommit::${REGION}://eks-gitops-repo
# ArgoCD 접속 정보 확인
export ARGOCD_SERVER=$(kubectl get svc argo-cd-argocd-server -n argocd \
-o json | jq --raw-output '.status.loadBalancer.ingress[0].hostname')
export ARGOCD_USER="admin"
export ARGOCD_PWD=$(kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d)
echo "ArgoCD URL: http://${ARGOCD_SERVER}"
echo "Username: ${ARGOCD_USER}"
echo "Password: ${ARGOCD_PWD}"
# ArgoCD CLI 로그인
argocd login ${ARGOCD_SERVER} \
--username ${ARGOCD_USER} \
--password ${ARGOCD_PWD} \
--insecure --skip-test-tls --grpc-web
ArgoCD 콘솔에서 각 컴포넌트가 개별 Application으로 등록되어 있는지, 모든 Pod가 Running 상태인지 확인합니다.
Control Plane Upgrade
EKS Control Plane 업그레이드는 AWS가 내부적으로 blue/green 방식으로 수행합니다.
- 새 버전의 control plane 컴포넌트를 프로비저닝합니다.
- 정상 동작이 확인되면 API server endpoint를 새 컴포넌트로 전환합니다.
- 구 컴포넌트를 종료합니다.
업그레이드 중에도 API server endpoint는 유지되므로 워크로드 가용성에 영향이 없습니다. 문제가 발생하면 자동으로 롤백됩니다. 한 번에 1 minor 버전만 올릴 수 있으며, 업그레이드된 Control Plane은 이전 버전으로 되돌릴 수 없습니다1.
Methods
# variables.tf에서 cluster_version을 "1.30" → "1.31"로 변경
cd labs/week7/terraform
terraform plan
terraform apply -auto-approve
10~15분 소요됩니다. MNG에 별도 AMI/version을 지정하지 않았다면 terraform plan 결과에 MNG 업그레이드도 함께 나타납니다. 이 워크샵에서는 CP만 먼저 올린 뒤 add-on → data plane 순서로 진행합니다.
Add-on Upgrade
Control Plane 업그레이드 후, data plane을 올리기 전에 EKS managed add-on을 먼저 업그레이드합니다. add-on의 기본 개념은 Week 1 — Add-ons and Capabilities를 참고하세요.
업그레이드 순서에 제약은 없지만, CoreDNS → kube-proxy → VPC CNI 순으로 진행하는 것이 일반적입니다. CoreDNS는 클러스터 내 DNS resolution을 담당하므로 먼저 안정화하고, kube-proxy와 VPC CNI는 노드 수준 네트워킹에 영향을 주므로 data plane 업그레이드 직전에 맞춰 올립니다. 호환 버전 조회 방법은 Upgrade Preparation — Add-on Compatibility를 참고하세요.
addons.tf에서 각 add-on 버전을 최신 호환 버전으로 변경하고 적용합니다.
업그레이드 후 kubectl get pods -n kube-system으로 add-on Pod가 정상 Running 상태인지 확인합니다.
Managed Node Group Upgrade
MNG 업그레이드는 두 가지 방식을 사용할 수 있습니다. In-Place rolling update와 Blue/Green node group 교체입니다.
In-Place Rolling Update Mechanism
MNG in-place 업그레이드는 자동화된 4단계 프로세스로 진행됩니다2.
flowchart LR
A["1. Setup<br/>Launch Template 생성<br/>ASG 업데이트"] --> B["2. Scale Up<br/>새 노드 프로비저닝<br/>구 노드 unschedulable"]
B --> C["3. Upgrade<br/>Pod drain (15분 timeout)<br/>노드 cordon → 종료"]
C --> D["4. Scale Down<br/>ASG 원래 크기로 복원"]
Phase Details
1. Setup — 대상 AMI를 반영한 새 EC2 Launch Template 버전을 생성하고 ASG를 업데이트합니다. updateConfig.max_unavailable_percentage로 병렬 업그레이드 노드 수를 제어합니다 (최대 100).
2. Scale Up — ASG의 max/desired를 증가시키고(AZ 수의 2배 또는 max_unavailable 중 큰 값), 새 노드가 Ready 상태가 될 때까지 대기합니다. 기존 노드는 unschedulable로 표시되고 node.kubernetes.io/exclude-from-external-load-balancers=true 레이블이 추가됩니다.
3. Upgrade — max_unavailable까지 노드를 랜덤으로 선택하여 Pod를 drain합니다 (15분 타임아웃, 초과 시 PodEvictionFailure — force 옵션으로 우회 가능). cordon 후 60초 대기한 뒤 ASG에 종료를 요청합니다. 모든 구 버전 노드가 교체될 때까지 반복됩니다.
4. Scale Down — ASG의 max/desired를 원래 값으로 복원합니다.
Default AMI vs Custom AMI
Terraform에서 MNG를 관리할 때, AMI 지정 방식에 따라 업그레이드 동작이 달라집니다.
| AMI Type | Upgrade Trigger | Action Required |
|---|---|---|
| Default (AMI 미지정) | eks_managed_node_group_defaults.cluster_version 변경 |
변수 하나만 변경 |
| Custom (AMI ID 지정) | cluster_version 변경만으로는 업그레이드되지 않음 |
AMI ID도 함께 변경 필요 |
Custom AMI의 경우 SSM Parameter로 최신 AMI ID를 조회합니다.
aws ssm get-parameter \
--name /aws/service/eks/optimized-ami/1.31/amazon-linux-2023/x86_64/standard/recommended/image_id \
--region $AWS_REGION \
--query "Parameter.Value" --output text
Workshop: In-Place MNG Upgrade
variables.tf에서mng_cluster_version을"1.30"→"1.31"로 변경합니다.- Custom AMI MNG가 있으면
ami_id변수도 v1.31 AMI로 업데이트합니다. terraform plan && terraform apply -auto-approve를 실행합니다.- 12~20분 후 모든 MNG 노드가 v1.31로 업그레이드된 것을 확인합니다.
Workshop: Blue/Green MNG Upgrade
Stateful 워크로드(orders + mysql)가 실행되는 blue-mng를 green-mng로 교체하는 시나리오입니다. blue-mng는 특정 AZ에 배치되고, taint(dedicated=OrdersApp:NoSchedule)와 label(type=OrdersMNG)이 적용되어 있습니다. Stateful 워크로드용 MNG는 AZ별로 분리하여 프로비저닝하는 것이 권장됩니다.
-
base.tf에green-mng를 추가합니다.blue-mng와 동일한 labels, taints, subnet_ids를 사용하되, cluster_version은 default(1.31)를 따릅니다.green-mng = { instance_types = ["m5.large", "m6a.large", "m6i.large"] subnet_ids = [module.vpc.private_subnets[0]] min_size = 1 max_size = 2 desired_size = 1 update_config = { max_unavailable_percentage = 35 } labels = { type = "OrdersMNG" } taints = [{ key = "dedicated" value = "OrdersApp" effect = "NO_SCHEDULE" }] } -
terraform apply로green-mng를 생성합니다. -
두 노드 그룹 모두 label과 taint가 동일한지 확인합니다.
-
PDB가 설정되어 있으므로, orders replica를 2로 증가시킵니다 (minAvailable=1 위반 방지).
-
base.tf에서blue-mng블록을 삭제하고terraform apply를 실행합니다 (10~15분). - EKS가 자동으로
blue-mng노드를 cordon, drain, delete합니다. -
orders Pod가
green-mng노드(v1.31)에서 실행되는 것을 확인합니다.
Karpenter Node Upgrade
Karpenter는 노드 그룹이나 ASG 같은 외부 인프라를 사용하지 않고 직접 EC2 인스턴스를 프로비저닝하므로, MNG와 달리 EKS가 자동으로 rolling update를 수행하지 않습니다. 대신 Drift와 expireAfter 두 가지 메커니즘으로 노드를 최신 상태로 유지합니다. Karpenter의 기본 개념은 Week 3 — Karpenter를 참고하세요.
Drift
EC2NodeClass의 AMI 설정이 변경되면 Karpenter는 기존 노드의 drift를 감지합니다. 감지된 노드에 대해 새 노드를 먼저 프로비저닝하고, 구 노드를 cordon하여 Pod를 evict한 뒤 종료합니다.
AMI를 지정하는 방식에 따라 drift 동작이 다릅니다.
amiSelectorTerms(명시적 AMI 지정)- AMI ID, name, tag로 지정합니다. AMI를 변경하면 drift가 발생합니다. 프로덕션 환경에서 AMI 승격을 제어할 때 적합합니다. EC2NodeClass의
status.amis필드에서 현재 탐지된 AMI를 확인할 수 있습니다. alias(EKS optimized AMI)family@version형식(e.g.al2023@latest)으로 지정합니다.latest를 사용하면 Karpenter가 SSM 파라미터를 모니터링하여 새 AMI 릴리스 시 자동으로 drift를 감지합니다. 새 AMI가 출시되면 기존 노드를 자동으로 교체하므로 pre-production 환경에 적합하지만, 프로덕션에서는 특정 버전을 핀하는 것을 권장합니다.
Drift Detection Detail
EC2NodeClass가 이전 AMI와 새 AMI를 모두 탐지하는 경우, 이전 AMI로 실행 중인 노드만 Drifted로 표시됩니다. kubectl describe ec2nodeclass 명령으로 status.amis 필드에서 현재 탐지된 AMI 목록을 확인할 수 있습니다.
expireAfter
spec.disruption.expireAfter로 노드의 수명(TTL)을 설정할 수 있습니다. 노드가 설정된 시간을 초과하면 Karpenter가 만료된 노드로 표시하고 disruption을 트리거합니다. 보안 패치를 위해 주기적으로 노드를 교체하는 용도로 사용합니다. 이 실습의 NodePool은 expireAfter: 720h(30일)로 설정되어 있습니다.
Disruption Budgets
Drift나 expireAfter로 인해 여러 노드가 동시에 교체 대상이 될 수 있습니다. NodePool의 spec.disruption.budgets로 disruption 속도와 시간을 제어하여 서비스 영향을 최소화할 수 있습니다. 미정의 시 기본값은 nodes: 10%이며, reasons 필드로 Drifted, Underutilized, Empty를 별도로 제어할 수 있습니다.
업무시간에는 disruption을 금지하고, 그 외 시간에는 10개까지 허용합니다.
Drift로 인한 교체는 1개씩만, Empty/Underutilized는 전체를 한 번에 교체합니다.
Workshop: Karpenter Node Upgrade
checkout 앱은 Karpenter default NodePool로 프로비저닝된 노드에서 실행됩니다. nodeSelector: team=checkout과 tolerations: dedicated=CheckoutApp:NoSchedule이 적용되어 있으며, checkout-redis가 persistent volume을 사용하는 stateful 워크로드입니다.
이 실습의 목표는 여러 Karpenter 노드가 존재하는 상태에서 AMI를 변경하고, disruption budget으로 한 번에 1개씩만 교체되도록 제어하는 것을 관찰하는 것입니다. 기본 budget(nodes: 10%)을 그대로 사용하면 10개 노드 중 1개만 동시에 교체되지만, 노드가 수백 개인 프로덕션 환경에서는 명시적으로 제한하지 않으면 대규모 동시 교체가 발생할 수 있습니다.
-
현재 Karpenter 노드와 checkout Pod 상태를 확인합니다.
# Karpenter 노드 버전 확인 (v1.30) kubectl get nodes -l team=checkout # 노드에 적용된 taint 확인 kubectl get nodes -l team=checkout \ -o jsonpath="{range .items[*]}{.metadata.name} {.spec.taints[?(@.effect=='NoSchedule')]}{\"\n\"}{end}" # checkout Pod가 Karpenter 노드에서 실행 중인지 확인 kubectl get pods -n checkout -o wide -
checkout replica를 1 → 10으로 scale up합니다. Karpenter가 unscheduled Pod의 리소스 요청을 집계하여 추가 노드를 프로비저닝합니다.
cd ~/environment/eks-gitops-repo sed -i 's/replicas: 1/replicas: 10/' apps/checkout/deployment.yaml git add apps/checkout/deployment.yaml git commit -m "Scale checkout replicas" git push --set-upstream origin main argocd app sync checkoutArgoCD가 변경을 감지하고 동기화하는 데 2~3분 소요됩니다. 새 노드가 프로비저닝되었는지 확인합니다.
-
v1.31 AMI ID를 조회합니다.
-
apps/karpenter/default-ec2nc.yaml의spec.amiSelectorTerms.id를 v1.31 AMI로 변경합니다. -
apps/karpenter/default-np.yaml의spec.disruption에 budget을 추가하여 Drift 교체를 1개씩만 수행하도록 제한합니다. -
변경사항을 commit/push하고 ArgoCD로 sync합니다.
-
Karpenter controller 로그에서 drift 감지와 노드 교체 과정을 확인합니다.
로그에서 다음 순서를 관찰할 수 있습니다:
disrupting nodeclaim(s) via replace— drift 감지, 구 노드 교체 시작created nodeclaim/launched nodeclaim— 새 노드(v1.31) 프로비저닝tainted node— 구 노드에karpenter.sh/disrupted:NoScheduletaint 추가하여 Pod 스케줄링 차단deleted node/deleted nodeclaim— Pod eviction 완료 후 구 노드 종료
nodes: "1"budget으로 인해 노드가 한 개씩 순차적으로 교체됩니다. 첫 번째 노드 교체가 완료된 후 다음 노드에 대해 같은 과정이 반복됩니다. -
모든 checkout Pod가 v1.31 노드에서 실행되는 것을 확인합니다.
Self-managed Node Upgrade
Self-managed 노드는 node.kubernetes.io/lifecycle=self-managed 레이블로 식별됩니다. MNG와 달리 EKS가 rolling update를 자동으로 수행하지 않으므로, AMI를 교체한 뒤 ASG의 인스턴스를 직접 교체해야 합니다. Terraform을 사용하면 Launch Template 변경 시 ASG가 인스턴스를 교체합니다. 워크샵에서는 carts 앱이 self-managed 노드에서 실행됩니다.
-
v1.31 AMI ID를 조회합니다.
-
base.tf의 self-managed node group AMI를 새 ID로 변경합니다. -
Terraform을 적용합니다.
-
Terraform이 ASG의 Launch Template을 업데이트하고 인스턴스를 교체합니다. 새 노드가 v1.31인지 확인합니다.
Fargate Node Upgrade
Fargate에서는 각 Pod이 전용 microVM에서 실행되며, AWS가 해당 VM의 kubelet 버전과 OS를 관리합니다. 기존 Pod의 Fargate 노드는 이미 실행 중인 버전을 유지하므로, Control Plane 업그레이드 후 Pod를 재시작해야 새 버전의 Fargate 노드에서 다시 스케줄링됩니다.
-
기존 Fargate Pod의 버전을 확인합니다.
-
Deployment를 재시작합니다.
-
새 Pod가 Ready 상태가 될 때까지 대기합니다.
-
새 Fargate 노드의 버전이 v1.31인지 확인합니다.