AWS 워크샵 EKS Upgrades(catalog.workshops.aws/eks-upgrades) 환경을 제공해주셔서 그대로 따라가며 정리한다. 워크샵은 1.30 → 1.31 한 단계 인플레이스 업그레이드를 컨트롤 플레인 → 애드온 → 4가지 노드 타입 순으로 수행한다.
1. 개요
1.1 EKS 업그레이드의 책임 분담
EKS 업그레이드에서 가장 먼저 잡고 가야 하는 개념은 공동 책임 모델이다. 컨트롤 플레인 컴포넌트(API Server · etcd · Scheduler · Controller Manager)는 AWS의 관리 VPC에서 동작하며, 그 가용성·백업·패치·고가용성 토폴로지는 모두 AWS가 보장한다. 반면 그 위에서 동작하는 데이터 플레인(워커 노드, kubelet, kube-proxy, CNI, 사용자 워크로드) 의 호환성과 가용성은 전적으로 사용자 몫이다.
업그레이드 관점에서 이 분담은 매우 구체적인 의미를 갖는다. AWS는 컨트롤 플레인 버전을 1.30 → 1.31로 올려주지만, 그 다음 노드 위에서 돌고 있는 kubelet과 add-on 컴포넌트가 새 API Server와 호환되는지, 사용자 워크로드가 deprecated된 API를 호출하지 않는지는 사용자가 책임진다. 클러스터 버전을 한 줄 바꾸면 끝나는 게 아니라, 컨트롤 플레인 → 애드온 → 노드 → 워크로드 순서로 호환성을 한 단계씩 끌어올리는 캐스케이드로 이해해야 한다.
1.2 왜 어려운가
- 컨트롤 플레인은 한 단계씩만: 1.30 → 1.32 같은 점프 업그레이드는 불가능하다. EKS는 Kubernetes 자체의 버전 스큐 정책을 그대로 따르며, n+1만 허용한다. 1.30에서 1.32로 가려면 1.31을 거쳐야 하고, 각 단계마다 데이터 플레인 동기화 → 사전 점검 → apply 사이클을 새로 돌려야 한다.
- 노드 타입마다 절차가 다르다: Managed Node Group은 EKS가 롤링을 대행하지만, Self-Managed는 ASG Instance Refresh를 직접 트리거해야 하고, Karpenter는 컨트롤러가 NodeClaim을 새로 만드는 모델이며, Fargate는 노드라는 개념 자체가 없어 파드 재시작으로 대체된다. 한 클러스터에 4종이 공존하면 업그레이드 절차도 4가지를 모두 알고 있어야 한다.
- 데이터 플레인 가용성: 노드를 갈아 끼우는 동안에도 트래픽은 흐른다. PodDisruptionBudget · TopologySpreadConstraints · Managed Node Group의 Surge 정책 · ASG Health Check가 모두 맞물려 있어야 무중단이 보장된다. 한 군데라도 헐거우면 그 워크로드만 정확히 트래픽 손실이 난다.
- Deprecated API: K8s는 마이너 버전마다 일부 API를 deprecate한다. PodSecurityPolicy(1.25에서 제거),
flowcontrol.apiserver.k8s.io/v1beta2(1.29),node.k8s.io/v1beta1등 이전 버전에서 멀쩡히 쓰던 매니페스트가 새 컨트롤 플레인에서는 거부될 수 있다. EKS Upgrade Insights가 이를 사전에 잡아주는 이유다.
1.3 워크샵 환경
워크샵은 us-west-2(오레곤) 리전 위에서 동작한다. AWS CloudFormation으로 띄운 IDE-Server(EC2 + code-server)에 접속해 작업하고, EKS 클러스터(eksworkshop-eksctl) 자체는 terraform-aws-eks-blueprints 기반 Terraform으로 이미 배포되어 있다. 모든 업그레이드 작업은 이 Terraform 코드를 수정하고 terraform apply를 돌리는 방식으로 수행한다.
| 컴포넌트 | 초기 상태 |
|---|---|
| Control Plane | 1.30 (platformVersion eks.65) |
| Managed Node group | initial, blue-mng (AL2023, 1.30) |
| Self-Managed Node group | default-selfmng (AL2023, 1.30) |
| Karpenter NodePool | default (Spot) |
| Fargate Profile | fp-profile (namespace: assets) |
| 애드온 | vpc-cni, coredns, kube-proxy, aws-ebs-csi-driver |
| GitOps | ArgoCD (apps, assets, carts, catalog, checkout, karpenter, orders, ui …) |
샘플 앱(retail-store-sample)은 ArgoCD가 GitOps로 동기화한 상태고, UI는 NLB로 노출해 두었다. 업그레이드 진행 중 다음 두 루프를 따로 띄워두면 영향도를 즉시 볼 수 있다.
# UI 헬스체크 반복
while true; do curl -s $UI_WEB; echo; date; sleep 1; done
# 클러스터 메타 반복
while true; do date; aws eks describe-cluster --name $EKS_CLUSTER_NAME \
| egrep 'version|endpoint"|issuer|platformVersion'; echo; sleep 2; done
2. 업그레이드 사전 점검
2.1 EKS Upgrade Insights
EKS는 Upgrade Insights API를 통해 현재 클러스터를 다음 버전으로 올렸을 때 발생할 호환성 문제를 사전에 진단한다. 클러스터 audit log와 컴포넌트 메타데이터를 분석해 다음과 같은 항목을 자동 점검한다.
- kube-proxy / CoreDNS / VPC CNI 버전 스큐 — 컨트롤 플레인과 일치하는지
- Deprecated API 호출 — audit log를 스캔해 v1beta1 등 사라질 API를 누가 호출하는지
- Add-on 호환성 — 다음 버전에서 사용 가능한지
- 노드 OS / 커널 / containerd 버전 — 새 K8s 버전 요구사항 충족 여부
각 Insight는 PASSING / WARNING / ERROR 중 하나의 상태와, 통과하지 못했을 때 어디를 손봐야 하는지에 대한 recommendation을 함께 반환한다. 업그레이드 전 ERROR가 하나라도 남아 있으면 진행을 멈추는 게 원칙이다.
aws eks list-insights --filter kubernetesVersions=1.31 \
--cluster-name $CLUSTER_NAME | jq .
{
"id": "676cc9cf-...",
"name": "kube-proxy version skew",
"category": "UPGRADE_READINESS",
"kubernetesVersion": "1.31",
"insightStatus": {
"status": "PASSING",
"reason": "kube-proxy versions match the cluster control plane version."
}
}


