Cloud/Kubernetes

AWS Pod EIP Controller로 Pod마다 고유한 공인 IP 부여하기

Jinseung's Blog 2026. 10. 25. 01:30

이번 블로그에서는 AWS EKS 환경에서 Pod마다 고유한 공인 IP를 할당하는 방법에 대한 글을  작성해 보겠습니다.

 

들어가며

외부 파트너사 API를 연동하다 보면 "허용할 IP를 알려주세요"라는 요청을 자주 받게 됩니다. 그런데 요구사항이 한 단계 더 나아가 Pod마다 서로 다른 IP로 식별되어야 한다면 이야기가 달라집니다.


EKS의 기본 구성에서는 여러 Pod가 노드의 IP나 NAT Gateway의 IP 하나를 공유해서 나가기 때문에, Pod 단위 화이트리스트가 애초에 성립되지 않습니다. 이 글에서는 이 문제를 AWS Pod EIP Controller로 해결하는 과정을 정리했습니다.

 

기본적으로 Pod는 어떻게 인터넷에 나가는 방법

EKS에서 Pod의 아웃바운드 경로는 Worker Node가 어느 서브넷에 있느냐에 따라 갈립니다.

  1. Public Subnet의 노드: Pod 트래픽이 노드의 Public IP 또는 EIP로 나갑니다.
  2. Private Subnet의 노드: NAT Gateway를 거쳐 NAT GW의 공인 IP로 변환되어 나갑니다.

핵심은 VPC CNI의 SNAT 동작입니다. Pod가 VPC CIDR 바깥과 통신하면, VPC CNI는 기본적으로 출발지 IP를 노드 기본 ENI의 IP로 SNAT 합니다. 그래서 노드가 Public Subnet에 있고 공인 IP를 가지고 있어도, Pod는 결국 노드의 IP를 빌려 쓰는 셈입니다.


NAT Gateway를 쓰는 경우도 마찬가지입니다. 아래처럼 Pod A, B, C가 전부 같은 공인 IP로 나가기 때문에 파트너사 입장에서는 세 Pod를 구분할 방법이 없습니다.

AWS Pod EIP Controller란?

AWS Pod EIP Controller는 Pod에 Annotation을 붙이면 Elastic IP를 자동으로 할당하고, Pod가 사라지면 해제해 주는 컨트롤러입니다. AWS Samples에서 공개한 오픈소스 프로젝트입니다. Pod별 화이트리스트를 구현하려면 두 가지가 함께 필요합니다.

  • EIP Controller로 Pod마다 EIP 부여: Pod의 Private IP(노드 ENI의 보조 IP)에 EIP를 연결합니다.
  • 노드 기반 SNAT 해제: SNAT가 켜져 있으면 EIP가 붙어 있어도 트래픽이 노드 IP로 바뀌어 나갑니다. 아래 옵션으로 꺼야 Pod IP 그대로 IGW에 도달하고, IGW가 연결된 EIP로 변환해 줍니다.
kubectl set env daemonset -n kube-system aws-node AWS_VPC_K8S_CNI_EXTERNALSNAT=true

 

실습: 컨트롤러 설치하기

1. EKS 클러스터 생성

EIP는 IGW를 통해서만 동작하므로, 노드 그룹을 Public Subnet에 두고 privateNetworking: false로 설정합니다. 서브넷 ID는 본인 환경에 맞게 바꿔 주세요.

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: eip-controller-demo
  version: "1.37"
  region: ap-northeast-2

iam:
  withOIDC: true

vpc:
  subnets:
    public:
      ap-northeast-2a: { id: <PUBLIC_SUBNET_A_ID> }
      ap-northeast-2b: { id: <PUBLIC_SUBNET_B_ID> }

managedNodeGroups:
  - name: public-nodegroup
    instanceName: public-node
    instanceType: t3.medium
    desiredCapacity: 2
    minSize: 2
    maxSize: 4
    privateNetworking: false

 

2. 환경 변수 설정

export ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
export AWS_REGION=ap-northeast-2

 

3. 컨트롤러 이미지 빌드 후 ECR에 푸시

컨트롤러는 공개 이미지가 없어 직접 빌드해야 합니다. 먼저 ECR 리포지토리를 만들고 로그인합니다.

aws ecr create-repository --repository-name aws-pod-eip-controller

aws ecr get-login-password --region ${AWS_REGION} \
    | docker login --username AWS --password-stdin ${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com

 

소스를 받아 amd64, arm64 멀티 아키텍처로 빌드합니다.

git clone https://github.com/aws-samples/aws-pod-eip-controller.git
cd aws-pod-eip-controller

docker buildx build \
    --tag ${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/aws-pod-eip-controller:latest \
    --platform linux/amd64,linux/arm64 \
    --push .

4. IAM 정책과 ServiceAccount 생성

컨트롤러는 EIP 할당·연결·해제와 태그 작업, ENI 조회 권한이 필요합니다.

cat << EOF > iam-policy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:AllocateAddress",
                "ec2:AssociateAddress",
                "ec2:CreateTags",
                "ec2:ReleaseAddress",
                "ec2:DisassociateAddress",
                "ec2:DeleteTags",
                "ec2:DescribeAddresses",
                "ec2:DescribeNetworkInterfaces"
            ],
            "Resource": "*"
        }
    ]
}
EOF

