Published on

EKS Pod DNS는 되는데 S3만 503? 엔드포인트 정책

Authors

서론

EKS에서 흔히 겪는 이상한 증상 중 하나가 이겁니다.

  • Pod 내부에서 nslookup, dig 같은 DNS 조회는 잘 된다.
  • 다른 외부 HTTPS 호출도 대체로 잘 된다.
  • 그런데 S3만 호출하면 503(Service Unavailable) 가 난다.

애플리케이션 로그에는 503만 남고, 네트워크는 살아있는 것처럼 보이니 원인 파악이 어려워집니다. 특히 S3를 VPC Endpoint(Gateway/Interface)로 붙여둔 환경에서는 “DNS는 되는데 S3만 503”이 엔드포인트 정책(Endpoint policy) 또는 경로/프록시 강제에서 발생하는 경우가 많습니다.

이 글에서는 EKS Pod 기준으로 S3 503을 빠르게 좁혀가는 방법과, 실제로 많이 터지는 S3 VPC Endpoint 정책 실수 패턴을 정리합니다.

> EKS 네트워크 진단을 더 넓게 보고 싶다면: EKS Pod는 뜨는데 트래픽 0 - NetPol·SG·CNI 10분 진단


1) 먼저 정리: S3에서의 503은 “권한 거부”가 아닐 수 있다

S3 권한 문제라면 보통 다음을 기대합니다.

  • 403 AccessDenied (버킷 정책/IRSA/IAM)
  • 404 NoSuchBucket

그런데 503은 보통 아래 범주에서 더 자주 나옵니다.

  • 경로(라우팅) 또는 프록시 계층에서 S3로 못 나감
  • VPC Endpoint 쪽에서 요청이 비정상적으로 처리됨(정책/엔드포인트 유형/리전/호스트)
  • S3 멀티파트/대용량 업로드에서 중간 장비가 끊는 경우(프록시, NAT, 방화벽)

즉, “DNS는 된다”는 사실만으로는 S3 경로가 정상인지 보장하지 않습니다. DNS는 Route53 Resolver로 잘 풀리는데, 실제 트래픽은 S3 엔드포인트로 강제되거나 반대로 엔드포인트가 막혀 실패할 수 있습니다.


2) 5분 컷 1차 진단: Pod에서 curl로 S3 엔드포인트 확인

애플리케이션 SDK 대신, Pod 안에서 S3 API 엔드포인트에 직접 붙여 “어디로 연결되는지/무슨 응답인지” 확인합니다.

2.1 디버그 Pod 띄우기

kubectl run -it --rm net-debug \
  --image=curlimages/curl:8.5.0 \
  --restart=Never -- sh

2.2 DNS 결과와 실제 연결 IP 확인

아래는 S3 virtual-hosted–style을 가정합니다.

# 버킷명을 넣어 테스트
BUCKET=my-bucket
REGION=ap-northeast-2

# DNS 확인
nslookup ${BUCKET}.s3.${REGION}.amazonaws.com

# TLS 핸드셰이크/연결 대상 확인
curl -v --connect-timeout 5 \
  https://${BUCKET}.s3.${REGION}.amazonaws.com/ \
  -o /dev/null

여기서 포인트:

  • 연결 IP가 사설 IP(10.x/172.16/192.168) 로 나오면 대개 Interface Endpoint(PrivateLink) 또는 내부 프록시를 타고 있을 가능성이 큽니다.
  • 연결 IP가 공인 IP로 나오면 NAT/IGW 경로일 수 있습니다(혹은 DNS가 퍼블릭 S3로 해석됨).
  • * Connected to ... 이후에 바로 503이 떨어지면, 애플리케이션 권한 문제보다는 네트워크 경로/엔드포인트 정책/중간 장비 가능성이 커집니다.

3) “DNS는 되는데 S3만 503”에서 가장 흔한 원인 3가지

원인 A) S3 Gateway Endpoint 정책이 필요한 액션/리소스를 막고 있음

S3 Gateway Endpoint(라우팅 테이블에 붙는 타입)는 “VPC에서 S3로 가는 경로”를 엔드포인트로 강제합니다. 이때 Endpoint policy가 지나치게 제한적이면 특정 요청이 실패합니다.

보통 정책이 막으면 403을 기대하지만, SDK/프록시/리전 미스매치가 겹치면 애매한 503으로 보이는 케이스도 있습니다(특히 앱 로그가 원문 응답을 제대로 남기지 않을 때).