2.2 PodDisruptionBudget 사전 점검
데이터 플레인 업그레이드는 노드 드레인을 동반한다. kubectl drain은 내부적으로 eviction subresource API(POST /api/v1/namespaces/{ns}/pods/{name}/eviction)를 호출하는데, 이 API는 삭제 전에 해당 파드가 속한 PDB를 평가한다. PDB가 정의한 최소 가용성을 위반하게 되면 API Server가 429 Too Many Requests를 반환하고 eviction은 거부된다. 즉 PDB는 단순한 어노테이션이 아니라 K8s API Server가 강제하는 admission 정책이다.
운영 관점에서 핵심은 두 가지다.
- PDB가 없는 워크로드는 노드와 함께 그냥 사라진다 — drain 명령은 무방비 상태로 evict를 강행한다.
- PDB만 있고 replicas가 1이면 노드 드레인이 영원히 막힌다 —
minAvailable: 1+ replicas 1 조합은 흔한 함정이다.
워크샵은 orders 앱에 PDB가 없다는 점을 짚고 추가 → 검증 시나리오를 따라간다.
# apps/orders/pdb.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: orders-pdb
namespace: orders
spec:
minAvailable: 1
selector:
matchLabels:
app.kubernetes.io/name: orders
ArgoCD로 sync한 뒤 kubectl drain을 시도해 보면, PDB 위반으로 evict가 거부된다.
kubectl drain "$nodeName" --ignore-daemonsets --force --delete-emptydir-data
# error when evicting pods/"orders-..." -n "orders": Cannot evict pod as it would violate
# the pod's disruption budget.
헷갈리기 쉬운 지점 — drain은 cordon까지 같이 한다
drain이 PDB로 막혀도 노드는 이미 SchedulingDisabled 상태가 되어 새 파드가 잡히지 않는다. Ctrl+C로 멈추면 kubectl uncordon으로 명시적으로 풀어줘야 한다.
2.3 버전 스큐 정책
K8s는 컴포넌트 간 통신 호환성을 보장하기 위해 Version Skew Policy를 GA 정책으로 명시한다. EKS는 이 정책을 그대로 따른다.
| 컴포넌트 | 컨트롤 플레인과 허용 차이 | 의미 |
|---|---|---|
| kubelet | 최대 2 마이너 (1.30 컨트롤 → 1.28 kubelet 까지) | 노드 업그레이드를 며칠 미뤄도 됨 |
| kube-proxy | 최대 2 마이너 | 보통 kubelet과 함께 갱신 |
| controller-manager / scheduler | API Server와 동일 마이너 | EKS 관리 영역 |
| client (kubectl, client-go) | 컨트롤 플레인 ±1 마이너 | CI 파이프라인 점검 필요 |
| CoreDNS / VPC CNI | EKS 호환성 표 별도 확인 | add-on은 별도 매트릭스 |
이 정책의 의도는 단순하다. API Server는 항상 N 버전이고, 그 외 컴포넌트는 N-1 또는 N-2까지 허용함으로써 점진적 업그레이드(rolling upgrade)를 가능하게 한다. 만약 동시에 모두 같은 버전이어야 한다면 무중단 업그레이드 자체가 불가능해진다.
실무 적용은 다음과 같다. 컨트롤 플레인을 1.30 → 1.31로 올린 뒤 노드는 1.30 그대로 며칠 운영해도 무방하다. 다만 다음 업그레이드(1.31 → 1.32)를 시작하기 전에는 반드시 노드를 1.31로 맞춰야 한다. 그렇지 않으면 컨트롤 1.32 + 노드 1.30 조합이 되어 스큐 한계를 넘어선다.
2.4 standard vs extended support
EKS 버전은 출시 후 14개월간 standard 지원, 이후 12개월간 extended 지원으로 운영된다. standard 기간에는 추가 비용이 없지만, extended 기간으로 진입하면 시간당 추가 요금($0.60/hr 수준)이 부과된다. 비용 영향이 있으니 클러스터 단위로 의식적으로 정책을 잡아야 한다.
aws eks describe-cluster --name $EKS_CLUSTER_NAME \
--query 'cluster.upgradePolicy'
# { "supportType": "EXTENDED" }
본 워크샵 클러스터는 EXTENDED로 설정되어 있다. 운영 환경에서는 STANDARD로 두면 standard 기간이 끝나는 시점에 자동으로 EKS가 다음 버전으로 강제 업그레이드를 수행하므로, 업그레이드 일정을 사용자가 통제하고 싶다면 EXTENDED로 명시하고 직접 사이클을 돌리는 패턴이 일반적이다.
3. 전략 비교 — In-place vs Blue/Green
| 전략 | 장점 | 단점 | 적합한 환경 |
|---|---|---|---|
| In-place | 비용 추가 없음, 절차 단순, Terraform 한 줄 | 롤백 사실상 불가 (control plane downgrade 불허), 같은 클러스터 ID 위에서 진행 | 개발/스테이징, 무상태 서비스 위주 프로덕션 |
| Blue/Green (cluster) | 검증 후 트래픽 전환, 즉시 롤백 가능, 클러스터 ID 자체 교체 가능 | 클러스터 1세트 비용 추가, PV/StatefulSet 데이터 마이그레이션, DNS 전환 설계 필요 | 금융·의료·결제 등 다운타임 비용이 큰 프로덕션 |
본 워크샵은 In-place로 컨트롤 플레인을 올리고, 노드 그룹 단위에서만 Blue/Green을 일부 섞어 쓴다. 즉 컨트롤 플레인 자체는 한 번만 올리지만, 데이터 플레인의 일부 워크로드(EBS PV를 쓰는 orders 등)는 신규 노드그룹으로 옮기는 하이브리드 전략이다. 이는 실무에서도 흔한 패턴으로, 컨트롤 플레인은 in-place, 위험도가 높은 데이터 플레인 일부는 blue/green이라는 두 단의 조합을 통해 비용과 안전성을 모두 잡는다.
모든 변경은 ~/environment/terraform/의 코드를 고친 뒤 terraform apply 한 번으로 수행한다. 콘솔 클릭 / eksctl / aws cli 옵션도 있지만 드리프트(Terraform state와 실제 리소스 불일치) 발생을 막기 위해 단일 진실 원천을 Terraform으로 통일한다. 콘솔에서 한 번이라도 직접 변경하면 다음 terraform plan이 그 차이를 되돌리려 시도하면서 사고로 이어진다.
4. Control Plane Upgrade
4.1 4가지 방법
| 방법 | 한 줄 요약 | 비고 |
|---|---|---|
eksctl upgrade cluster |
CLI 한 줄 | 한 단계만 가능 |
| AWS 관리 콘솔 | 클릭 | 드리프트 발생 |
aws eks update-cluster-version |
API 직접 호출 | 노드 그룹 버전 일치 필요 |
| Terraform | cluster_version 변수 변경 |
✅ 채택 |
4.2 Terraform으로 컨트롤 플레인 올리기
variables.tf의 cluster_version 변수를 1.30 → 1.31로 바꾼다.