aws iam create-policy \
    --policy-name AWSPodEIPControllerIAMPolicy \
    --policy-document file://iam-policy.json

 

IRSA로 ServiceAccount에 정책을 연결합니다.

eksctl create iamserviceaccount \
    --cluster=eip-controller-demo \
    --namespace=kube-system \
    --name=aws-pod-eip-controller \
    --attach-policy-arn=arn:aws:iam::${ACCOUNT_ID}:policy/AWSPodEIPControllerIAMPolicy \
    --override-existing-serviceaccounts \
    --region ${AWS_REGION} \
    --approve

5. Helm으로 컨트롤러 설치

helm install aws-pod-eip-controller ./charts/aws-pod-eip-controller \
  --namespace kube-system \
  --set image=${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/aws-pod-eip-controller:latest \
  --set clusterName=eip-controller-demo \
  --set serviceAccountName=aws-pod-eip-controller \
  --wait

6. 테스트용 Deployment 배포

aws-samples.github.com/aws-pod-eip-controller-type: auto Annotation을 붙이면 컨트롤러가 Pod마다 EIP를 할당합니다. nodeSelector로 Public Subnet 노드 그룹에 배치합니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: busybox
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: busybox-eip
  template:
    metadata:
      labels:
        app: busybox-eip
      annotations:
        aws-samples.github.com/aws-pod-eip-controller-type: auto
    spec:
      nodeSelector:
        alpha.eksctl.io/nodegroup-name: public-nodegroup
      containers:
      - name: busybox
        image: busybox
        command:
          - sleep
          - "3600"
        imagePullPolicy: IfNotPresent

 

할당된 EIP는 Pod 라벨 aws-pod-eip-controller-public-ip에 기록됩니다. 아래 명령으로 Pod IP와 EIP를 함께 확인할 수 있습니다.

kubectl get pods -o=custom-columns=NAME:.metadata.name,STATUS:.status.phase,PODIP:.status.podIP,EIP:.metadata.labels.aws-pod-eip-controller-public-ip -w

 

테스트: SNAT 설정에 따라 출발지 IP가 달라지는지 확인

Pod 안에서 api.ipify.org를 호출하면 외부에서 보이는 출발지 IP를 확인할 수 있습니다.

kubectl exec -it <POD_NAME> -- /bin/sh
wget -qO- http://api.ipify.org
테스트 1: SNAT 활성화 (기본값)

EIP가 Pod에 할당되어 있는데도, 응답으로 노드의 Public IP가 돌아옵니다. VPC CNI가 트래픽을 노드 ENI IP로 SNAT 하기 때문입니다.

테스트 2: SNAT 비활성화

AWS_VPC_K8S_CNI_EXTERNALSNAT=true를 적용한 뒤 같은 명령을 다시 실행합니다.

kubectl set env daemonset -n kube-system aws-node AWS_VPC_K8S_CNI_EXTERNALSNAT=true
kubectl rollout status daemonset aws-node -n kube-system

 

이번에는 Pod에 부여된 EIP가 그대로 조회됩니다. Pod마다 다른 IP가 나오므로, 파트너사에 Pod별 IP를 전달해 화이트리스트를 등록할 수 있습니다.

 

주의할 점

  • SNAT 해제는 노드 전체에 적용됩니다. EIP가 없는 Pod는 Public Subnet 노드에서 인터넷으로 나갈 수 없게 되므로, EIP가 필요한 워크로드를 별도 노드 그룹으로 분리하는 편이 안전합니다.
  • EIP 한도와 비용: 리전당 EIP 기본 한도가 있고, 퍼블릭 IPv4 주소에는 시간당 요금이 붙습니다. replica 수만큼 EIP가 늘어난다는 점을 고려해야 합니다.
  • Pod 재생성 시 IP가 바뀔 수 있습니다. auto 모드는 Pod가 새로 뜰 때마다 EIP를 새로 할당하므로, 고정 IP가 필요하면 컨트롤러 README의 다른 모드를 확인해 보세요.

 

마무리

NAT Gateway나 노드 IP를 공유하는 구조에서는 Pod별 화이트리스트를 만들 수 없습니다. AWS Pod EIP Controller로 Pod마다 EIP를 붙이고 VPC CNI의 SNAT를 해제하면, 각 Pod가 고유한 공인 IP로 외부와 통신하게 됩니다.

 

이것으로 AWS Pod EIP Controller의 대한 글을 마치겠습니다. 감사합니다!