Published on

EKS에서 AWS Load Balancer Controller 403 해결법

Authors

서론

EKS에서 Ingress를 만들었는데 ALB가 생성되지 않거나, Service type=LoadBalancer가 계속 Pending 상태로 남고, aws-load-balancer-controller 로그에 403(AccessDenied/Unauthorized) 계열 오류가 찍히는 경우가 있습니다. 이 403은 단순히 “권한이 없다”로 끝나지 않습니다. 실제로는 IRSA(OIDC) 설정 누락, ServiceAccount 어노테이션 불일치, IAM 정책 버전/누락 액션, 리전/파티션/STS 엔드포인트, 태그/리소스 권한 조건 미스매치처럼 여러 원인이 겹쳐 발생합니다.

이 글에서는 403을 원인별로 분해하고, 로그→AWS API 호출→IRSA 토큰→IAM 정책까지 단계적으로 좁혀가며 해결하는 실전 절차를 정리합니다. 네트워크/IRSA 자체 문제로 TLS handshake timeout이 동반된다면 EKS TLS handshake timeout 해결 - IRSA·VPC·CoreDNS도 함께 확인하면 좋습니다.

1) 403 증상 패턴: 로그에서 먼저 분류하기

가장 먼저 컨트롤러 로그를 봅니다.

kubectl -n kube-system logs deploy/aws-load-balancer-controller \
  --tail=200 | sed -n '1,200p'

403은 대체로 아래 형태로 나타납니다.

  • AccessDenied: User: arn:aws:sts::...:assumed-role/... is not authorized to perform: elasticloadbalancing:CreateLoadBalancer
  • AccessDeniedException: Not authorized to perform sts:AssumeRoleWithWebIdentity
  • UnauthorizedOperation: You are not authorized to perform this operation.
  • AccessDenied: ... not authorized to perform: ec2:CreateSecurityGroup (또는 AuthorizeSecurityGroupIngress)
  • AccessDenied: ... not authorized to perform: elasticloadbalancing:AddTags (태그 권한)

여기서 포인트는 어떤 AWS 서비스 API에서 403이 났는지입니다.

  • sts:AssumeRoleWithWebIdentity → IRSA/OIDC/신뢰 정책 문제
  • elasticloadbalancing:* → ALB/NLB 관련 IAM 정책 누락
  • ec2:* → SG, ENI, 서브넷/태그 조회 등 EC2 권한 누락
  • iam:CreateServiceLinkedRole → ELB Service Linked Role 생성 권한 부족(초기 1회)
  • tagging:* 또는 AddTags/RemoveTags → 태그 조건/권한 문제

2) IRSA가 맞게 동작하는지: “STS AssumeRoleWithWebIdentity”부터 확인

AWS Load Balancer Controller는 보통 IRSA로 권한을 받습니다. 403이 sts:AssumeRoleWithWebIdentity라면 IAM 정책을 아무리 늘려도 해결되지 않습니다. 토큰으로 역할을 가정(assume)하는 단계에서 막혔기 때문입니다.

2.1 ServiceAccount 어노테이션 확인

kubectl -n kube-system get sa aws-load-balancer-controller -o yaml | sed -n '1,120p'

다음이 있어야 합니다.

  • eks.amazonaws.com/role-arn: arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_NAME>

어노테이션이 없거나 다른 SA에 붙어있으면 컨트롤러는 노드 IAM(혹은 아무 권한도 없는 상태)로 AWS API를 호출하다가 403을 냅니다.

2.2 Pod가 실제로 그 SA를 쓰는지 확인

kubectl -n kube-system get pod -l app.kubernetes.io/name=aws-load-balancer-controller -o jsonpath='{.items[0].spec.serviceAccountName}{"\n"}'

ServiceAccountName이 기대값과 다르면 Helm values나 manifest를 수정해야 합니다.

2.3 EKS OIDC Provider 연결 확인

IRSA는 클러스터에 OIDC Provider가 연결되어 있어야 합니다.

aws eks describe-cluster --name <CLUSTER_NAME> \
  --query "cluster.identity.oidc.issuer" --output text

출력된 issuer URL을 기반으로 IAM OIDC provider가 등록되어 있는지 확인합니다.

aws iam list-open-id-connect-providers

없다면 eksctl utils associate-iam-oidc-provider로 연결합니다.

eksctl utils associate-iam-oidc-provider \
  --cluster <CLUSTER_NAME> --approve

2.4 IAM Role의 Trust Policy(신뢰 정책) 점검

가장 자주 틀리는 지점입니다. Principal.Federated가 OIDC provider ARN을 가리키고, Conditionsub정확한 namespace/sa로 매칭돼야 합니다.

aws iam get-role --role-name <ROLE_NAME> --query 'Role.AssumeRolePolicyDocument' \
  --output json

