Search

EKS AL2023 노드 교체하며 정리한 것들

들어가며

Amazon Linux 2(AL2)의 지원 종료로 클러스터에서 사용 중인 AL2 노드를 AL2023으로 전환하는 작업을 했다. 오랜만에 쿠버네티스 환경으로 돌아온 김에 이 작업을 하며 다시 짚어야 했던 개념들을 정리해 두려고 한다.
작업 자체는 노드 OS만 바꾸는 것처럼 보이지만 실제로 이해가 필요했던 건 다음 세 가지였다.
1.
EKS 노드가 어떻게 공급/관리되는가 (Managed Node Group vs Karpenter)
2.
노드를 어떻게 비우고 교체하는가 (cordon / drain)
3.
파드가 AWS 권한을 어떻게 얻는가 (IMDS vs IRSA)

1. EKS 노드 관리 두 가지 방식

EKS 클러스터를 만들면 Control Plane만 제공되고, 워커 노드(EC2)는 직접 붙여야 한다. 노드를 공급하는 방식은 크게 두 가지다.

1) Managed Node Group

EKS가 관리하는 동일 스펙 노드의 고정 묶음
내부적으로 EC2 Auto Scaling Group 위에서 동작한다.
인스턴스 타입, min/max/desired, AMI 타입을 미리 고정해서 생성
amiType(AL2 / AL2023)은 생성 후 변경 불가→ OS를 바꾸려면 신규 노드그룹을 만들어 옮기고 기존 것을 삭제하는 Blue/Green 방식이 필요
단순하고 예측 가능하지만 유연성은 낮음

2) Karpenter

별도로 설치하는 오픈소스 노드 오토스케일러
Managed Node Group이 미리 정해둔 묶음이라면 Karpenter는 스케줄 안 된 파드를 보고 맞는 노드를 즉석에서 띄운다.
참고: Karpenter 자체도 파드라 이를 배포할 작은 Managed NG가 먼저 필요함
Karpenter는 다음 오브젝트들로 동작한다.
오브젝트
역할
NodePool
어떤 노드를 띄울지 정책 • 노드 1개의 허용 범위(인스턴스 타입/CPU/AZ/spot 여부) • 풀 전체 상한(limits) + 없애는 규칙(disruption) + taint
EC2NodeClass
그 노드를 AWS에서 어떻게 만들지 실제 스펙 • AMI, 디스크, IAM 역할, IMDS 설정 등
NodeClaim
노드 1개에 대한 실제 요청/생명주기
두 축을 구분하는 게 중요함
requirements = 노드 1개의 허용 범위 (어떤 인스턴스로 띄울지)
limits = 풀 전체의 상한 (모든 노드 합산 CPU/메모리 뚜껑)
그리고 NodePool은 EC2NodeClass를 참조(nodeClassRef) 하므로 둘은 세트로 필요하다. 다른 스펙이 필요하면 EC2NodeClass를 여러 개 두면 되고(보통 워크로드별로 둠) 스펙이 같으면 하나를 여러 NodePool이 공유할 수도 있다.
프로비저닝 흐름 (scale-up)
Pending 파드 발생 → Karpenter가 관찰 → NodePool(정책) + EC2NodeClass(스펙)로 가장 적합/저렴한 인스턴스 계산 → NodeClaim 생성 → EC2 노드 join → 파드 배치
Plain Text
복사
Disruption (노드 없애기/교체)
Karpenter는 노드를 만들기만 하는 게 아니라 없애고 교체도 한다.
트리거
언제
동작
Consolidation
노드가 놀고 있을 때
없애거나 더 싼 노드로 교체 (비용 최적화)
Expiration
노드가 오래됨(expireAfter)
새 노드로 교체 (보안/패치)
Drift
노드 실제 상태 ≠ NodePool/EC2NodeClass 원하는 상태
새 노드로 교체
Karpenter 노드는 EC2NodeClass의 amiFamilyAL2 → AL2023으로 바꾸면 → 지금 노드는 AL2인데 설정은 AL2023라는 drift 감지 → 자동으로 AL2023 새 노드로 교체된다. (Managed NG처럼 수동으로 할 필요 없음)