# 현재 이미지 스냅샷 (이후 비교용)
kubectl get pods -A -o jsonpath="{.items[*].spec.containers[*].image}" \
| tr -s '[[:space:]]' '\n' | sort | uniq -c > 1.30.txt
terraform plan -no-color > plan-output.txt # IDE에서 검토
terraform apply -auto-approve
4.3 결과 — 파드는 단 한 개도 재생성되지 않는다
aws eks describe-cluster --name $EKS_CLUSTER_NAME \
| egrep 'version|endpoint"|issuer|platformVersion'
# "version": "1.31",
# "endpoint": "https://...sk1.us-west-2.eks.amazonaws.com", # 동일
# "issuer": "https://oidc.eks.us-west-2.amazonaws.com/...", # 동일
# "platformVersion": "eks.X",
endpoint와 OIDC issuer가 그대로 유지되는 게 핵심 포인트다. EKS는 컨트롤 플레인을 업그레이드할 때 새로운 클러스터 인스턴스를 만들지 않고 기존 클러스터의 마스터 컴포넌트만 롤링 교체한다. 클러스터 식별자가 같으니 OIDC issuer URL(oidc.eks.{region}.amazonaws.com/id/{clusterId})도 동일하다. 즉 IRSA를 쓰는 워크로드(LBC, EBS CSI, Karpenter,
ArgoCD 등)는 IAM Role의 Trust Policy를 다시 잡을 필요가 없고, 파드 재시작이나 토큰 재발급도 발생하지 않는다.
이는 Blue/Green Cluster 업그레이드와 가장 큰 차이점이다. 신규 클러스터를 띄우면 OIDC issuer URL이 바뀌므로, IRSA를 쓰는 모든 IAM Role의 Trust Policy를 새 issuer로 갱신해야 한다. 운영 클러스터의 IAM Role 수십~수백 개를 한 번에 갱신해야 하는 부담은 Blue/Green Cluster 전략의 숨은 비용이다.
diff 1.30.txt 1.31.txt
# (출력 없음 — 컨테이너 이미지 동일)
kubectl get pod -A # AGE가 모두 업그레이드 이전 그대로


