Published on

EKS Pod는 뜨는데 트래픽 0 - NetPol·SG·CNI 10분 진단

Authors

서버가 멀쩡히 떠 있는데(Deployment/Pod Running) 트래픽이 0인 상황은 운영에서 가장 시간을 잡아먹는 유형입니다. 로그도 조용하고, HPA도 반응이 없고, ALB/NLB 헬스체크만 빨갛게 보이거나(혹은 반대로 초록인데도 사용자 트래픽은 0), 내부 호출도 안 되는 경우가 많습니다.

이 글은 **“Pod는 떴다”**를 전제로, 네트워크 계층에서 흔히 터지는 3대 축인 **NetworkPolicy / Security Group(SG) / CNI(aws-vpc-cni)**를 중심으로 10분 안에 원인을 좁히는 진단 루틴을 제공합니다.


0) 증상 정의: “트래픽 0”을 3가지로 분해

먼저 “트래픽이 0”이 정확히 무엇인지 분해하면 진단 속도가 빨라집니다.

  1. 외부 → Ingress/Service까지는 오는데 Pod로 전달이 안 됨
    • ALB/NLB 헬스체크 실패, Target deregistered, 5xx
  2. 클러스터 내부 Pod → 대상 Pod로 통신이 안 됨
    • 같은 네임스페이스에서도 timeout
  3. Pod는 요청을 받지만 응답이 못 나감(egress 차단)
    • 외부 API 호출, DB 연결, DNS 실패로 앱이 요청 처리 못함

이 글의 루틴은 위 1~3을 모두 커버하지만, 특히 **1과 2(인바운드 경로)**에서 NetworkPolicy/SG/CNI가 얽히는 경우를 집중합니다.


1) 10분 진단 루틴 개요(결론부터)

아래 순서대로 보면 보통 10분 내에 범위를 좁힐 수 있습니다.

  1. Service/Endpoints/Port 매칭 확인(1분)
  2. Pod IP로 직접 접근 테스트(2분)
  3. NetworkPolicy 존재 여부 및 기본 차단 여부 확인(2분)
  4. 노드/Pod ENI의 Security Group 인바운드/아웃바운드 확인(3분)
  5. 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과 일치하는가?
  • porttargetPort가 실제 컨테이너 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까지 도달하지 못하는 것입니다.

확인 루틴:

  1. ALB SG에서 아웃바운드가 막혀 있지 않은지(대부분 all 허용이지만, 제한형이면 체크)
  2. 노드 SG 또는 Pod SG 인바운드에 ALB SG를 소스로 허용했는지
  3. 포트가 실제 컨테이너 포트/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 (체크리스트)

  1. Service selector 불일치 / Endpoints 0
  2. targetPort 불일치(Service는 8080으로 보내는데 컨테이너는 8000 리스닝)
  3. NetworkPolicy default deny로 Ingress/Egress 차단
  4. ALB target-type과 SG 규칙 불일치
    • instance인데 NodePort 미오픈
    • ip인데 Pod SG/노드 SG에서 ALB SG 미허용
  5. Pod SG(Security Groups for Pods) 규칙 과도 제한
  6. 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 curlService/LB를 분리하고
  • netpol정책 기반 차단 여부를 확인한 뒤
  • SG + target-typeAWS 경로 차단을 체크하고
  • 마지막으로 aws-nodeCNI 상태를 확인하는 것입니다.

이 루틴을 팀 런북으로 고정해두면, 비슷한 장애에서 평균 복구 시간이 크게 줄어듭니다.