체크

  • VPC 콘솔 → Endpoints → com.amazonaws.<region>.s3 → Policy 확인
  • CloudTrail의 S3 Data Events(활성화된 경우) 또는 VPC Flow Logs로 흐름 확인

“너무 빡센” 정책 예

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": ["s3:GetObject"],
      "Resource": ["arn:aws:s3:::my-bucket/*"]
    }
  ]
}

위 정책은 GetObject만 허용합니다. 하지만 실제 애플리케이션은 다음을 추가로 요구하는 경우가 많습니다.

  • s3:ListBucket (prefix listing)
  • s3:PutObject, s3:AbortMultipartUpload, s3:ListMultipartUploadParts
  • KMS 사용 시 kms:Decrypt/Encrypt (이건 Endpoint policy가 아니라 IAM/KMS 정책이지만 함께 점검)

최소 안전 정책(예시)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject",
        "s3:ListBucket",
        "s3:AbortMultipartUpload",
        "s3:ListBucketMultipartUploads",
        "s3:ListMultipartUploadParts"
      ],
      "Resource": [
        "arn:aws:s3:::my-bucket",
        "arn:aws:s3:::my-bucket/*"
      ]
    }
  ]
}

> 운영에서는 버킷/프리픽스 단위로 더 좁히고, 필요한 액션만 남기되 “멀티파트/리스트” 계열을 빠뜨리지 않는 것이 핵심입니다.


원인 B) S3 Interface Endpoint(PrivateLink)로 붙였는데 DNS/호스트가 꼬임

S3는 전통적으로 Gateway Endpoint가 정석이지만, 요건에 따라 Interface Endpoint를 쓰는 구성도 있습니다(정책/감사/프록시/특정 제약 등).

이때 자주 생기는 문제:

  • Private DNS를 켜고/끄는 과정에서 Pod가 보는 S3 도메인이 기대와 다르게 해석됨
  • 리전 엔드포인트(s3.ap-northeast-2.amazonaws.com)와 버킷 엔드포인트(bucket.s3.ap-northeast-2.amazonaws.com)가 섞임
  • 경로 스타일(path-style) 강제 사용(s3.amazonaws.com/bucket)이 남아있어 TLS SNI/Host가 꼬임

체크

  • Endpoint 타입 확인: Gateway인지 Interface인지
  • Interface라면 Private DNS 설정과, 해당 VPC의 DNS 설정(EnableDnsHostnames/Support)
  • 애플리케이션 SDK 설정에서 s3ForcePathStyle 같은 옵션이 켜져 있는지

Node.js AWS SDK v3에서 path-style을 강제하는 예(문제 유발 가능):

import { S3Client } from "@aws-sdk/client-s3";

const s3 = new S3Client({
  region: "ap-northeast-2",
  forcePathStyle: true // virtual-hosted가 필요한 환경에서 이게 켜져 있으면 꼬일 수 있음
});

원인 C) “S3는 반드시 엔드포인트로만” 강제했는데 라우팅/서브넷이 일부 누락

EKS는 서브넷이 여러 개이고, 노드 그룹/파드가 서로 다른 서브넷에 떠 있을 수 있습니다.

S3 Gateway Endpoint는 라우팅 테이블 단위로 붙습니다. 즉,

  • 어떤 서브넷의 라우팅 테이블에는 S3 Endpoint route가 있고
  • 다른 서브넷 라우팅 테이블에는 없다

이러면 “어떤 파드에서는 되고, 어떤 파드에서는 503/timeout” 같은 형태로 나타납니다.

체크

  • EKS 노드가 속한 서브넷들의 Route Table에 S3 Prefix List 경로가 있는지
  • VPC Endpoint → Subnets(Interface) 또는 Route Tables(Gateway)가 “전부” 포함되는지

AWS CLI로 Gateway endpoint 연결 라우팅 테이블 확인:

aws ec2 describe-vpc-endpoints \
  --filters Name=service-name,Values=com.amazonaws.ap-northeast-2.s3 \
  --query 'VpcEndpoints[0].{Id:VpcEndpointId,Type:VpcEndpointType,RouteTables:RouteTableIds,Policy:PolicyDocument}' \
  --output json

4) 503을 “정확히” 보기: SDK 재시도/프록시 숨김을 제거하고 원문 로그 남기기

애플리케이션이 S3 SDK를 쓰면 내부적으로 재시도/추상화가 들어가서, 실제로는 403인데 앱 로그에는 503만 남는 식의 왜곡이 생길 수 있습니다.

Python(boto3)에서 디버그 로그 켜기