헷갈리기 쉬운 지점 — Control Plane만 올라간 클러스터는 정상이다
컨트롤 플레인이 1.31, kubelet은 1.30인 상태로 며칠씩 굴러가도 무방하다. 버전 스큐가 2 마이너 이내면 EKS는 이를 지원되는 운영 상태로 본다.
5. Add-on Upgrade
CoreDNS, kube-proxy, VPC CNI, EBS CSI Driver 4개 애드온을 컨트롤 플레인 버전에 맞춰 끌어올린다. 이 4개는 모두 EKS의 Managed Add-on으로 등록할 수 있는 컴포넌트로, helm으로 직접 띄우는 self-deployed 방식과 비교했을 때 다음 이점이 있다.
- EKS API로 버전 관리 —
aws eks describe-addon-versions로 K8s 버전별 호환 매트릭스를 자동 조회 - CVE 패치 자동 적용 — 보안 패치를 EKS가 직접 푸시
- IRSA 자동 연동 —
service_account_role_arn한 줄로 ServiceAccount까지 묶어줌 - Configuration Schema 검증 — 잘못된 config는 apply 단계에서 거부
자체 관리(helm) 방식은 유연성은 높지만 위 모든 것을 사용자가 직접 챙겨야 한다. 컨트롤 플레인과 강결합된 핵심 컴포넌트는 EKS Managed Add-on을 쓰는 게 정석이다.
eksctl get addon --cluster $CLUSTER_NAME
# UPDATE AVAILABLE 컬럼에서 후보 버전 확인
aws eks describe-addon-versions --addon-name coredns \
--kubernetes-version 1.31 --output table \
--query "addons[].addonVersions[:5].{Version:addonVersion,Default:compatibilities[0].defaultVersion}"
선택한 버전을 addons.tf의 각 addon 블록에 박아 넣고 terraform apply 한 번이면 끝난다.