정리

Managed Node Group
Karpenter
이름 규칙
...-nodegroup
...-nodepool
노드 공급
미리 정한 묶음(ASG)
파드 수요 보고 즉석 생성
AMI 변경
불가 → Blue/Green
EC2NodeClass 수정 → drift
설치
EKS 기본 방법
별도 설치 (기반 Managed NG 위에 공존)

2. 노드 교체 — cordon & drain

쿠버네티스엔 파드를 옮긴다(move)는 개념이 없다. 파드는 일회용이라 다른 노드에서 돌리려면 삭제 → 컨트롤러가 재생성 → 스케줄러가 재배치가 전부다. (VM처럼 live migration이 없음)
이 모델 위에서 노드를 비우는 도구가 cordon / drain이다.
명령
동작
cordon
이 노드에 새 파드 스케줄 금지
drain
이 노드의 파드를 evict → 컨트롤러가 다른 노드에 재생성

cordon → drain 순서

drain만 하면 삭제된 파드가 재생성될 때 스케줄러가 아직 스케줄 가능한 이 노드로 도로 올려버릴 수 있다. cordon으로 먼저 막아둬야 재생성분이 확실히 다른 노드로 간다.
drain 자체도 하드 킬이 아니라 graceful termination + PodDisruptionBudget 준수로, 가용성 규칙(최소 N개 생존)을 지키며 하나씩 비운다.
배치할 노드를 직접 지정하지 않는 이유는 자원/AZ 분산/제약 조건을 종합해 최적 배치하는 건 스케줄러 몫이기 때문. 특정 노드로 보내야 하면 nodeSelector 같은 제약을 주면 된다.

노드 교체가 곧 OS 교체인 이유

파드는 노드에 설치된 게 아니라 선언(spec) 이고 그 선언은 중앙(etcd)에 있다. 컨테이너 이미지는 userland를 통째로 들고 다녀서 AL2/AL2023 어느 노드에서나 동일하게 뜬다. 따라서:
AL2023 AMI로 새 노드 생성 → 기존 AL2 노드 cordon → drain → 파드가 AL2023 노드에 재생성됨 → 기존 AL2 노드 삭제
Plain Text
복사
즉 OS를 in-place로 바꾸는 게 아니라 노드를 통째로 갈아끼우고 파드를 재생성하는 것이다.
단, PV(EBS)를 쓰는 stateful 워크로드는 새 노드가 같은 AZ에 있어야 볼륨이 재부착된다. 신규 노드그룹은 반드시 같은 서브넷/AZ로 맞춰야 한다.

3. 파드는 AWS 권한을 어떻게 얻나 — IMDS vs IRSA

AL2023 전환에서 문제가 가장 많이 나는 지점. 파드가 AWS 자격증명을 얻는 방식이 두 가지인데 AL2와 AL2023의 기본값이 달라서 생기는 이슈이다.

IMDS (Instance Metadata Service)

모든 EC2에서 169.254.169.254로 접근하는 특수 HTTP 서버. Nitro(가상화 플랫폼) 계층이 응답(끄거나 위조 불가)
EC2에 IAM 역할(instance profile)을 붙이면 → 그 역할의 임시 자격증명(access key + secret + session token) 이 IMDS 경로(/latest/meta-data/iam/security-credentials/<role>)에 서빙된다.
AWS SDK/CLI는 다른 자격증명이 없으면 자동으로 IMDS에서 가져다 쓴다.
흐름:
IAM 역할(권한+신뢰정책, 자격증명 없음) → EC2/Nitro가 자동으로 STS에 요청 → STS가 임시 자격증명 발급(예: 1시간) → Nitro가 IMDS 경로에 캐시/서빙 → 만료 전 자동 갱신(계속 롤링) → 노드 안 SDK가 IMDS에서 읽어 사용
Plain Text
복사
단점은 단위가 노드라서 한 노드 위 모든 파드가 같은 역할을 공유해 세밀한 권한 분리 불가
IMDSv2와 hop limit — AL2/AL2023 차이
HttpTokens
HopLimit
AL2
optional (IMDSv1 허용)
2
AL2023
required (IMDSv2 강제)
1
IMDS 응답 패킷엔 hop limit(IP TTL)이 있고 파드는 자기 네트워크 네임스페이스에 있어서 IMDS까지 홉이 1개 더 든다.
AL2(hop 2): 파드도 IMDS 도달 가능
AL2023(hop 1): 파드 요청의 응답이 도중에 죽어 IMDS 접근 실패
→ AL2023이 hop limit을 1로 낮춘 건 보안 목적. 파드가 노드 역할 자격증명을 함부로 못 가져가게 막고 IRSA로 유도

