- Published on
EKS Pod는 뜨는데 트래픽 0 - NetPol·SG·CNI 10분 진단
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버가 멀쩡히 떠 있는데(Deployment/Pod Running) 트래픽이 0인 상황은 운영에서 가장 시간을 잡아먹는 유형입니다. 로그도 조용하고, HPA도 반응이 없고, ALB/NLB 헬스체크만 빨갛게 보이거나(혹은 반대로 초록인데도 사용자 트래픽은 0), 내부 호출도 안 되는 경우가 많습니다.
이 글은 **“Pod는 떴다”**를 전제로, 네트워크 계층에서 흔히 터지는 3대 축인 **NetworkPolicy / Security Group(SG) / CNI(aws-vpc-cni)**를 중심으로 10분 안에 원인을 좁히는 진단 루틴을 제공합니다.
- Service/Endpoints 자체가 비어 있는 케이스는 아래 글이 더 빠릅니다: EKS에서 Pod는 뜨는데 Service Endpoints가 0일 때
- ALB Ingress에서 502가 보이는 케이스는 아래 글의 체크리스트도 같이 보세요: EKS에서 ALB Ingress 502 Bad Gateway 원인 9가지
0) 증상 정의: “트래픽 0”을 3가지로 분해
먼저 “트래픽이 0”이 정확히 무엇인지 분해하면 진단 속도가 빨라집니다.
- 외부 → Ingress/Service까지는 오는데 Pod로 전달이 안 됨
- ALB/NLB 헬스체크 실패, Target deregistered, 5xx
- 클러스터 내부 Pod → 대상 Pod로 통신이 안 됨
- 같은 네임스페이스에서도 timeout
- Pod는 요청을 받지만 응답이 못 나감(egress 차단)
- 외부 API 호출, DB 연결, DNS 실패로 앱이 요청 처리 못함
이 글의 루틴은 위 1~3을 모두 커버하지만, 특히 **1과 2(인바운드 경로)**에서 NetworkPolicy/SG/CNI가 얽히는 경우를 집중합니다.
1) 10분 진단 루틴 개요(결론부터)
아래 순서대로 보면 보통 10분 내에 범위를 좁힐 수 있습니다.
- Service/Endpoints/Port 매칭 확인(1분)
- Pod IP로 직접 접근 테스트(2분)
- NetworkPolicy 존재 여부 및 기본 차단 여부 확인(2분)
- 노드/Pod ENI의 Security Group 인바운드/아웃바운드 확인(3분)
- CNI 상태(aws-node), IP 할당/라우팅 문제 확인(2분)
각 단계에서 “정상이라면 다음으로”, “비정상이라면 여기서 고친다” 형태로 진행합니다.
2) 1분: Service/Endpoints/TargetPort가 맞는지(가장 흔함)
네트워크 문제처럼 보이지만 실제로는 Service가 Pod를 못 가리키는 경우가 매우 흔합니다.
kubectl -n <ns> get svc <svc>
kubectl -n <ns> describe svc <svc>
kubectl -n <ns> get endpoints <svc> -o wide
체크 포인트:
selector가 Pod label과 일치하는가?port↔targetPort가 실제 컨테이너containerPort/리스닝 포트와 일치하는가?- Endpoints가 0이면 네트워크가 아니라 디스커버리 문제입니다.
Endpoints가 0이라면 위 내부 링크 글(Service Endpoints가 0일 때)의 루틴이 더 정확합니다.
3) 2분: Pod IP로 직접 붙어서 “경로”를 분리
Service/LB 계층을 배제하고, Pod IP:port로 직접 접근해 봅니다.
3.1 클러스터 내부에서 테스트용 Pod 띄우기
kubectl -n <ns> run netshoot --rm -it \
--image=nicolaka/netshoot -- bash
3.2 DNS/Service/PodIP 순서로 테스트
# (1) DNS
nslookup <svc>.<ns>.svc.cluster.local
# (2) Service ClusterIP
curl -sv http://<svc>.<ns>.svc.cluster.local:<port>/healthz
# (3) Pod IP 직접
curl -sv http://<pod-ip>:<container-port>/healthz
판단:
- Pod IP는 되는데 Service가 안 된다 → kube-proxy/iptables/IPVS 또는 Service/port 설정 쪽
- Pod IP도 안 된다 → NetworkPolicy/SG/CNI/애플리케이션 리스닝(0.0.0.0 여부) 쪽
> 팁: 애플리케이션이 127.0.0.1에만 바인딩되어도 “트래픽 0”이 됩니다. 컨테이너 내부에서 ss -lntp로 리스닝 주소를 확인하세요.
kubectl -n <ns> exec -it <pod> -- sh -c 'ss -lntp || netstat -lntp'
4) 2분: NetworkPolicy가 “기본 차단”을 만들고 있지 않은가
EKS 기본은 NetworkPolicy가 없어도 통신이 됩니다. 하지만 아래 조건이면 이야기가 달라집니다.
- Calico/Cilium 등 NetworkPolicy 구현체가 설치되어 있고
- 네임스페이스에
deny-all성격의 정책이 들어가 있거나 - 특정 label만 허용하는 정책이 있는데 대상 Pod label이 누락됨
4.1 NetworkPolicy 목록부터 확인
kubectl -n <ns> get netpol
kubectl -n <ns> describe netpol <name>
4.2 “default deny” 패턴 빠르게 식별
아래처럼 podSelector: {} + policyTypes: [Ingress]만 있고 ingress rule이 비어 있으면, 해당 네임스페이스는 인바운드 전면 차단입니다.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: app
spec:
podSelector: {}
policyTypes:
- Ingress
이 경우, 허용 정책을 추가해야 합니다. 예: 같은 네임스페이스의 Ingress Controller(또는 특정 Pod label)만 허용.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-ingress
namespace: app
spec:
podSelector:
matchLabels:
app: my-api
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
podSelector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
ports:
- protocol: TCP
port: 8080
4.3 Egress 차단도 같이 본다(DNS/외부 API)
요청은 들어오는데 앱이 외부 의존성 호출을 못 해서 “사실상 트래픽 0”처럼 보이기도 합니다(헬스체크 실패 → LB가 트래픽을 끊음).
Egress default deny가 있으면 최소 DNS(53/UDP,TCP)와 필요한 외부 목적지를 열어야 합니다.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: app
spec:
podSelector:
matchLabels:
app: my-api
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
5) 3분: Security Group(SG)에서 막히는 대표 시나리오
EKS에서 SG는 크게 두 레벨에서 트래픽을 막을 수 있습니다.
- 노드(EC2) SG: NodePort/인바운드, 노드 간 통신
- Pod SG (Security Groups for Pods): 특정 Pod ENI에 SG를 붙여 세밀 제어
특히 ALB → Pod 경로에서 SG가 어긋나면 “Pod는 정상인데 트래픽 0”이 됩니다.
5.1 ALB Target Type에 따라 SG 체크 포인트가 달라짐
target-type: instance
- ALB는 노드의 NodePort로 트래픽을 보냄
- 따라서 노드 SG 인바운드에 NodePort 범위(기본 30000-32767) 또는 특정 NodePort가 열려야 함
target-type: ip
- ALB는 Pod IP로 직접 트래픽을 보냄
- Pod SG(또는 노드 SG/ENI SG)가 ALB SG로부터의 인바운드를 허용해야 함
Ingress annotation이나 컨트롤러 설정으로 target-type을 확인하세요.
kubectl -n <ns> get ingress <ing> -o jsonpath='{.metadata.annotations.alb\.ingress\.kubernetes\.io/target-type}'
5.2 “헬스체크만 실패”는 SG/NetworkPolicy 힌트
- ALB Target health check가 timeout/failed
- Pod 로그는 조용함
이면, 대개 헬스체크 트래픽이 Pod까지 도달하지 못하는 것입니다.
확인 루틴:
- ALB SG에서 아웃바운드가 막혀 있지 않은지(대부분 all 허용이지만, 제한형이면 체크)
- 노드 SG 또는 Pod SG 인바운드에 ALB SG를 소스로 허용했는지
- 포트가 실제 컨테이너 포트/Service targetPort와 일치하는지
AWS CLI로 노드가 쓰는 SG를 확인(Managed Node Group 기준):
aws eks describe-nodegroup \
--cluster-name <cluster> \
--nodegroup-name <ng> \
--query 'nodegroup.resources.autoScalingGroups[0].name' \
--output text
그 다음 EC2/ASG에서 인스턴스 SG를 추적합니다(환경마다 방식이 달라 여기선 개념만).
5.3 Pod SG를 쓰는 경우: “Pod만” 막히는 현상
Security Groups for Pods를 쓰면 같은 노드에 떠도 특정 Pod만 통신이 안 될 수 있습니다.
- 특정 Deployment에만
vpc.amazonaws.com/pod-eni가 붙어 있음 - 해당 Pod SG에서 인바운드/아웃바운드 규칙이 과도하게 제한됨
이때는 kubectl describe pod에서 annotation/ENI 관련 정보를 보고, VPC CNI의 설정(ENIConfig, IRSA 권한 등)을 함께 봐야 합니다.
6) 2분: CNI(aws-vpc-cni)에서 IP/라우팅이 꼬였는지 확인
Pod가 Running이어도 CNI 레벨에서 다음 문제가 있으면 트래픽이 0이 될 수 있습니다.
- Pod IP 할당은 됐지만 라우팅/iptables가 깨짐
- 노드의 secondary IP 고갈로 일부 Pod 통신 불능(간헐)
aws-node데몬셋 오류/크래시
6.1 aws-node 상태 확인
kubectl -n kube-system get ds aws-node
kubectl -n kube-system get pods -l k8s-app=aws-node -o wide
kubectl -n kube-system logs -l k8s-app=aws-node --tail=200
로그에서 자주 보는 힌트:
- IP 할당 실패, ENI attach 실패
failed to assign an IP address to container- 권한 문제(특히 IRSA/노드 IAM)
6.2 노드에서 Pod 네트워크가 깨진 경우 빠른 확인
노드에 SSM/SSH 접근이 가능하다면:
# 라우팅 테이블/인터페이스
ip route
ip addr
# conntrack/iptables는 환경에 따라
sudo iptables -S | head
노드 자체가 NotReady이거나 CNI plugin not initialized라면 트래픽 0 이전에 스케줄링/네트워크가 더 크게 깨진 상태일 수 있습니다. 이 케이스는 아래 글이 더 직접적입니다.
7) 재현/검증용 “네트워크 단위 테스트” 템플릿
원인 좁히기에서 중요한 건 “어디까지 되는지”를 항상 같은 방식으로 측정하는 것입니다. 아래는 현장에서 자주 쓰는 최소 템플릿입니다.
7.1 대상 Pod/Service 정보 수집
kubectl -n <ns> get pod -l app=my-api -o wide
kubectl -n <ns> get svc my-api -o wide
kubectl -n <ns> get endpoints my-api -o wide
7.2 경로별 curl
# 내부에서 Service로
kubectl -n <ns> run curl --rm -it --image=curlimages/curl -- \
curl -sv http://my-api.<ns>.svc.cluster.local:8080/healthz
# 내부에서 Pod IP로
kubectl -n <ns> run curl --rm -it --image=curlimages/curl -- \
curl -sv http://<pod-ip>:8080/healthz
7.3 tcpdump로 “패킷이 오긴 오는가” 확인(가능한 경우)
권한/보안정책이 허용된다면 노드에서 tcpdump는 결정타가 됩니다.
sudo tcpdump -ni any port 8080
- 패킷이 아예 안 보이면: SG/NetworkPolicy/ALB 타겟 경로 문제
- SYN은 오는데 ACK/응답이 없으면: 앱 리스닝/Pod 내부 방화벽/리턴 경로 문제
8) 흔한 원인 Top 6 (체크리스트)
- Service selector 불일치 / Endpoints 0
- targetPort 불일치(Service는 8080으로 보내는데 컨테이너는 8000 리스닝)
- NetworkPolicy default deny로 Ingress/Egress 차단
- ALB target-type과 SG 규칙 불일치
- instance인데 NodePort 미오픈
- ip인데 Pod SG/노드 SG에서 ALB SG 미허용
- Pod SG(Security Groups for Pods) 규칙 과도 제한
- aws-node(CNI) 장애/노드 IP 고갈/라우팅 이상
9) 마무리: “정상 경로”를 한 줄로 그려두면 빨라진다
EKS에서 트래픽 경로를 한 줄로 적어두면, 어디를 봐야 할지 즉시 결정됩니다.
- (외부) Client → ALB/NLB → (instance) NodePort → kube-proxy → Pod
- (외부) Client → ALB → (ip) Pod IP
- (내부) Pod → Service → kube-proxy → Pod
여기서 NetworkPolicy는 Pod 레벨, SG는 ENI/노드 레벨, CNI는 IP 할당/라우팅 레벨을 담당합니다. “Pod는 Running”은 애플리케이션 프로세스가 떴다는 의미일 뿐, 위 3계층 중 하나라도 막히면 트래픽은 0이 될 수 있습니다.
운영 중 가장 효율적인 접근은:
Pod IP direct curl로 Service/LB를 분리하고netpol로 정책 기반 차단 여부를 확인한 뒤SG + target-type로 AWS 경로 차단을 체크하고- 마지막으로
aws-node로 CNI 상태를 확인하는 것입니다.
이 루틴을 팀 런북으로 고정해두면, 비슷한 장애에서 평균 복구 시간이 크게 줄어듭니다.