# addons.tf 발췌
coredns = {
addon_version = "v1.11.4-eksbuild.33"
}
kube-proxy = {
addon_version = "v1.31.14-eksbuild.9"
}
헷갈리기 쉬운 지점 — VPC CNI는 호환성 표를 따로 본다
VPC CNI는 컨트롤 플레인 버전과 1:1 매핑이 아니다. "1.31에서 동작하는 VPC CNI 최신 버전"을 VPC CNI 호환성 표에서 별도로 확인하고 박아야 한다.
6. Node Upgrade — 4가지 패턴
여기서부터가 진짜다. 노드 타입별로 갱신 메커니즘이 모두 다르다.
| 패턴 | 메커니즘 | 책임 주체 | 핵심 변수 |
|---|---|---|---|
| Managed In-Place | EKS 자체 롤링 (Surge) | EKS | cluster_version, ami_id |
| Managed Blue/Green | 신규 노드그룹 생성 + 마이그레이션 | 사용자 + EKS | 신규 nodegroup block |
| Karpenter | NodeClass의 amiSelectorTerms 변경 | Karpenter 컨트롤러 | EC2NodeClass |
| Self-Managed | ASG Instance Refresh | 사용자 + ASG | ami_id |
| Fargate | Deployment rollout restart | Fargate scheduler | 없음 |
6.1 Managed Node Group — In-Place
기본 동작은 단순하다. cluster_version을 1.31로 올린 뒤 terraform apply를 돌리면, EKS가 자체적으로 새 AMI 노드를 surge로 띄우고 기존 노드를 cordon → drain → terminate 한다. 내부 동작은 EKS Managed Node Group Update API에 정의된 4단계 phase로 진행된다.
- Setup phase — 새 Launch Template 버전 생성, ASG 스케일링 정책 갱신
- Scale up phase —
update_config.max_unavailable을 고려해 surge 노드를 새 AMI로 띄움 - Upgrade phase — 기존 노드를 하나씩 cordon → drain (PDB 평가) → terminate
- Scale down phase — desired capacity를 원래 값으로 복귀
워크샵은 한 가지 변종을 추가한다. Custom AMI를 사용하는 노드그룹은 ami_id를 직접 박아두는데, 이 경우 자동 갱신 대상에서 빠진다. 따라서 사전 준비로 custom AMI를 쓰는 신규 노드그룹(custom-Y)을 하나 추가해 두고, 컨트롤 플레인 업그레이드와 함께 어떻게 처리되는지 관찰한다.
# eks.tf 발췌 — initial은 자동, custom-Y는 ami_id 명시
eks_managed_node_groups = {
initial = { ... } # ami_id 미지정 → 자동
"custom-Y" = {
ami_id = data.aws_ami.eks_1_30.id # 직접 지정
ami_type = "AL2023_x86_64_STANDARD"
}
}
cluster_version을 1.31로 바꾸고 apply 하면, initial은 1.31 AMI로 롤링되고 custom-Y는 1.30 AMI 그대로 남는다. 즉 custom AMI 노드그룹은 사용자가 따로 ami_id를 1.31용으로 갱신해야 한다.