import boto3
import logging

boto3.set_stream_logger('botocore', level=logging.DEBUG)

s3 = boto3.client('s3', region_name='ap-northeast-2')
print(s3.list_objects_v2(Bucket='my-bucket', MaxKeys=1))

여기서 확인할 것:

  • 실제 요청 URL(호스트가 bucket.s3.<region>.amazonaws.com 형태인지)
  • 응답 헤더에 x-amz-request-id, x-amz-id-2가 있는지(진짜 S3 응답인지)
  • 프록시를 타는지(HTTPS_PROXY 환경변수 등)

5) 엔드포인트 정책을 설계할 때의 실전 가이드

5.1 Endpoint policy vs Bucket policy vs IAM의 역할 분리

  • IAM/IRSA 정책: “누가(Principal) 무엇을(Action) 할 수 있나”의 1차 권한
  • Bucket policy: 버킷 리소스 관점에서의 추가 제약(특정 VPC/VPCE만 허용 등)
  • VPC Endpoint policy: “이 VPC 엔드포인트를 통해 나가는 트래픽”에 대한 필터

운영에서 흔한 실수는 Endpoint policy를 너무 강하게 걸어놓고, 장애 시 원인을 IAM/IRSA로만 의심하는 겁니다. IRSA가 꼬였을 때 전면 장애가 나기도 하니 함께 점검하세요: EKS OIDC Provider 삭제로 IRSA 전부 실패했을 때 복구

5.2 “특정 버킷만 허용” + “필수 액션 포함”을 기본으로

  • 리소스는 arn:aws:s3:::bucketarn:aws:s3:::bucket/* 둘 다 필요
  • ListBucket 류는 객체 ARN이 아니라 버킷 ARN에 걸림
  • 멀티파트/업로드를 쓰면 관련 액션이 추가로 필요

5.3 버킷 정책으로 VPCE 제한 시, 엔드포인트 교체/멀티계정에 주의

버킷 정책에서 아래처럼 aws:sourceVpce로 제한하면 보안은 강해지지만,

  • 엔드포인트 재생성(VPCE ID 변경)
  • 멀티 VPC/멀티 계정 접근

에서 장애가 날 수 있습니다.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowOnlyFromSpecificVPCE",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::my-bucket",
        "arn:aws:s3:::my-bucket/*"
      ],
      "Condition": {
        "StringNotEquals": {
          "aws:sourceVpce": "vpce-0123456789abcdef0"
        }
      }
    }
  ]
}

이 구성이 들어가 있으면, “DNS는 되는데 S3만” 특정 경로에서 실패하는 현상이 더 자주 보입니다(특히 파드가 다른 VPC/엔드포인트 경로로 나가는 경우).


6) 체크리스트: 재현 → 원인 분리 → 해결 순서

  1. Pod에서 curl -v로 S3 엔드포인트 직접 호출
    • 진짜 S3 응답인지(헤더)
    • 연결 IP가 사설/공인인지
  2. VPC Endpoint 타입 확인
    • Gateway: 라우팅 테이블 누락 여부
    • Interface: Private DNS/호스트 스타일/SDK 설정
  3. Endpoint policy 확인
    • 필요한 액션(ListBucket, 멀티파트 등) 포함 여부
    • 리소스 ARN 범위 정확성
  4. 버킷 정책의 aws:sourceVpce/aws:sourceVpc 조건 확인
  5. IRSA/IAM 권한과 CloudTrail 이벤트로 최종 확인

네트워크/정책 이슈는 “파드 상태는 멀쩡한데 트래픽만 이상”으로 나타나는 경우가 많습니다. 그런 경우엔 네트워크 계층을 체계적으로 분해해서 보는 게 가장 빠릅니다: Kubernetes apiserver i/o timeout 원인과 해결


결론

EKS Pod에서 DNS가 정상인데 S3만 503이 나는 상황은, 단순한 권한 문제라기보다 S3 VPC Endpoint(특히 정책/라우팅/Private DNS) 구성 불일치에서 시작되는 경우가 많습니다.

핵심은 “Pod에서 실제로 어디로 붙는지”를 먼저 확인하고(curl -v, SDK 디버그), 그 다음에 Gateway/Interface 엔드포인트 유형에 맞춰 라우팅 테이블과 Endpoint policy를 점검하는 것입니다. 정책은 보안을 위해 좁히되, S3가 실제로 사용하는 액션(리스트/멀티파트)을 빠뜨리지 않도록 설계하면 같은 장애를 크게 줄일 수 있습니다.