IT/AWS

EKS Pod Identity로 IRSA 마이그레이션, 이렇게 한다

gfrog 2026. 5. 29. 06:17
반응형

EKS 클러스터에서 워크로드 권한 부여, 어떻게 바뀌고 있나

EKS 클러스터에서 워크로드가 AWS API를 호출할 때 권한을 어떻게 주느냐는 꽤 오래된 주제다. 한참 동안은 IRSA(IAM Roles for Service Accounts)가 사실상의 표준이었는데, 2023년 말에 Pod Identity가 나오고 2024-2025년을 거치면서 AWS도 "신규 워크로드는 Pod Identity 권장"으로 톤이 바뀌었다. 우리 팀도 작년 말부터 클러스터 두 개를 점진적으로 옮겼는데, 그 과정에서 정리한 실무 노트다.

IRSA를 완전히 버리는 건 아니다. Fargate는 여전히 IRSA만 지원하고, 어떤 도구들은 아직 Pod Identity 어노테이션 매핑이 어색하다. 그래도 "신규 SA는 Pod Identity, 기존은 점진 이관"이 합리적인 기본값이다.

왜 굳이 옮기는가

Pod Identity가 IRSA보다 좋다고 느끼는 지점은 사실 화려한 게 없다. 그냥 운영 부담이 줄어든다.

첫째, OIDC provider를 클러스터마다 만들고 trust policy에 thumbprint를 박는 작업이 없어진다. 클러스터를 새로 만들 때마다 이 OIDC provider URL을 신뢰 정책에 추가해주는 게 묘하게 귀찮은 작업이었다. 멀티 클러스터 환경에서 같은 IAM Role을 여러 클러스터의 SA에 매핑하려면 trust policy 안의 StringEquals 배열을 계속 늘려야 했고, 클러스터를 삭제하면 잊지 말고 정리도 해야 했다.

둘째, 같은 Role을 여러 클러스터에서 재사용하기가 훨씬 깔끔해진다. trust policy는 그냥 pods.eks.amazonaws.com 서비스 principal 하나만 신뢰하면 되고, 어느 클러스터의 어떤 SA가 그 Role을 쓸지는 eks create-pod-identity-association 호출로 결정한다. 권한 자체(Role)와 바인딩(association)이 분리되니까 Terraform 모듈 구조도 단순해진다.

셋째, 세션 태그가 기본으로 붙는다. eks-cluster-name, kubernetes-namespace, kubernetes-service-account 같은 태그가 STS 세션에 자동으로 들어가기 때문에 ABAC 정책 쓰기가 한결 쉬워졌다. 우리 팀에서는 같은 Role을 여러 네임스페이스에서 공유하면서 네임스페이스 태그로 S3 버킷 prefix를 격리하는 패턴에 이걸 쓰고 있다.

사전 조건과 함정

작업 들어가기 전에 확인해야 할 것들이 몇 개 있다.

EKS 컨트롤 플레인 버전은 최소 1.24인데, 솔직히 1.29 이하 클러스터에서는 Pod Identity Agent가 가끔 이상하게 동작하는 케이스를 봤다. 가능하면 1.29 이상에서 시작하길 권한다. Fargate 프로파일에서 돌아가는 Pod는 Pod Identity가 동작하지 않으니 IRSA를 계속 유지해야 한다. 같은 클러스터 안에서 EC2 노드 워크로드만 Pod Identity로 옮기고 Fargate 워크로드는 IRSA로 남겨두는 혼용은 공식적으로 지원된다.

그리고 한 가지, trust policy에 aws:SourceArn 조건을 빠뜨리면 안 된다. Pod Identity의 trust 서비스 principal은 단순히 pods.eks.amazonaws.com인데, 이 조건이 없으면 같은 AWS 계정 내 다른 EKS 클러스터에서도 이 Role을 가져갈 수 있는 이론적 위험이 있다. 보안 검토에서 가장 먼저 지적당하는 부분이니 처음부터 박아두는 게 좋다.

마이그레이션 흐름

핵심은 한꺼번에 끄지 말고 병행 운영하는 거다. IRSA 어노테이션(eks.amazonaws.com/role-arn)이 붙어 있는 SA에 Pod Identity association을 추가로 만들면 어떻게 되느냐? 우선순위는 Pod Identity가 더 높다. Pod에 Pod Identity Agent가 환경 변수(AWS_CONTAINER_CREDENTIALS_FULL_URI)를 주입해주는데, AWS SDK 자격 증명 체인이 이 환경 변수를 IRSA의 OIDC 토큰 파일보다 먼저 탐색한다.