헷갈리기 쉬운 지점 — Surge 정책의 단위
Managed Node group의 update_config.max_unavailable_percentage는 동시에 빠질 수 있는 비율이고, surge는 동시에 새로 뜰 수 있는 노드 수다. 둘이 함께 동작하므로 PDB가 빡빡하면 surge를 키우고, 그래도 안 되면 max_unavailable을 더 낮추는 식으로 조정한다.
6.2 Managed Node Group — Blue/Green
EBS PV를 쓰는 워크로드처럼 롤링이 안전하지 않은 케이스는 Blue/Green이 정공법이다. EBS Volume은 단일 노드에만 attach되므로(ReadWriteOnce), 새 노드가 같은 PV를 attach하려면 기존 노드에서 detach가 먼저 끝나야 한다. In-place 롤링이 빠르게 surge하면 detach 타이밍이 어긋나 파드가 Pending에 갇히기 쉽다. 노드그룹을 통째로 새로 띄우고 워크로드를 명시적으로 옮기면 이 race condition이 사라진다.
워크샵은 blue-mng(1.30, EBS PVC를 쓰는 앱이 떠 있음)에서 green-mng(1.31)로 옮기는 시나리오를 따라간다.
# eks.tf — green-mng 신규 추가
"green-mng" = {
cluster_version = "1.31"
taints = { dedicated = { key = "dedicated", value = "OrdersApp", effect = "NO_SCHEDULE" } }
labels = { type = "OrdersMng" }
subnet_ids = module.vpc.private_subnets
}
apply로 green-mng가 올라오면 다음 순서로 옮긴다.
- 워크로드의 nodeSelector / toleration을 green 쪽 라벨에 맞춘다 (Argo로 sync).
kubectl drain blue-mng-node로 트래픽을 옮긴다 — PDB가 막아주면 그게 정상이다.- 옮겨진 게 확인되면
blue-mng블록을 삭제하고 apply.


헷갈리기 쉬운 지점 — EBS PV의 AZ 제약
EBS Volume은 AZ에 묶여 있다. blue 노드가 떠 있던 AZ에 green 노드가 없으면 PVC가 새 노드에 바인딩되지 못해 파드가 Pending에 갇힌다. green 노드그룹의 subnet_ids를 blue와 동일한 AZ 세트로 잡아주는 게 핵심이다.
사전 예방으로는 EBS storageClass의 volumeBindingMode를 WaitForFirstConsumer로 설정해 PV 생성을 파드 스케줄링 시점까지 미루는 패턴이 일반적이다. 이러면 파드가 스케줄될 노드의 AZ에 맞춰 EBS가 생성되어 AZ 미스매치가 원천 차단된다.
6.3 Karpenter Nodes
Karpenter는 노드를 직접 만들고 회수하는 컨트롤러이므로, 업그레이드 메커니즘도 컨트롤러에게 맡긴다. ASG/노드그룹이라는 중간 레이어 없이 EC2 API를 직접 호출하는 모델이라, 업그레이드 트리거는 AMI 선택 정책 변경 한 줄이다.
Karpenter v1의 EC2NodeClass.spec.amiSelectorTerms는 EC2 AMI 검색 조건을 명시한다. alias 필드가 가장 직관적인데, al2023@latest는 AWS가 발행한 최신 AL2023 EKS-optimized AMI를 클러스터의 K8s 버전에 맞춰 자동 선택한다는 뜻이다. 즉 컨트롤 플레인이 1.31로 올라간 시점에 al2023@latest로 두면 Karpenter는 자동으로 1.31용 AMI를 사용한다.
# EC2NodeClass — amiSelectorTerms 변경 전/후
amiSelectorTerms:
- alias: al2023@v20260301 # 1.30 시점에 핀 고정한 AMI
# 변경 후:
- alias: al2023@latest # 컨트롤 플레인 버전(1.31)에 맞춰 자동 선택
이 변경 후 새 파드가 들어오면 Karpenter는 신규 NodeClaim을 1.31 AMI로 띄우고, 기존 1.30 노드는 disruption controller가 consolidation / drift 정책에 맞춰 점진적으로 정리한다. 특히 drift 감지는 현재 노드의 AMI가 NodeClass 명세와 다를 때 자동으로 감지되어 노드를 교체 대상으로 올리는 기능으로, 이번 업그레이드의 핵심 트리거다.
워크샵은 효과를 빠르게 보기 위해 checkout 디플로이먼트를 10개로 증설한다. 기존 노드 용량으로는 부족하니 Karpenter가 즉시 신규 NodeClaim을 만들어 1.31 AMI로 노드를 띄운다.
kubectl scale deploy checkout -n checkout --replicas=10
# 신규 NodeClaim이 떠서 1.31 AMI로 노드가 잡힘
kubectl get nodeclaims
# default-xxxxx r4.large spot us-west-2a ip-... True 2m


