지난주에 팀 내부에서 조용히 진행해온 일 하나가 있다. EKS 클러스터 12개에 걸쳐 흩어져 있던 IRSA(IAM Roles for Service Accounts) 설정을 Pod Identity로 이관하는 작업이다. 큰 장애 없이 끝났지만, 중간에 몇 번은 새벽에 알람이 울려서 눈이 번쩍 떠졌다. 그 삽질 기록.
시작한 이유는 단순했다. 클러스터를 하나 더 늘리는데, 또 OIDC provider를 만들고, trust policy에 system:serviceaccount:*:* 문자열을 하나하나 박고 있는 내가 좀 한심하게 느껴졌다. 마침 최근에 나온 자료들에서도 신규 워크로드는 Pod Identity 쪽으로 가는 게 대세라고 얘기하고 있었고. 참고로 IRSA가 deprecated 된 건 아니다. 그냥 새로 만드는 건 이제 Pod Identity로 가자는 정도의 분위기다.
OIDC를 없앨 필요는 없더라
처음엔 이걸 "이관 프로젝트"로 접근했다. 모든 IRSA를 걷어내고 Pod Identity로 갈아엎겠다는 야심. 근데 실제로 해보니 그럴 필요가 없었다.
우리 클러스터 중 몇 개는 Fargate 프로파일을 쓰는 배치 워크로드가 살아있는데, Pod Identity는 Fargate에서 아직 지원이 안 된다. 정확히 말하면, Pod Identity Agent가 DaemonSet으로 노드에 뜨는 구조라 Fargate처럼 노드가 없는 환경에선 애초에 붙을 곳이 없다.
그래서 결론은 이렇게 정리했다.
- EC2 노드 워크로드 → Pod Identity로 전환
- Fargate 워크로드 → IRSA 유지
- 두 개가 한 클러스터에서 공존해도 EKS는 알아서 처리해준다
이걸 알기 전까지는 "Fargate 걷어내는 것부터 계획 짜야 하나" 하고 있었다. 아니었다. 그냥 냅두면 된다.
같은 SA에 둘 다 붙어있으면 무슨 일이 일어나나
이게 좀 애매했다. 우리는 workload를 점진적으로 옮기는 방식을 택했는데, 그 말은 이관 도중에는 하나의 ServiceAccount에 IRSA 어노테이션(eks.amazonaws.com/role-arn)과 Pod Identity association이 동시에 붙어있는 상태가 존재한다는 뜻이다.
이 상태에서 Pod가 뜨면 어느 자격증명을 쓸까? 한참 문서를 뒤졌는데, Pod Identity가 우선이다. EKS의 mutating webhook이 두 설정을 다 보긴 하는데, Pod Identity 환경변수(AWS_CONTAINER_CREDENTIALS_FULL_URI 등)를 우선적으로 주입한다. IRSA용 AWS_WEB_IDENTITY_TOKEN_FILE 은 무시된다.
이걸 알고 나니까 이관 순서가 단순해졌다.
- 새 Pod Identity association을 만든다 (같은 IAM 역할이든 새 역할이든 상관 없음)
- Deployment rollout — 이때부터 이미 Pod Identity가 쓰이기 시작한다
- CloudTrail로 며칠 지켜본다. IRSA 쪽 assume-role 호출이 완전히 사라졌는지
- 사라진 게 확인되면 SA에서
eks.amazonaws.com/role-arn어노테이션과 원본 IAM 역할을 정리
문서만 보면 이 우선순위가 좀 헷갈리는데, 실제로는 이렇게 돌더라.
Trust policy 정리하다가 뒤통수 맞은 이야기
Pod Identity로 옮긴 후에 IAM 역할의 trust policy를 정리하는데, 여기서 한 번 사고가 났다.
기존 IRSA용 trust policy는 이렇게 생겼다.
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/ABCDEF..."
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.ap-northeast-2.amazonaws.com/id/ABCDEF...:sub": "system:serviceaccount:prod:app-a"
}
}
}]
}
Pod Identity로 옮기면 이걸 이걸로 바꿔야 한다.
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "pods.eks.amazonaws.com" },
"Action": ["sts:AssumeRole", "sts:TagSession"]
}]
}
sts:TagSession을 빼먹지 말자. 이거 없으면 Pod Identity가 세션 태그 붙일 때 실패하는데, 그 에러가 즉시 폭발하지 않고 애플리케이션이 STS 콜을 처음 하는 시점에 이상하게 튕긴다. 우리 팀에선 어떤 배치 잡이 새벽 2시에 처음 돌면서 이 문제로 실패했다. 원인 찾는 데 40분 걸렸다.
그리고 하나 더. Pod Identity로 옮긴 후에 예전 IRSA trust policy를 지울 때 급하게 지우지 말자. 우리는 이관 완료됐다고 판단하고 몇 개 역할의 federated trust를 걷어냈는데, 알고 보니 다른 클러스터의 CI 워크로드가 아직 그 역할을 IRSA로 assume하고 있었다. 클러스터 간 role reuse가 있으면 이런 함정이 있다.
교훈: CloudTrail에서 AssumeRoleWithWebIdentity 이벤트가 특정 역할에 대해 최소 2주 정도 안 나오는 걸 확인한 후에 trust 정리하자.
Terraform 코드가 확실히 깔끔해지긴 한다
이건 좀 만족스러웠던 부분이다. IRSA 시절 우리 모듈은 이랬다.
data "aws_iam_policy_document" "assume_role" {
statement {
effect = "Allow"
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = [var.oidc_provider_arn]
}
condition {
test = "StringEquals"
variable = "${var.oidc_provider_url}:sub"
values = ["system:serviceaccount:${var.namespace}:${var.service_account}"]
}
condition {
test = "StringEquals"
variable = "${var.oidc_provider_url}:aud"
values = ["sts.amazonaws.com"]
}
}
}
클러스터마다 OIDC provider URL/ARN이 다르니까 이걸 다 변수로 받아야 했고, 클러스터 새로 만들 때마다 provider도 새로 만들어야 했고. 모듈 파라미터가 지저분했다.
Pod Identity로 바꾸니까 trust policy는 상수가 됐다. Principal: pods.eks.amazonaws.com 딱 하나. 그리고 실제 바인딩은 aws_eks_pod_identity_association 리소스로 처리한다.
resource "aws_eks_pod_identity_association" "app_a" {
cluster_name = var.cluster_name
namespace = "prod"
service_account = "app-a"
role_arn = aws_iam_role.app_a.arn
}
한 클러스터에서 만든 IAM 역할을 다른 클러스터에서 재사용하는 것도 훨씬 쉬워졌다. 예전엔 클러스터별로 OIDC 조건이 다르니까 사실상 role도 클러스터별로 나눠야 했는데, 이제는 아니다.
세션 태그가 예상보다 유용했다
Pod Identity의 세션 태그는 처음엔 부가 기능이라고 생각했는데, 로그 조사할 때 꽤 도움이 됐다.
Pod Identity로 assume된 세션은 CloudTrail 이벤트에 자동으로 eks-cluster-name, kubernetes-namespace, kubernetes-service-account, kubernetes-pod-name 같은 태그가 붙는다. IRSA 시절엔 CloudTrail만 봐서는 어느 클러스터, 어느 namespace의 파드가 이 API를 호출한 건지 알 수가 없었다. 세션 이름이 뭔가 랜덤한 문자열이라 유추도 어려웠고.
이제는 이런 쿼리가 된다.
{ $.userIdentity.sessionContext.attributes.creationDate > ...
&& $.requestParameters...
&& $.userIdentity.sessionContext.assumedRoleUser.arn = ...
&& contains($.additionalEventData.sessionTags, "prod/app-a") }
정확한 신택스는 Athena/CloudTrail Lake 쿼리로 잡아야 하지만, 여튼 "누가 이 S3 오브젝트를 지웠나?"에 대한 답이 클러스터/네임스페이스/파드까지 나온다. 이거 하나로 사후 조사 시간이 확 줄었다.
지금 어디까지 왔나
12개 클러스터 중 9개는 이관 완료. 남은 3개는 Fargate가 섞여 있어서 부분 이관 상태로 유지 중이다. 사실 남은 것도 굳이 끝까지 밀어붙일 이유는 없는 것 같다. 한 클러스터에서 두 방식이 공존해도 동작에는 문제가 없고, 새로 만드는 워크로드만 Pod Identity로 통일하면 시간이 알아서 해결해줄 것 같다.
만약 지금 시작하는 분이라면, 우선 새 워크로드부터 Pod Identity로 만들어보고, 기존 걸 옮기는 건 이관할 명확한 이유가 생겼을 때(예: OIDC provider 관리 부담, ABAC 필요, IAM 역할 클러스터 간 재사용) 그때 하는 게 낫다고 본다. 하나씩, 배포 사이클 안에서.
혹시 Fargate와 EC2가 섞인 클러스터에서 다르게 접근하시는 분 있으면 코멘트로 알려주시라. 우리는 부분 이관 상태를 유지하는 게 답이라고 결론 냈는데, 더 나은 방법이 있을 수도 있으니까.
태그: EKS, PodIdentity, IRSA, AWS, Kubernetes, 마이그레이션
'IT > AWS' 카테고리의 다른 글
| EKS Auto Mode 실전 도입 가이드 (0) | 2026.08.29 |
|---|---|
| ALB 뒤에서 502가 뚝뚝 떨어지던 날의 회고 (0) | 2026.08.10 |
| Karpenter WhenEmptyOrUnderutilized 켰다가 밤새운 이야기 (0) | 2026.08.04 |
| Cluster Autoscaler에서 Karpenter로, 우리는 정말 이득이었나 (0) | 2026.07.25 |
| ALB deregistration_delay를 30초로 뒀더니 배포마다 5xx가 튀던 이야기 (0) | 2026.07.16 |