Published on

EKS AWS Load Balancer Controller 설치 후 403 해결

Authors

서론

EKS에서 AWS Load Balancer Controller(이하 LBC)를 설치하고 Ingress를 붙였는데, 기대했던 200/302가 아니라 403 Forbidden이 떨어지는 경우가 생각보다 흔합니다. 문제는 “403”이라는 결과가 너무 포괄적이라는 점입니다.

  • ALB가 내는 403인지(예: WAF, listener rule, fixed-response)
  • 애플리케이션이 내는 403인지(예: 인증/인가 미들웨어)
  • LBC가 리소스를 제대로 만들지 못해 엉뚱한 대상/규칙으로 라우팅되는지(권한/어노테이션/타겟그룹)

이 글은 LBC 설치 직후 또는 Ingress 적용 직후에 맞닥뜨리는 403을 “어디서 발생했는지”부터 분리하고, 가장 빈도가 높은 원인들을 로그/명령어 기반으로 끝까지 해결하는 체크리스트로 구성했습니다.

1) 403의 “발생 지점”부터 확정하기

403을 해결하는 가장 빠른 방법은 “누가 403을 반환했는지”를 확정하는 것입니다. 같은 403이라도 해결책이 완전히 달라집니다.

1.1 ALB/WAF가 반환하는 403인지 확인

가장 먼저 브라우저가 아니라 curl -v로 헤더를 봅니다.

curl -vk https://example.your-domain.com/ \
  -H 'Host: example.your-domain.com'

아래 힌트가 있으면 ALB/WAF 쪽일 확률이 큽니다.

  • 응답 헤더에 server: awselb/2.0가 보임
  • 본문이 AWS 고정 에러 페이지 형태
  • WAF를 붙였다면 x-amzn-ErrorType, x-amzn-RequestId 등 단서가 보이기도 함

만약 WAF가 원인이라면, LBC 자체 문제가 아니라 WAF 규칙/로그 분석이 핵심입니다. 이 케이스는 아래 글이 가장 직접적입니다.

1.2 애플리케이션이 반환하는 403인지 확인

ALB를 통과해 Pod까지 갔는데 앱이 403을 내는 경우도 많습니다.

  • 응답 헤더에 앱 프레임워크 흔적(예: server: nginx, x-powered-by, set-cookie 등)
  • 동일 경로를 Pod IP로 직접 호출하면 같은 403이 재현됨

이 경우는 Ingress/ALB가 아니라 인증/인가 설정, JWT/세션, 프록시 헤더(X-Forwarded-For/Proto) 처리 등을 봐야 합니다.

1.3 LBC가 “엉뚱한 규칙”을 만들어 403이 나는지 확인

ALB는 listener rule에 fixed-response 403을 걸 수 있습니다. 의도치 않게 기본 규칙이 403으로 남아 있거나, host/path 매칭이 실패해 기본 규칙으로 떨어지면 403이 발생합니다.

다음 순서로 확인합니다.

  1. Ingress가 의도한 host/path를 갖고 있는지
  2. ALB listener rule이 Ingress spec과 일치하는지
  3. rule 우선순위(priority) 충돌이 없는지
kubectl get ingress -A -o wide
kubectl describe ingress -n <ns> <ingress-name>

2) Controller 로그로 “권한/리소스 생성 실패”부터 제거

LBC 설치 직후 403이 뜬다면, 의외로 컨트롤러가 필요한 AWS 리소스를 제대로 만들지 못한 상태에서 기존 ALB/리스너가 남아 기본 403을 내는 경우가 있습니다. 따라서 먼저 컨트롤러 로그를 봅니다.

kubectl -n kube-system get deploy | grep -i load-balancer
kubectl -n kube-system logs deploy/aws-load-balancer-controller --tail=200

로그에서 특히 자주 보이는 패턴:

  • AccessDenied / UnauthorizedOperation → IAM 권한/IRSA 문제
  • failed to reconcile → 어노테이션 값 오류, 서브넷 태그, SG 참조 문제
  • WebIdentityErr / AssumeRoleWithWebIdentity 실패 → IRSA 신뢰 정책/서비스어카운트 연결 문제