예시(핵심만):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::<ACCOUNT_ID>:oidc-provider/oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>:sub": "system:serviceaccount:kube-system:aws-load-balancer-controller"
        }
      }
    }
  ]
}
  • subdefault:...로 되어 있거나 SA 이름이 다르면 403이 납니다.
  • aud 조건을 추가했다면 보통 sts.amazonaws.com이어야 합니다.

3) IAM 정책 누락/버전 문제: 공식 정책을 기준으로 “차이(diff)”를 줄이기

IRSA가 정상인데도 elasticloadbalancing:CreateLoadBalancer 같은 403이면, 이제는 역할에 붙은 IAM Policy가 문제입니다.

3.1 현재 Role에 붙은 정책 확인

aws iam list-attached-role-policies --role-name <ROLE_NAME>
aws iam list-role-policies --role-name <ROLE_NAME>

정책 문서(Inline이라면)를 확인합니다.

aws iam get-role-policy --role-name <ROLE_NAME> --policy-name <INLINE_POLICY_NAME>

3.2 AWS Load Balancer Controller 권장 정책 적용

가장 안전한 방법은 AWS 공식 권장 정책(버전 최신)을 그대로 사용하고, 커스텀 조건은 마지막에 최소화하는 것입니다.

  • 오래된 블로그/가이드의 정책을 복붙하면 DescribeListenerAttributes, DescribeTrustStores 등 새 액션이 빠져 403이 날 수 있습니다.
  • 특히 컨트롤러 버전을 올렸는데 IAM 정책을 그대로 두면 “갑자기 403”이 재현됩니다.

권장 정책은 AWS 문서/깃헙에서 확인해 최신으로 반영하세요. (환경마다 필요한 액션이 달라질 수 있어, 여기서는 ‘공식 정책과 diff’ 접근을 권합니다.)

3.3 자주 빠지는 권한 TOP

  • elasticloadbalancing:AddTags, elasticloadbalancing:RemoveTags
  • ec2:CreateSecurityGroup, ec2:AuthorizeSecurityGroupIngress, ec2:RevokeSecurityGroupIngress
  • ec2:DescribeSubnets, ec2:DescribeVpcs, ec2:DescribeSecurityGroups
  • iam:CreateServiceLinkedRole (초기 1회 필요할 수 있음)
  • wafv2:*, shield:*는 기능 사용 시에만

로그에 찍힌 “not authorized to perform: X”를 그대로 정책에 추가하기 전에, 왜 X가 필요한지(컨트롤러 기능/리소스 타입/애노테이션)를 먼저 확인하는 것이 좋습니다.

4) 태그 조건 때문에 나는 403: 서브넷/보안그룹 태그와 정책 Condition 충돌

조직에서 보안 강화를 위해 IAM 정책에 태그 조건을 걸어두는 경우가 많습니다. 이때 컨트롤러가 생성/수정하는 리소스에 필요한 태그가 없거나, 정책 조건이 기대하는 키가 달라 403이 발생합니다.

4.1 서브넷 태그 점검(ALB/NLB 발견 실패 + 403 혼합)

EKS에서 ALB가 서브넷을 자동 선택하려면 보통 다음 태그가 필요합니다.

  • 퍼블릭 서브넷: kubernetes.io/role/elb=1
  • 프라이빗 서브넷: kubernetes.io/role/internal-elb=1
  • 공통: kubernetes.io/cluster/<CLUSTER_NAME>=shared 또는 owned

확인:

aws ec2 describe-subnets --filters "Name=vpc-id,Values=<VPC_ID>" \
  --query 'Subnets[*].{SubnetId:SubnetId,Tags:Tags}' --output json

4.2 IAM 정책에 tag condition이 있는지 확인

예: aws:RequestTag/elbv2.k8s.aws/cluster 또는 aws:ResourceTag/... 조건이 걸려 있으면, 컨트롤러가 태그를 붙이는 순서/키가 맞지 않아 AddTags에서 403이 날 수 있습니다.

이 경우 해결책은 보통 둘 중 하나입니다.

  • 공식 정책의 태그 조건 구조로 맞추기(컨트롤러가 실제로 사용하는 키 기준)
  • 혹은 조직 표준 태그를 컨트롤러가 생성하는 리소스에 강제 주입(애노테이션/웹훅/정책 조정)

5) “노드 역할로 호출”로 인한 403: IRSA 미적용 시 흔한 함정

IRSA가 깨지면 컨트롤러는 노드 IAM Role(EC2 instance profile)의 권한으로 AWS API를 호출할 수 있습니다. 이때 노드 역할에 ELB 권한이 없으면 403이 납니다.

컨트롤러 Pod 내부에서 현재 AWS 호출 주체를 확인하는 가장 확실한 방법은 다음입니다.

5.1 Pod에 들어가 STS GetCallerIdentity 확인