IRSA (IAM Roles for Service Accounts)

파드(ServiceAccount)마다 전용 IAM 역할을 부여하는 방식
ServiceAccount(SA) = 파드의 신원. 모든 파드는 어떤 SA로 실행되고 미지정 시 해당 namespace의 default SA로 실행됨(pod.spec.serviceAccountName)
SA에 eks.amazonaws.com/role-arn 어노테이션으로 IAM 역할을 붙이면 그 SA를 쓰는 파드가 해당 역할을 받는다.
결과적으로 SA 단위로 파드마다 다른 권한 부여 가능
메커니즘: OIDC federation
OIDC(OpenID Connect)
OAuth2 위에 세운 인증 프로토콜
서명된 토큰(JWT)로 신뢰 하는 방식이다
신원 공급자(IdP)가 신원 맞다고 서명한 JWT를 발급
JWT = header.payload.signature 구조, payload엔 sub/aud/exp 등 claims 포함
신뢰하는 쪽은 IdP의 공개키(JWKS)로 서명만 검증하면 됨, 매번 IdP에 물어볼 필요 없음
IRSA에 적용:
[사전 설정] 1. 클러스터는 개인키/공개키 쌍을 갖고 있음 (SA 토큰 서명용) 2. 공개키를 AWS IAM에 등록 → 이 클러스터가 서명한 토큰의 신원 검증을 AWS가 대신 해줌 3. IAM 역할을 권한별로 여러 개 생성, 각 역할에 신뢰정책(trust policy) 설정 - 역할A: sub = system:serviceaccount:default:s3-reader-sa 만 허용 - 역할B: sub = system:serviceaccount:default:dynamo-writer-sa 만 허용 4. SA도 필요한 권한 조합별로 여러 개 생성 - 각 SA에 role-arn 어노테이션으로 사용할 역할 1개씩 지정 [파드 실행 시 — 자동] 1. 파드가 SA X로 실행됨 2. API 서버가 sub=X인 JWT를 클러스터 개인키로 서명, 파드에 파일로 주입 (role-arn도 env로 함께 주입) 3. SDK가 role-arn + JWT를 STS에 제출 4. STS 검증 두 단계: (a) 신원 검증: 공개키로 서명 확인 → 이 클러스터가 발급한 토큰인지 확인 (b) 권한 검증: 요청한 역할의 신뢰정책 확인 → sub=X가 허용된 sub인지 확인 둘 다 통과 시 임시 자격증명 발급 5. 파드가 자격증명으로 AWS 사용 (IMDS 미경유)
Plain Text
복사
토큰은 마운트된 파일에서 오고 자격증명 교환은 STS API로 하므로 IMDS hop limit과 무관하고 파드별 최소 권한이 가능

IMDS vs IRSA 정리

IMDS
IRSA
단위
노드 (모든 파드가 노드 역할 공유)
ServiceAccount (파드별 역할)
자격증명 경로
Nitro → STS → IMDS 서빙
OIDC 토큰 → STS AssumeRoleWithWebIdentity
IMDS 의존
O (hop limit 영향 받음)
X (파일+STS)
AL2023 hop 1
파드 접근 실패 위험
무영향
참고: 자격증명 탐색 순서
환경변수 → 공유파일(~/.aws) → IRSA → IMDS
IRSA가 설정돼 있으면 IMDS까지 가지 않음
노드 공급 방식이 교체 절차를 정하고 cordon/drain이 그 절차를 실행하고 IRSA가 그 과정에서 끊길 수 있는 AWS 권한을 미리 끊어주는 구조이다.