IRSA가 얽힌 AccessDenied는 403과 동반되는 대표 케이스입니다. 아래 글이 디버깅 흐름을 잘 정리합니다.

2.1 IRSA가 제대로 붙었는지(서비스 어카운트/어노테이션)

LBC는 보통 IRSA를 사용합니다. 서비스 어카운트에 role ARN이 붙었는지 확인합니다.

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

기대값(예시):

metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::<ACCOUNT_ID>:role/AWSLoadBalancerControllerRole

그리고 파드 환경변수/토큰 마운트가 정상인지도 봅니다.

kubectl -n kube-system get pod -l app.kubernetes.io/name=aws-load-balancer-controller
kubectl -n kube-system describe pod <controller-pod>

2.2 IAM 정책이 최신/충분한지

LBC는 버전에 따라 필요한 권한이 달라질 수 있습니다. “예전에 쓰던 정책”을 그대로 재사용하면 일부 API에서 AccessDenied가 나며 리소스가 반쯤 생성되고, 최종적으로 트래픽이 기본 403으로 떨어질 수 있습니다.

권장 흐름:

  • 사용 중인 LBC 버전 확인
  • AWS 공식 문서의 해당 버전 정책(JSON)으로 업데이트
  • 최소한 다음 계열 권한이 막히지 않는지 확인
    • elasticloadbalancing:*(특히 listener/rule/targetgroup)
    • ec2:Describe*, ec2:CreateSecurityGroup, ec2:AuthorizeSecurityGroupIngress
    • iam:CreateServiceLinkedRole(필요 시)
    • waf-regional:*/wafv2:*(WAF 연동 시)

3) Ingress 어노테이션 실수로 인한 “기본 403” 패턴

권한이 정상인데도 403이면, 다음으로 흔한 원인이 Ingress 어노테이션/스펙 불일치입니다.

3.1 ingressClassName / ingress.class 불일치

클러스터에 NGINX Ingress, ALB Ingress 등 여러 컨트롤러가 공존하면, Ingress가 원치 않는 컨트롤러에 잡히거나 아무도 잡지 않는 경우가 있습니다. 그 결과 ALB 규칙이 기대와 다르게 남아 403이 날 수 있습니다.

Ingress에 명시적으로 지정하세요.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app
  namespace: default
spec:
  ingressClassName: alb
  rules:
    - host: example.your-domain.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: app-svc
                port:
                  number: 80

(구형 클러스터/리소스에서는 kubernetes.io/ingress.class: alb 어노테이션을 쓰기도 하지만, 가능하면 spec.ingressClassName를 권장합니다.)

3.2 host/path가 실제 요청과 매칭되지 않아 기본 규칙으로 떨어짐

예를 들어 Ingress에 host를 example.com으로 걸어놓고, 실제 요청은 alb-dns-name.amazonaws.com으로 때리면 host 매칭이 실패합니다. 그러면 listener 기본 규칙(종종 fixed 404/403)에 걸립니다.

해결:

  • Route53/CNAME 또는 테스트 시 curl -H 'Host: ...'로 host를 맞추기
  • Ingress에 host를 비우고(와일드카드) 테스트 후 점진적으로 고정
curl -vk https://<ALB_DNS_NAME>/ -H 'Host: example.your-domain.com'

3.3 action/조건 어노테이션 오타로 fixed-response가 생성되는 경우

LBC는 alb.ingress.kubernetes.io/actions.<name> 같은 고급 어노테이션으로 redirect/fixed-response를 만들 수 있습니다. JSON이 깨지거나 serviceName 참조가 틀리면 의도치 않게 fixed-response가 남을 수 있습니다.

Ingress 이벤트를 확인하세요.

kubectl describe ingress -n <ns> <ingress-name> | sed -n '/Events:/,$p'

4) “ALB는 정상인데 403”일 때: WAF, 인증, 헤더

컨트롤러/규칙이 정상인데도 403이면, 실제로는 보안 계층에서 막는 경우가 많습니다.

4.1 WAF 연동 시: 차단 로그부터 본다

WAF가 붙어 있다면 추측하지 말고 로그로 확정하는 게 가장 빠릅니다. 특히 다음 조건에서 WAF 403이 자주 발생합니다.

  • Host 헤더가 예상과 다름(테스트 도메인/ALB DNS로 접근)
  • 특정 User-Agent/국가/IP 대역 차단
  • Body size/JSON 스키마/SQLi/XSS 매칭

