EKS Pod Identity로 IRSA 마이그레이션 가이드 — 한 워크로드씩 옮기기
옮기기 전에 체크할 것
EKS Pod Identity가 GA된 게 2023년 말이니까 벌써 2년 반쯤 됐다. 처음 나왔을 때는 "굳이 IRSA에서 갈아탈 이유가 있나" 싶었는데, 클러스터 수가 10개를 넘어가면서 OIDC provider를 매번 등록하고 trust policy의 sub 클레임을 클러스터마다 갈아끼우는 게 슬슬 짜증나기 시작했다. 올해 들어 우리 팀도 본격적으로 옮기기 시작했고, 이제 절반쯤 마이그레이션한 상태다.
이 글은 "왜 옮겨야 하나"보다는 "어떻게 옮기나"에 집중한다. 결론부터 말하면 한 번에 다 갈아엎을 필요는 없다. 같은 클러스터, 같은 네임스페이스에서 IRSA와 Pod Identity가 공존한다.
먼저 마이그레이션을 시작하기 전에 확인해야 할 게 몇 가지 있다.
EKS 클러스터 버전이 1.24 이상이어야 한다. 우리는 1.30이라 문제없었는데, 아직 1.23 이하 쓰는 팀이 있으면 클러스터 업그레이드부터 해야 한다.
그리고 Fargate workload는 여전히 IRSA를 써야 한다. 이게 2026년 6월 기준으로도 안 풀린 제약사항이다. 우리 팀은 batch job 일부를 Fargate에서 돌리고 있는데, 그 부분은 그냥 IRSA로 남겨뒀다. 같은 클러스터에서 EC2 노드는 Pod Identity, Fargate는 IRSA로 가도 문제없이 잘 돌아간다.
AWS SDK 버전도 확인해야 한다. Pod Identity는 SDK가 컨테이너 자격증명을 알아서 가져오는 구조라, 너무 오래된 SDK는 동작 안 한다. Go SDK v2면 1.24.0 이상, boto3는 1.34.41 이상, Java SDK v2는 2.21.30 이상이 안전 마지노선이다. 실무에서는 그냥 SDK 최신으로 올리고 시작하는 게 편하다.
1단계: Pod Identity Agent 설치
EKS add-on으로 한 줄이면 끝난다.
aws eks create-addon \
--cluster-name prod-cluster \
--addon-name eks-pod-identity-agent \
--addon-version v1.3.4-eksbuild.1
Terraform이면 이렇게.
resource "aws_eks_addon" "pod_identity" {
cluster_name = aws_eks_cluster.this.name
addon_name = "eks-pod-identity-agent"
addon_version = "v1.3.4-eksbuild.1"
}
설치되면 각 노드에 DaemonSet으로 agent가 뜬다. 169.254.170.23 주소로 메타데이터 서비스 비슷하게 돌아가는데, SDK가 알아서 찾아간다. 노드 단위로 뜨니까 EC2 인스턴스가 한 5개 이상이면 메모리 점유 합쳐서 한번 체크해보길 바란다. 우리는 노드당 약 30MB 정도였다.
2단계: IAM 역할의 trust policy 새로 작성
IRSA의 trust policy는 이렇게 생겼었다. OIDC provider ARN이 클러스터마다 달라서 정말 짜증났던 부분.
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:sub": "system:serviceaccount:default:my-app"
}
}
}
Pod Identity는 훨씬 단순하다.
{
"Effect": "Allow",
"Principal": {
"Service": "pods.eks.amazonaws.com"
},
"Action": [
"sts:AssumeRole",
"sts:TagSession"
]
}
클러스터 ID가 trust policy에 안 박힌다. 그래서 dev/staging/prod 클러스터에서 같은 역할을 그대로 쓸 수 있다. 우리 팀에서 이 portability가 제일 좋았다. 환경별로 IAM 역할을 3개씩 만들던 걸 하나로 합쳤다.
3단계: Pod Identity Association 만들기
이게 IRSA와 가장 큰 차이점이다. IRSA는 ServiceAccount에 annotation을 박았는데, Pod Identity는 별도의 association 리소스를 만든다.
aws eks create-pod-identity-association \
--cluster-name prod-cluster \
--namespace default \
--service-account my-app \
--role-arn arn:aws:iam::123456789012:role/my-app-role
Terraform:
resource "aws_eks_pod_identity_association" "my_app" {
cluster_name = aws_eks_cluster.this.name
namespace = "default"
service_account = "my-app"
role_arn = aws_iam_role.my_app.arn
}
이게 처음에는 좀 어색했다. Kubernetes 리소스가 아니라 AWS API에서 관리하는 매핑이라, kubectl로 보이지 않는다. ServiceAccount yaml만 보고 "어 이게 어떤 권한 가지지?"를 알 수 없다. 대신 aws eks list-pod-identity-associations --cluster-name prod-cluster로 확인하는 식이다.
이 부분이 처음 마이그레이션할 때 멘탈에 좀 부담이었는데, 막상 적응하면 ServiceAccount yaml에 annotation 박는 것보다 깔끔하다. IAM 매핑이 AWS 쪽에 모여있다.
4단계: 한 워크로드씩 옮기기 (실전)
우리 팀이 실제로 쓴 절차다. 한 워크로드 옮기는 데 보통 30분~1시간 걸린다.
먼저 새로운 IAM 역할을 만든다. 기존 IRSA 역할을 수정하지 말고 새 역할을 만든다. 왜냐하면 trust policy 형식이 완전히 달라서, 잘못 수정하면 IRSA가 깨진다. 우리는 app-name-role-irsa와 app-name-role-pi 식으로 네이밍을 분리했다.
새 역할의 정책은 기존 IRSA 역할 정책을 그대로 복사해서 attach한다. Terraform이면 aws_iam_policy_attachment를 두 역할에 다 걸어두면 된다.
그 다음 Pod Identity Association을 만든다. 이 시점에서는 ServiceAccount에 IRSA annotation도 그대로 남아있다. Pod Identity가 IRSA보다 우선순위가 높기 때문에, association을 만든 순간부터 새로 뜨는 Pod은 새 역할로 동작한다.
# 기존 Pod 재시작 전에 association 잘 만들어졌는지 확인
aws eks describe-pod-identity-association \
--cluster-name prod-cluster \
--association-id a-xxxxxxxxxxxxxxxxx
이제 deployment를 rolling restart한다.
kubectl rollout restart deployment/my-app
Pod이 새로 뜨면 자격증명이 Pod Identity 쪽에서 떨어진다. 검증은 두 가지로 한다. 첫째, CloudTrail에서 새 역할 ARN으로 호출이 찍히는지 확인. 둘째, Pod 안에서 aws sts get-caller-identity를 쳐서 역할 확인.
kubectl exec -it deployment/my-app -- aws sts get-caller-identity
여기서 새 역할 ARN이 나오면 성공. 24시간 정도 모니터링하면서 에러 없으면 마지막으로 ServiceAccount에서 IRSA annotation을 떼고, 기존 IRSA 역할을 삭제한다. 우리는 이 마지막 단계를 일주일 미뤄두는 편이다. 롤백이 필요한 일이 생기면 annotation만 다시 붙여서 IRSA로 돌아가는 게 가장 빠르거든.
함정 두 개
옮기다가 실제로 겪은 함정 두 개를 공유한다.
첫째, session tag를 정책 조건으로 쓰는 경우. Pod Identity는 자동으로 eks-cluster-arn, eks-cluster-name, kubernetes-namespace, kubernetes-service-account를 session tag로 박아준다. ABAC을 쓰면 깔끔한데, 우리는 기존에 IRSA에서 sub 클레임으로 namespace를 식별하는 정책이 있었다. 이걸 그대로 두면 Pod Identity로 옮긴 후에 권한이 안 먹는다. session tag 기반으로 정책을 갈아야 한다.
"Condition": {
"StringEquals": {
"aws:PrincipalTag/kubernetes-namespace": "production"
}
}
둘째, 일부 helm chart가 IRSA annotation을 강제로 박는다. External Secrets Operator나 AWS Load Balancer Controller 같은 게 helm values에서 serviceAccount.annotations를 받아서 OIDC 관련 annotation을 박는다. 이 annotation이 남아있어도 Pod Identity가 우선순위는 높지만, 가독성 측면에서 헷갈리니까 values.yaml에서 빈 값으로 덮어쓰는 게 깔끔하다.
우리가 안 옮긴 것들
마이그레이션 가이드 글이지만 우리가 안 옮긴 것들도 적어둔다.
Fargate에서 도는 cron job들은 그대로 IRSA. 앞에 적은 대로 Pod Identity가 Fargate 미지원이라 강제로 남겼다.
CI/CD pipeline에서 임시로 떴다 사라지는 build pod도 그대로. 이건 Karpenter on-demand로 뜨는 EC2라 Pod Identity 가능한데, 어차피 일주일에 몇 번만 도는 거라 trust policy 그대로 두고 갈아끼울 동인이 약했다.
마무리
결국 이 마이그레이션은 "꼭 해야 하는" 일은 아니다. AWS도 IRSA deprecation 일정을 안 내놓고 있고, IRSA가 갑자기 동작 멈출 일도 없다. 다만 클러스터가 늘어나고 IAM 역할이 수십 개를 넘기 시작하면, OIDC 매번 등록하고 trust policy 클러스터별로 갈아끼우는 비용이 무시 못할 수준이 된다.
우리 팀은 이걸 옮기면서 IAM 역할 수가 약 40% 줄었다 (환경별로 분리돼있던 게 합쳐져서). Terraform module도 단순해졌고. 그래도 한 워크로드씩 천천히 가는 걸 추천한다. 한꺼번에 옮기면 권한 문제 디버깅이 지옥이다.
다음에는 Pod Identity의 session tag로 ABAC을 구성한 사례도 한번 정리해보려고 한다.