헷갈리기 쉬운 지점 — Karpenter는 "당장" 다 갈아엎지 않는다
AMI를 바꿔도 기존 1.30 노드가 즉시 사라지진 않는다. disruption.consolidationPolicy와 expireAfter 설정이 도달해야 정리가 시작된다. 빨리 보고 싶으면 노드를 직접 drain 하거나 NodeClaim을 삭제한다.
6.4 Self-Managed Node Group
Self-Managed Node Group은 사용자가 직접 만든 ASG에 EKS-optimized AMI를 얹어 워커 노드를 운영하는 형태다. EKS는 ASG의 존재를 알지 못하며, 노드 갱신은 사용자가 ASG의 Launch Template과 Instance Refresh 메커니즘으로 직접 처리한다. 즉 AMI ID 한 줄을 갱신하고 Instance Refresh를 트리거하는 게 전부지만, 그 AMI ID를 어디서 어떻게 가져오느냐가 실무 포인트다.
AWS는 EKS-optimized AMI의 최신 ID를 SSM Parameter Store에 K8s 버전 / OS / 아키텍처 / 프로파일별로 발행한다. 이 경로를 그대로 Terraform data source로 참조하면 매번 최신 AMI를 자동 추적할 수 있다.
# 1.31용 최신 AL2023 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
# ami-00e0cfd6e5895fe3a
# base.tf
self_managed_node_groups = {
self-managed-group = {
instance_type = "m5.large"
ami_id = "ami-00e0cfd6e5895fe3a" # 1.31
subnet_ids = module.vpc.private_subnets
}
}
terraform apply 하면 ASG가 Instance Refresh로 들어간다. ASG의 Instance Refresh는 MinHealthyPercentage 정책에 따라 새 EC2를 먼저 띄워 Health 통과를 확인한 뒤, InstanceWarmup 시간만큼 기다린 뒤 기존 인스턴스를 종료한다. 이 두 파라미터가 PDB와 함께 무중단 보장의 마지막 안전장치다.