이 동작 덕분에 다음과 같은 순서로 안전하게 이관할 수 있다.

  1. 클러스터에 Pod Identity Agent 애드온을 설치한다. EKS Add-on으로 eks-pod-identity-agent를 추가하면 끝이다. 데몬셋이 각 노드에 떠서 169.254.170.23 주소로 자격 증명을 서빙한다.
  2. 새 trust policy를 가진 Role을 만들거나 기존 Role의 trust policy에 Pod Identity 항목을 추가한다. 기존 Role을 재사용하고 싶으면 trust policy에 두 종류의 statement를 같이 둔다. 하나는 OIDC provider를 신뢰하는 기존 IRSA용, 하나는 pods.eks.amazonaws.com을 신뢰하는 Pod Identity용. 둘 다 살아있으면 어느 쪽 경로로 호출이 들어와도 통과된다.
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E:sub": "system:serviceaccount:billing:reporter"
        }
      }
    },
    {
      "Effect": "Allow",
      "Principal": { "Service": "pods.eks.amazonaws.com" },
      "Action": ["sts:AssumeRole", "sts:TagSession"],
      "Condition": {
        "ArnEquals": {
          "aws:SourceArn": "arn:aws:eks:ap-northeast-2:123456789012:cluster/prod-a"
        }
      }
    }
  ]
}
  1. Pod Identity association을 만든다. SA와 Role을 묶는 한 줄짜리 작업이다.
aws eks create-pod-identity-association \
  --cluster-name prod-a \
  --namespace billing \
  --service-account reporter \
  --role-arn arn:aws:iam::123456789012:role/billing-reporter

이 시점부터 해당 네임스페이스의 Pod가 재시작되면 Pod Identity 경로로 자격 증명을 받기 시작한다. 기존 Pod는 다음 롤링 재시작까지는 IRSA 경로로 그대로 돈다. 우리 팀은 deployment마다 일부러 강제 재시작을 하지 않고 자연스러운 배포 사이클에 맞춰 옮겼다.

  1. CloudTrail로 호출 경로를 검증한다. Pod Identity로 받은 자격 증명은 userIdentity.sessionContext.sessionIssuer.userName에 Role 이름이, sessionContext.attributes에 세션 태그가 노출된다. IRSA로 받은 자격 증명은 AssumeRoleWithWebIdentity 이벤트가 함께 보인다. 한쪽이 사라지면 이관 완료다.
  2. IRSA 잔여물을 정리한다. SA의 eks.amazonaws.com/role-arn 어노테이션 제거 → trust policy에서 OIDC statement 삭제 → 더 이상 어느 SA도 OIDC를 안 쓴다는 게 확실해지면 OIDC provider 자체도 IAM에서 지운다. 우리는 이 마지막 단계까지 한 달 정도 보수적으로 기다렸다.

한 번 데인 부분

처음에 별생각 없이 옮기다가 잠깐 멈춰야 했던 케이스가 두 개 있었다.

환경 변수를 가로채는 init container. 우리 어떤 워크로드는 init container에서 AWS_* 환경 변수를 다시 설정하는 패턴이 있었다. Pod Identity Agent가 주입한 AWS_CONTAINER_CREDENTIALS_FULL_URIAWS_CONTAINER_CREDENTIALS_FULL_URI_TOKEN이 덮어써져서 main container가 자격 증명을 못 받았다. init script에서 이 변수들은 절대 건드리지 않도록 가드를 추가했다.

오래된 AWS SDK. Java SDK v1처럼 컨테이너 자격 증명 공급자를 늦게 추가한 SDK들은 AWS_CONTAINER_CREDENTIALS_FULL_URI 환경 변수를 모른다. 결과적으로 인스턴스 메타데이터 서비스(IMDS)로 빠지면서 노드 IAM Role이 사용되는 사고가 한 번 났다. 이건 사실 SDK를 올리는 게 정답인데, 당장 그게 안 되는 레거시는 IRSA로 남겨두는 게 안전하다.

마무리

전체 그림 자체는 어렵지 않다. trust policy 두 벌로 병행 운영하면서 SA 단위로 천천히 옮기는 게 핵심이다. 변경 자체보다도 "한 번에 끄지 말 것", "Fargate와 EC2를 구분할 것", "SDK 버전 확인" 같은 기본기에서 사고가 더 많이 나는 느낌이었다.

다음에는 Pod Identity association을 Terraform 모듈로 어떻게 묶어두면 깔끔한지 정리해보려고 한다. 우리는 결국 pod_identity_associations 변수를 모듈 레벨로 뽑아내는 형태로 가닥을 잡았는데, 이게 모두에게 맞는 건지는 아직 자신이 없다. 다른 팀에서는 어떻게 관리하시는지 궁금하다.

반응형