컨트롤러 이미지에는 awscli가 없을 수 있으니, 임시 디버그 Pod를 띄워 확인합니다.

kubectl -n kube-system run -it --rm awscli \
  --image=amazon/aws-cli:2.15.0 \
  --serviceaccount=aws-load-balancer-controller \
  --command -- sh

쉘에서:

aws sts get-caller-identity
  • 여기서 Arnassumed-role/<ROLE_NAME>/...로 나오면 IRSA는 정상입니다.
  • assumed-role/<NODE_ROLE_NAME>/...로 나오면 IRSA가 적용되지 않은 것입니다(403의 근본 원인).

6) 컨트롤러/차트 설정 실수: clusterName, region, vpcId

권한이 충분해도 설정이 틀리면 엉뚱한 리전에 호출하거나 리소스 탐색이 실패하면서 403/404가 섞여 보일 수 있습니다.

Helm 설치 값을 점검합니다.

helm -n kube-system get values aws-load-balancer-controller -a

특히 다음을 확인하세요.

  • clusterName: 실제 EKS 클러스터 이름과 동일
  • region: 컨트롤러가 실행 중인 리전과 동일
  • vpcId: 특정 VPC를 강제했다면 올바른지

7) 재현 가능한 해결 절차(체크리스트)

403을 만났을 때 아래 순서대로 진행하면 “땜질”을 줄일 수 있습니다.

  1. 로그에서 403이 난 API 액션을 추출

    • kubectl logs ... | grep -E "AccessDenied|Unauthorized|not authorized"
  2. IRSA부터 확정

    • SA 어노테이션(role-arn)
    • Pod의 serviceAccountName
    • OIDC provider 연결
    • Role trust policy의 sub/aud 일치
    • 디버그 Pod로 aws sts get-caller-identity 확인
  3. 공식 권장 IAM 정책으로 회귀

    • 커스텀 정책을 쓰고 있다면 우선 공식 정책으로 맞춘 뒤, 필요한 조건만 최소 추가
  4. 태그 조건/서브넷 태그 점검

    • 서브넷/SG 태그
    • AddTags/RemoveTags 403이면 정책 condition과 컨트롤러 태그 키 불일치 의심
  5. 기능 사용 여부에 따른 추가 권한

    • WAF/Shield, 인증서(ACM), PrivateLink 등 사용 시 추가 액션 필요

8) 실전 예시: IRSA 신뢰 정책과 SA 어노테이션을 한 번에 고치기

아래는 “IRSA가 안 붙어서 노드 역할로 호출되던” 상황을 해결하는 전형적인 수정 흐름입니다.

8.1 ServiceAccount에 role-arn 어노테이션 추가

kubectl -n kube-system annotate sa aws-load-balancer-controller \
  eks.amazonaws.com/role-arn=arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_NAME> \
  --overwrite

8.2 Deployment 재시작

kubectl -n kube-system rollout restart deploy/aws-load-balancer-controller
kubectl -n kube-system rollout status deploy/aws-load-balancer-controller

8.3 STS 호출 주체 확인

kubectl -n kube-system run -it --rm awscli \
  --image=amazon/aws-cli:2.15.0 \
  --serviceaccount=aws-load-balancer-controller \
  --command -- sh -c 'aws sts get-caller-identity && env | grep -E "AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE"'

AWS_ROLE_ARNAWS_WEB_IDENTITY_TOKEN_FILE이 보이고, caller identity가 기대 Role이면 IRSA는 정상입니다.

9) 403 이후에 자주 이어지는 문제: ALB는 떴는데 502/504

권한(403)을 해결하고 나면 ALB는 생성되지만, 다음 단계로 502/504가 터지는 경우가 흔합니다. 이는 권한 문제가 아니라 타겟 그룹 헬스체크, 보안그룹, idle timeout, HTTP/2, readiness probe 등 L7/L4 설정 문제일 가능성이 큽니다. 이 단계는 AWS ALB 502·504 난사 - 원인별 해결 체크리스트를 참고하면 빠르게 좁힐 수 있습니다.

결론

EKS에서 AWS Load Balancer Controller 403은 대부분 다음 두 갈래로 정리됩니다.

  • (A) IRSA 자체가 실패해서 sts:AssumeRoleWithWebIdentity에서 막힘 → OIDC/Trust/SA 어노테이션/Pod SA 매칭을 고쳐야 함
  • (B) IRSA는 되지만 IAM 정책이 부족/구식/조건 충돌 → 공식 정책으로 회귀 후, 태그 조건과 기능별 권한을 최소로 추가

가장 빠른 해결법은 “로그의 액션을 정책에 계속 추가”가 아니라, STS CallerIdentity로 주체를 확정 → 공식 정책 기반으로 diff를 줄이기 → 태그 조건을 마지막에 다듬기입니다. 이 순서대로 하면 403을 재발 없이 정리할 수 있습니다.