WAF 403은 아래 글의 흐름대로 보면 대부분 해결됩니다.

4.2 앱 인증/인가가 “프록시 뒤”에서 깨지는 경우

ALB 뒤에서 앱이 403을 내는 흔한 이유:

  • HTTPS 종료 지점이 바뀌면서 앱이 X-Forwarded-Proto를 못 읽고 CSRF/redirect 정책이 틀어짐
  • 허용 IP를 X-Forwarded-For가 아니라 소스 IP로 판단(ALB IP로 보임)
  • OIDC/SSO 콜백 URL이 도메인 불일치로 거부

이건 애플리케이션/미들웨어 설정 영역이라 프레임워크별로 다르지만, 공통적으로는 Forwarded 헤더를 신뢰하도록 설정하고, 외부 도메인/스킴을 정확히 맞추는 것이 핵심입니다.

5) NLB/타겟그룹 상태 이상이 403으로 “보이게” 만드는 경우

엄밀히 말해 타겟이 죽으면 502/504가 더 흔하지만, 환경에 따라 기본 규칙/커스텀 에러 처리로 403처럼 관측될 때도 있습니다(특히 CloudFront/WAF/게이트웨이 계층이 앞에 있을 때).

타겟그룹 헬스와 보안그룹/Pod readiness를 함께 점검하세요.

6) 재현 가능한 “10분 진단 체크리스트”

마지막으로, 현장에서 바로 쓸 수 있게 순서를 고정합니다.

  1. curl로 403 주체 확인
    • server: awselb/2.0인지, 앱 헤더인지
  2. Ingress 매칭 확인
    • ingressClassName: alb
    • host/path가 실제 요청과 일치하는지
  3. Controller 로그 확인
    • kubectl -n kube-system logs deploy/aws-load-balancer-controller
    • AccessDenied/AssumeRole 실패 여부
  4. IRSA 확인
    • SA에 eks.amazonaws.com/role-arn 존재
    • 신뢰 정책(OIDC provider, sub/sa) 일치
  5. Listener rule 확인(기본 규칙 403 여부)
    • host/path 미매칭으로 기본 규칙으로 떨어지는지
  6. WAF 사용 시 로그로 차단 근거 확인

7) 예시: 가장 흔한 403 케이스 2가지와 해결

케이스 A: ALB DNS로 접속하니 403(Host 미매칭)

  • 증상: https://<alb-dns>/는 403, curl -H 'Host: my.domain'는 200
  • 원인: Ingress rule이 host: my.domain인데 요청 Host가 ALB DNS라 매칭 실패
  • 해결: Route53 연결 후 도메인으로 접속하거나, 테스트 시 Host 헤더를 강제로 맞춤
curl -vk https://<alb-dns-name>/ -H 'Host: my.domain'

케이스 B: Controller 로그에 AccessDenied, ALB 규칙이 반쪽

  • 증상: Ingress는 생성됐지만 listener rule/targetgroup 생성이 실패하고 기본 403만 반환
  • 원인: IRSA/IAM policy 부족
  • 해결: LBC 버전에 맞는 IAM policy 적용 + SA role-arn 재확인 + 파드 재시작
kubectl -n kube-system rollout restart deploy/aws-load-balancer-controller
kubectl -n kube-system logs deploy/aws-load-balancer-controller --tail=200

결론

EKS에서 AWS Load Balancer Controller 설치 후 마주치는 403은 “네트워크가 막혔다”기보다, 대개 (1) Host/Path 매칭 실패로 기본 규칙으로 떨어지거나, (2) IRSA/IAM 권한 문제로 리소스가 완성되지 않거나, (3) WAF/인증 계층에서 의도대로 차단되는 경우가 대부분입니다.

403을 보면 곧바로 설정을 뜯어고치기보다, 먼저 curl -v로 403의 발신자를 특정하고(ALB/WAF vs 앱), 다음으로 controller 로그와 Ingress 이벤트를 기반으로 원인을 좁히면 재발 방지까지 포함해 빠르게 정리할 수 있습니다.