- Published on
EKS VPC CNI IP 누수로 Pod IP 고갈 해결하기
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스가 아닌 EKS(EC2 노드) 환경에서 갑자기 Failed to assign an IP address to container 류의 이벤트가 폭발하고, 신규 Pod가 Pending/ContainerCreating에서 멈춘 뒤 결국 노드가 NotReady까지 가는 경우가 있습니다. 노드의 CPU/메모리는 멀쩡한데 Pod IP가 고갈되는 상황이죠.
이 글은 그중에서도 자주 놓치는 케이스인 VPC CNI의 IP 할당 누수(혹은 누수처럼 보이는 warm pool 고착/해제 실패) 때문에 IP가 회수되지 않아 고갈되는 문제를, 운영 관점에서 빠르게 진단하고 안전하게 복구하는 방법을 정리합니다.
관련해서 네트워크 계층 이슈를 함께 점검해야 하는 경우도 많습니다. 예를 들어 DNS는 되는데 HTTPS만 실패하는 케이스처럼 CNI/SNAT/MTU가 엮이면 증상이 비슷하게 나타날 수 있으니, 필요하면 EKS Pod DNS는 되는데 HTTPS만 실패할 때 점검도 같이 참고하세요. 또 CNI 자체 초기화 문제로 NotReady가 발생한다면 EKS kubelet NotReady - CNI plugin not initialized 해결도 도움이 됩니다.
1) 대표 증상: “리소스는 남는데 Pod만 안 뜬다”
다음 중 2~3개가 동시에 보이면 IP 고갈을 강하게 의심할 수 있습니다.
- Pod 이벤트
FailedCreatePodSandBox/Failed to create pod sandboxfailed to assign an IP address to containerAWS CNI failed to assign an IP address to a container
- 노드 이벤트/로그
ipamd(aws-node) 로그에InsufficientCidrBlocks,no available IP addresses,AssignPodIPv4Address실패
- 관측 지표
- 노드 단위로 Pod 수가 특정 숫자에서 더 이상 증가하지 않음(인스턴스 타입별 ENI/IP 한계와 일치)
- 노드 스케일 인/아웃 직후부터 문제가 악화
- 재시작/재배포를 반복할수록 “남는 IP가 줄어드는 느낌”
2) 배경: EKS VPC CNI의 IP 할당 방식(핵심만)
EKS의 AWS VPC CNI는 기본적으로 Pod IP를 VPC 서브넷의 IP로 직접 할당합니다.
- 노드(EC2)에 ENI(Elastic Network Interface)를 붙이고
- ENI에 secondary IP를 여러 개 붙인 뒤
- 그 IP를 Pod에 할당합니다.
여기서 중요한 개념이 warm pool입니다.
WARM_IP_TARGET: 미리 확보해둘 “여분 IP” 개수MINIMUM_IP_TARGET: 최소로 유지할 “전체 IP” 목표치WARM_ENI_TARGET: 미리 확보해둘 ENI 개수
warm pool이 크면 스케일/배포 시 IP 할당 지연이 줄어드는 대신, 서브넷 IP를 더 빨리 소모합니다. 반대로 너무 작으면 순간 트래픽/스케일 시 Pod 생성이 느려질 수 있습니다.
그리고 운영에서 흔히 “누수”라고 부르는 상황은 크게 2가지로 나뉩니다.
- 실제 누수: Pod가 사라졌는데도 IP가 회수/반납되지 않거나, ipamd 상태가 꼬여 IP를 더 이상 재사용하지 못하는 상태
- 누수처럼 보이는 고착: warm pool 설정/노드 교체/스케줄링 패턴 때문에 사용하지 않는 IP가 계속 잡혀 있어 서브넷이 고갈되는 상태
둘 다 결과는 같습니다. 신규 Pod가 IP를 못 받아 죽습니다.
3) 10분 내 1차 진단 체크리스트
3.1 Pod/노드 이벤트로 IP 할당 실패 확인
kubectl -n kube-system get pods -l k8s-app=aws-node -o wide
kubectl describe pod <문제 Pod> | sed -n '/Events:/,$p'
이벤트에 FailedCreatePodSandBox + CNI 관련 메시지가 보이면 다음 단계로 넘어갑니다.
3.2 aws-node(ipamd) 로그에서 “IP 부족” 패턴 찾기
# 특정 노드의 aws-node 로그 확인
kubectl -n kube-system logs -l k8s-app=aws-node --tail=200 | egrep -i "ipamd|assign|no available|insufficient|cidr|eni"
노드별로 보고 싶으면:
NODE=<node-name>
POD=$(kubectl -n kube-system get pod -l k8s-app=aws-node -o wide | awk -v n="$NODE" '$7==n{print $1;exit}')
kubectl -n kube-system logs "$POD" -c aws-node --tail=300
3.3 노드에 실제로 붙은 ENI/secondary IP 확인(EC2 레벨)
가장 확실한 건 EC2 네트워크 인터페이스 상태를 보는 것입니다.
# 노드 인스턴스 ID 찾기
kubectl get node -o wide
# (예) providerID: aws:///ap-northeast-2a/i-0123456789abcdef0
INSTANCE_ID=i-0123456789abcdef0
aws ec2 describe-instances --instance-ids "$INSTANCE_ID" \
--query 'Reservations[0].Instances[0].NetworkInterfaces[*].{ENI:NetworkInterfaceId,IPs:PrivateIpAddresses[*].PrivateIpAddress}' \
--output json
여기서 IP가 많이 붙어 있는데도 Pod가 IP를 못 받으면 ipamd 상태 꼬임/회수 실패 가능성이 커집니다.
3.4 서브넷 IP 고갈 여부 확인
서브넷의 AvailableIpAddressCount가 낮다면, warm pool 고착이든 누수든 증상은 더 빨리 폭발합니다.
SUBNET_ID=subnet-xxxx
aws ec2 describe-subnets --subnet-ids "$SUBNET_ID" \
--query 'Subnets[0].{SubnetId:SubnetId,AZ:AvailabilityZone,Available:AvailableIpAddressCount,CIDR:CidrBlock}' \
--output table
4) 원인 패턴 5가지(운영에서 자주 만나는 순)
4.1 warm pool 설정이 과도해서 “누수처럼” 서브넷을 잡아먹는 경우
WARM_IP_TARGET가 너무 큼WARM_ENI_TARGET까지 설정해 ENI를 과도하게 선점- 노드가 많아지면 노드마다 warm pool이 쌓이면서 서브넷이 급격히 고갈
특히 작은 /24 서브넷에서 노드가 늘어나는 순간, 실제 Pod 수보다 훨씬 많은 IP가 예약되어 고갈이 빨리 옵니다.
4.2 노드 종료/스케일 인/드레인 타이밍에 IP 회수가 지연되는 경우
- 노드가 terminate 되기 전에 Pod가 우아하게 종료되지 못하거나
- CNI/iptables 상태가 꼬여 ipamd가 “이 IP는 아직 사용 중”으로 판단
- 결과적으로 사용하지 않는 secondary IP가 남아 있는 것처럼 보임
4.3 aws-node 데몬셋 불안정(재시작 루프, 권한, IMDS, API throttling)
ipamd가 EC2 API를 호출해 ENI/IP를 붙였다 떼어야 하는데, 다음이 있으면 회수/할당이 꼬일 수 있습니다.
- IRSA/노드 IAM 권한 부족(Describe/Assign/Unassign 관련)
- IMDS 접근 문제
- EC2 API throttling(대규모 스케일 이벤트에서 간헐적으로 발생)
4.4 Security Group for Pods(Trunk/Branch ENI) 구성에서의 예기치 않은 ENI 소비
SGP를 쓰면 ENI 소비 모델이 달라져서, 인스턴스별 ENI 한계에 더 빨리 도달할 수 있습니다. 이때도 “IP가 남아 보이는데 Pod가 안 뜨는” 상황이 옵니다(실제로는 ENI 슬롯이 부족).
4.5 인스턴스 타입의 Pod 최대치(ENI/IP 한계)에 도달
이건 누수는 아니지만, 현상은 거의 동일합니다.
- 특정 인스턴스 타입은 ENI 개수/ENI당 IP 개수 한계가 작아 Pod를 많이 못 띄움
maxPods설정(bootstrap.sh 또는 EKS AMI 기본 계산)과도 연결
따라서 “고갈”이 서브넷이 아니라 노드의 네트워크 capacity일 수도 있습니다.
5) 해결 전략: “즉시 복구”와 “재발 방지”를 분리
5.1 즉시 복구(서비스 살리기): 문제 노드 격리 + aws-node 재시작
- 문제가 발생한 노드를 우선 스케줄링에서 제외합니다.
kubectl cordon <node>
- 워크로드를 다른 노드로 옮길 수 있으면 drain합니다.
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data --grace-period=60 --timeout=10m
- 해당 노드의 aws-node를 재시작(또는 데몬셋 롤링 재시작)합니다.
kubectl -n kube-system rollout restart ds/aws-node
kubectl -n kube-system rollout status ds/aws-node --timeout=5m
aws-node 재시작만으로 ipamd 상태가 정상화되어 IP가 다시 할당되는 경우가 꽤 많습니다.
- 그래도 회복이 안 되면, 가장 확실한 응급처치는 노드 교체(terminate) 입니다.
- Managed Node Group이면 해당 노드를 강제 종료해 새 노드로 대체
- Karpenter면 노드 삭제 후 재프로비저닝
> 주의: 노드 교체는 “그 노드에 붙어 있던 ENI/IP 정리”를 강제로 유도합니다. 다만 근본 원인이 warm pool/서브넷 설계라면 곧 재발합니다.
5.2 재발 방지 1: warm pool을 현실적으로 줄이기
가장 흔한 해법은 WARM_IP_TARGET를 낮추는 것입니다. 트래픽 특성상 순간 스케일이 크지 않다면 1~2로도 충분한 경우가 많습니다.
kubectl -n kube-system set env ds/aws-node \
WARM_IP_TARGET=2 \
MINIMUM_IP_TARGET=0 \
WARM_ENI_TARGET=0
kubectl -n kube-system rollout restart ds/aws-node
WARM_IP_TARGET: 노드당 “여분 IP”MINIMUM_IP_TARGET: 최소 확보량을 강제할 필요가 없다면 0으로 단순화WARM_ENI_TARGET: 특별한 이유가 없다면 0(기본) 유지
운영 팁:
- 배포/스케일이 잦은 클러스터는
WARM_IP_TARGET를 너무 낮추면 Pod 생성 지연이 생길 수 있으니, 노드 수 × warm IP가 서브넷 여유 IP를 얼마나 먹는지부터 계산하세요.
5.3 재발 방지 2: 서브넷 CIDR 확장/추가(가장 근본적)
Pod IP를 VPC IP로 직접 쓰는 모델 자체가 “서브넷 IP”를 비용처럼 소비합니다. 클러스터가 성장할 계획이면:
- 노드가 붙는 서브넷을 /24 → /22 등으로 확장하거나
- 새로운 서브넷을 추가하고 Node Group을 분산
이 접근이 가장 근본적이며, warm pool 튜닝만으로는 한계가 있는 경우가 많습니다.
5.4 재발 방지 3: Prefix Delegation(/28 프리픽스) 활성화 검토
AWS VPC CNI는 Prefix Delegation을 지원합니다. 노드가 개별 IP를 하나씩 붙이는 대신 /28 같은 프리픽스를 위임받아 더 효율적으로 Pod IP를 공급할 수 있습니다(환경에 따라 ENI/API 호출 패턴과 확장성이 개선).
활성화 예시는 다음과 같습니다(클러스터/버전/정책에 따라 검증 필요).
kubectl -n kube-system set env ds/aws-node \
ENABLE_PREFIX_DELEGATION=true \
WARM_PREFIX_TARGET=1
kubectl -n kube-system rollout restart ds/aws-node
Prefix Delegation은 만능은 아니지만, 대규모 클러스터에서 IP 할당 병목과 ENI/IP 관리 복잡도를 줄이는 데 도움이 됩니다.
5.5 재발 방지 4: 인스턴스 타입/maxPods 재점검
- 인스턴스 타입별 ENI/IP 한계를 확인하고, 워크로드 밀도를 조정
- 너무 작은 인스턴스에 Pod를 과밀하게 올리면, IP 고갈처럼 보이는 “capacity 한계”에 자주 부딪힙니다.
EKS AMI는 기본적으로 인스턴스 타입 기반으로 maxPods를 계산하지만, 커스텀 AMI/부트스트랩 옵션으로 달라질 수 있습니다.
5.6 재발 방지 5: 관측/알람(서브넷 여유 IP, 노드 IP 사용량)
다음 2가지만 알람 걸어도 조기 감지가 가능합니다.
- 서브넷
AvailableIpAddressCount임계치 알람 - aws-node/ipamd 관련 에러 로그 급증(예: CloudWatch Logs Metric Filter)
간단한 CloudWatch 지표 예시(서브넷 여유 IP는 EC2 Subnet 지표로 직접 나오지 않으므로 주기적으로 Lambda/스크립트로 describe-subnets를 긁어 커스텀 메트릭으로 넣는 방식이 흔합니다).
6) 운영 시나리오별 “가장 빠른” 선택지
시나리오 A: 특정 노드 몇 대에서만 발생
- cordon/drain → aws-node 재시작 → 안 되면 노드 교체
- 동시에 warm pool이 과도하지 않은지 확인
시나리오 B: 클러스터 전반에서 동시다발
- 서브넷
AvailableIpAddressCount확인 - 부족하면 즉시:
- 새 서브넷 추가 + 노드 분산(가능하면)
- warm pool 낮추기
- 스케일 아웃을 잠시 멈추고(오토스케일 정책 조정) 안정화
시나리오 C: 노드 롤링 업데이트/스케일 이벤트 후에만 재현
- aws-node 버전/설정 변경 이력 확인
- EC2 API throttling/권한 문제 확인
- 노드 종료 훅(예: lifecycle hook)에서 drain이 제대로 되는지 확인
7) 트러블슈팅에 유용한 커맨드 모음
7.1 노드별 Pod 수와 IP 이슈 상관 보기
kubectl get pods -A -o wide --field-selector spec.nodeName=<node> | wc -l
kubectl describe node <node> | sed -n '/Non-terminated Pods:/,/Allocated resources:/p'
7.2 aws-node 환경변수 확인
kubectl -n kube-system describe ds aws-node | sed -n '/Environment:/,/Mounts:/p'
7.3 CNI 관련 이벤트 빠르게 모으기
kubectl get events -A --sort-by=.lastTimestamp | egrep -i "cni|ipamd|sandbox|assign an ip" | tail -n 50
8) 정리: “누수”로 보이면 먼저 warm pool과 회수 경로를 의심
EKS에서 Pod IP 고갈은 흔히 ‘서브넷이 작아서’로 끝나지만, 실제 현장에서는 다음 조합이 많습니다.
- warm pool이 과도 → 서브넷 고갈 가속
- 스케일 이벤트/노드 교체 타이밍 → ipamd 상태 꼬임/회수 지연
- 결과적으로 “IP가 새는 것처럼” 보이는 현상
따라서 대응은 두 갈래로 가져가야 합니다.
- 즉시 복구: 문제 노드 격리/교체 + aws-node 재시작으로 서비스부터 살리기
- 재발 방지: warm pool 튜닝, 서브넷 설계 개선, Prefix Delegation 검토, 관측/알람
네트워크 레벨에서 증상이 섞여 보일 때는 CNI/SNAT/MTU까지 함께 점검하는 것이 좋습니다. 특히 “DNS는 되는데 HTTPS만 실패”처럼 보이는 케이스는 IP 고갈과 별개로도 발생할 수 있으니, 앞서 언급한 EKS Pod DNS는 되는데 HTTPS만 실패할 때 점검 글을 체크리스트로 활용해보세요.