들어가며
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의 amiFamily를 AL2 → 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 권한을 미리 끊어주는 구조이다.