그림 8 — ASG Instance Refresh 진행. 새 노드가 먼저 뜬 뒤 기존 노드가 빠진다.
kubectl get nodes -l node.kubernetes.io/lifecycle=self-managed
# ip-10-0-14-215... Ready 2m21s v1.31.14-eks-bbe087e
# ip-10-0-21-221... Ready 2m36s v1.31.14-eks-bbe087e
헷갈리기 쉬운 지점 — Self-Managed는 EKS 콘솔에서 안 보인다
EKS 콘솔의 Node groups 탭에서는 Managed만 노출된다. Self-Managed는 EC2 콘솔의 ASG에서 직접 봐야 한다. Instance Refresh 진행 상태도 ASG → Activity 탭이다.
6.5 Fargate
Fargate는 Firecracker 기반의 microVM에 파드 한 개를 격리해서 띄우는 서버리스 컨테이너 런타임이다. 클러스터에 정의된 FargateProfile의 selector(namespace + labels)에 매칭된 파드는 일반 노드가 아닌 fargate-scheduler가 처리하며, 파드 하나당 microVM 하나가 즉석에서 프로비저닝된다.
업그레이드 관점에서 핵심은 Fargate에는 노드라는 개념이 없다는 점이다. AMI도 ASG도 Launch Template도 없으며, microVM의 OS·kubelet·containerd는 AWS가 컨트롤 플레인 버전과 함께 자동으로 갱신한다. 사용자가 할 일은 새 fargate 노드(=microVM)를 만들도록 파드를 재생성하는 것뿐이다.
kubectl rollout restart deployment assets -n assets
kubectl wait --for=condition=Ready pods --all -n assets --timeout=180s
kubectl get node $(kubectl get pods -n assets -o jsonpath='{.items[0].spec.nodeName}')
# fargate-ip-10-0-35-224... Ready 60s v1.31.14-eks-f69f56f
헷갈리기 쉬운 지점 — Fargate는 노드:파드가 1:1
Fargate에서는 파드 하나당 fargate 노드 하나가 뜬다. 즉 디플로이먼트를 재시작하면 노드도 통째로 갈린다. 별도의 AMI/ASG 갱신 절차가 없는 이유다.
7. 모든 노드 1.31 확인
kubectl get node
# NAME VERSION
# fargate-ip-10-0-35-224....compute.internal v1.31.14-eks-f69f56f
# ip-10-0-0-159....compute.internal v1.31.14-eks-bbe087e
# ip-10-0-14-215....compute.internal v1.31.14-eks-bbe087e
# ip-10-0-2-146....compute.internal v1.31.14-eks-bbe087e
# ip-10-0-2-216....compute.internal v1.31.14-eks-bbe087e
# ip-10-0-21-221....compute.internal v1.31.14-eks-bbe087e
# ip-10-0-4-136....compute.internal v1.31.14-eks-bbe087e
# ip-10-0-44-71....compute.internal v1.31.14-eks-bbe087e
단계별 절차를 표로 다시 한번 정리해 보면, 동일한 컨트롤 플레인 위에서 노드 타입마다 갱신 주체와 트리거가 명확히 다르다는 점이 한눈에 들어온다.
| 단계 | 트리거 | 갱신 단위 | 사용자 개입 |
|---|---|---|---|
| Control Plane | terraform apply |
클러스터 | Terraform 변수 1개 |
| Add-ons | terraform apply |
EKS Managed Add-on | addons.tf 버전 명시 |
| Managed In-Place | EKS 자동 | 노드그룹 단위 롤링 | (Custom AMI는 사용자) |
| Managed Blue/Green | 사용자 마이그레이션 | 노드그룹 신규 + 삭제 | nodeSelector / drain |
| Karpenter | 신규 파드 진입 시 | NodeClaim 단위 | EC2NodeClass 변경 |
| Self-Managed | ASG Instance Refresh | EC2 단위 | ami_id 변경 |
| Fargate | Deployment rollout | Pod = 노드 | kubectl rollout restart |
8. 실습 인사이트
8.1 가시화의 가치 — kube-ops-view + UI 헬스체크
업그레이드는 시간이 길고 동작이 동시에 여러 곳에서 일어난다. 그래서 워크샵 가이드에 없지만 두 가지를 추가로 띄워두는 게 권장된다.
- kube-ops-view: 노드 단위로 어느 파드가 어디 떠 있는지를 실시간 시각화. 새 노드가 뜨고 기존 노드가 빠지는 흐름이 한눈에 보인다.
- UI 헬스체크 루프:
while true; do curl ...; done한 줄. PDB 없는 워크로드(=assetsrollout 등)에서 응답 끊김이 보이는지 즉시 확인 가능하다.
8.2 IRSA가 끊기지 않는 이유
컨트롤 플레인을 한 단계 올렸을 때 OIDC issuer URL이 그대로 유지된다는 것은 운영 관점에서 큰 의미가 있다. IAM Role Trust Policy의 oidc.eks.../id/... 값을 다시 잡지 않아도 된다는 뜻이며, 이는 LBC, EBS CSI, Karpenter, ArgoCD 등 IRSA를 쓰는 모든 컴포넌트가 업그레이드 동안 끊기지 않게 만드는 전제다.
8.3 노드 그룹 인벤토리 먼저
이번 워크샵의 노드 인벤토리는 Managed 2개(initial, blue-mng) + Self-Managed 1개(default-selfmng) + Karpenter NodePool 1개 + Fargate Profile 1개 + 실습 중 추가되는 custom-Y/green-mng 까지 6~7가지 노드 형태가 한 클러스터에 공존한다. 업그레이드 전에 어떤 노드그룹이 있는지, 각각이 어떤 워크로드를 들고 있는지 정리해 두지 않으면 어디까지 끝났는지 추적이 어렵다.
kubectl get node -L eks.amazonaws.com/nodegroup,karpenter.sh/nodepool
kubectl get node --label-columns=eks.amazonaws.com/capacityType,node.kubernetes.io/lifecycle'AWS > EKS' 카테고리의 다른 글
| [AEWS4] EKS Auto Mode 살펴보기 (0) | 2026.05.16 |
|---|---|
| EKS Multi-tenant SaaS GitOps 워크샵(Flux v2 + Argo Workflows) (0) | 2026.04.27 |
| [AEWS4] EKS IRSA 트러블슈팅 가이드 (0) | 2026.04.19 |
| EKS AuthN/AuthZ - AEWS4 4주차 (0) | 2026.04.13 |
| EKS 컴퓨팅과 오토스케일링 - AEWS4 3주차 (0) | 2026.04.03 |