Published on

EKS에서 Pod egress는 되는데 ingress만 실패할 때

Authors

서버리스가 아니라 EKS(노드 기반)에서 Pod egress는 정상인데 ingress만 실패하면, 문제는 대부분 “Pod까지 들어오는 경로” 중 어딘가가 끊긴 것입니다. egress는 NAT/IGW 경로만 타면 되지만, ingress는 LB/NodePort/ClusterIP + kube-proxy/ipvs/iptables + 보안그룹/네트워크ACL + CNI 라우팅까지 복합적으로 얽힙니다.

이 글은 “외부에서 접속이 안 된다”를 정확히 어디에서 끊기는지로 분해해, 10~20분 안에 원인을 좁히는 실전 점검 순서를 제공합니다.

> 함께 읽기: Pod는 Running인데도 503이 뜨는 경우는 readiness/EndpointSlice가 원인인 경우가 많습니다. EKS에서 Pod는 Running인데 503가 뜰 때 점검

1) 먼저 용어를 명확히: “ingress 실패”의 4가지 패턴

ingress가 안 된다는 말은 실제로는 아래 중 하나입니다.

  1. LB까지는 도착하지만 Pod로 전달이 안 됨 (ALB/NLB target unhealthy, 5xx, timeout)
  2. Service는 있는데 Endpoints가 비어 있음 (selector 불일치, readiness false)
  3. Node까지는 오는데 NodePort/iptables에서 드랍 (kube-proxy, security group, NACL)
  4. Pod IP로 직접 접근이 안 됨 (VPC 라우팅/보안그룹, CNI 설정)

따라서 진단도 “어디까지 도착하나”를 기준으로 진행해야 합니다.

2) 5분 컷 1차 분해: Pod/Service/Endpoint 상태 확인

외부에서 안 들어오더라도, 클러스터 내부에서 Service가 Pod로 잘 붙는지부터 확인합니다.

2.1 Pod readiness와 포트 리스닝

kubectl -n <ns> get pod -o wide
kubectl -n <ns> describe pod <pod>
kubectl -n <ns> logs <pod> --tail=200

# 컨테이너가 실제로 포트를 리스닝하는지
kubectl -n <ns> exec -it <pod> -- sh -c "ss -lntp || netstat -lntp"
  • readiness probe가 실패하면 Endpoint에 등록되지 않아 ingress가 실패합니다.
  • 앱이 0.0.0.0이 아니라 127.0.0.1로만 바인딩하면 “Pod 내부에서는 되는데 외부에서는 안 됨”이 발생합니다.

2.2 Service selector/Endpoints/EndpointSlice

kubectl -n <ns> get svc <svc> -o yaml
kubectl -n <ns> get endpoints <svc> -o yaml
kubectl -n <ns> get endpointslice -l kubernetes.io/service-name=<svc>

# selector와 Pod label 매칭 확인
kubectl -n <ns> get pod --show-labels
  • endpoints가 비어 있으면 ingress는 100% 실패합니다.
  • selector 오타/label 변경, 또는 readiness false가 흔한 원인입니다.

> 이 케이스가 의심되면 위 내부 링크 글(Pod Running인데 503)의 체크리스트가 그대로 적용됩니다.

2.3 클러스터 내부에서 Service로 curl

kubectl -n <ns> run -it tmp --rm --image=curlimages/curl -- sh
# 같은 네임스페이스에서
curl -sv http://<svc>.<ns>.svc.cluster.local:<port>/health
  • 내부에서도 Service가 안 되면 LB 이전 문제가 아니라 Service/Pod/네트워크 정책 문제입니다.
  • 내부에서는 되는데 외부만 안 되면 LB/보안그룹/NodePort 쪽으로 범위를 좁힙니다.

3) LB를 쓰는 경우: ALB/NLB에서 “타깃이 왜 unhealthy인가”부터

EKS에서 ingress는 보통 ALB Ingress 또는 NLB Service로 들어옵니다. egress는 되는데 ingress만 실패하면, 흔히 타깃 그룹 헬스체크 또는 보안그룹 인바운드에서 끊깁니다.

3.1 ALB/NLB 타깃 타입 확인 (instance vs ip)

  • instance 타깃: LB → 노드의 NodePort로 들어감 → kube-proxy가 Pod로 전달
  • ip 타깃: LB → Pod IP로 직접 들어감 (보안그룹/라우팅이 다름)

AWS Load Balancer Controller를 쓴다면 Ingress/Service annotation에 따라 달라집니다.

kubectl -n <ns> get ingress <ing> -o yaml
kubectl -n <ns> get svc <svc> -o yaml

확인 포인트:

  • NLB + instance 타깃이면 노드 SG에 NodePort 인바운드가 필요합니다.
  • ALB + ip 타깃이면 Pod ENI(또는 노드 SG)로 들어오는 인바운드가 성립해야 합니다.

3.2 헬스체크 path/port/protocol 불일치

헬스체크가 200을 못 받으면 타깃이 unhealthy가 되고, 결국 ingress는 timeout/502/503이 됩니다.

  • 앱은 /healthz인데 헬스체크는 /로 되어 있음
  • 앱은 HTTPS인데 헬스체크는 HTTP로 시도
  • 컨테이너 포트와 Service targetPort가 불일치
kubectl -n <ns> describe ingress <ing>
kubectl -n <ns> describe svc <svc>

ALB 502/504가 섞여 보이면 아래 글의 “원인별 분기”가 도움이 됩니다.

3.3 AWS Load Balancer Controller/Webhook 문제

간혹 ingress 리소스는 만들어졌는데, 컨트롤러의 webhook 타임아웃/권한 문제로 정상 프로비저닝이 안 된 상태가 됩니다. 이 경우 “egress는 되는데 ingress가 안 됨”으로 관측됩니다.

  • Ingress 이벤트에 webhook timeout
  • TargetGroupBinding 생성 실패
kubectl -n kube-system logs deploy/aws-load-balancer-controller --tail=200
kubectl describe ingress <ing>

관련 케이스:

4) NodePort 경로 점검: “LB → 노드”는 되는데 “노드 → Pod”가 안 되는 경우

instance 타깃(또는 외부에서 NodePort로 직접 접근)이라면 트래픽 흐름은 다음과 같습니다.

Client → LB → Node SG 인바운드 → NodePort → kube-proxy(iptables/ipvs) → Pod

여기서 egress는 정상이어도 ingress만 실패하는 대표 원인은:

  • 노드 보안그룹이 NodePort 범위(기본 30000-32767) 를 허용하지 않음
  • NACL이 ephemeral 포트/리턴 트래픽을 차단
  • kube-proxy 비정상/iptables 꼬임

4.1 노드에서 NodePort 리스닝/iptables 룰 확인

노드에 SSM 또는 SSH로 접속 가능하다는 가정 하에:

# NodePort 확인
kubectl -n <ns> get svc <svc> -o jsonpath='{.spec.ports[0].nodePort}'; echo

# 노드에서 리스닝 확인(실제는 kube-proxy가 DNAT 처리)
sudo ss -lntp | grep <nodePort> || true

# iptables 규칙 확인(iptables 모드일 때)
sudo iptables -t nat -S | grep KUBE-SVC | head
sudo iptables -t nat -S | grep <nodePort> | head

# kube-proxy 상태
kubectl -n kube-system get ds kube-proxy -o wide
kubectl -n kube-system logs ds/kube-proxy --tail=200
  • kube-proxy가 CrashLooping이면 ingress 계열이 특히 불안정해집니다.
  • ipvs 모드라면 ipvsadm -Ln으로 서비스/엔드포인트가 잡혔는지 확인합니다.

4.2 보안그룹/네트워크ACL 체크 포인트

  • 노드 SG 인바운드: LB SG(또는 클라이언트 CIDR) → NodePort 허용
  • 노드 SG 아웃바운드: 응답 트래픽이 나갈 수 있어야 함(대개 all open)
  • NACL: 인바운드/아웃바운드 모두 허용(특히 ephemeral 포트 1024-65535)

NACL은 “egress는 되는데 ingress만 안 됨”을 만들기 쉬운 구성입니다. egress 테스트는 통과해도, 리턴 패킷이 NACL에서 막혀 세션이 성립하지 않는 일이 많습니다.

5) Pod IP로 직접 들어가는(ip target) 경우: CNI/보안그룹/라우팅

ALB/NLB가 Pod IP를 타깃으로 잡는 경우, 노드에서 DNAT하는 경로가 아니라 Pod 네트워크(ENI/secondary IP) 로 직접 들어옵니다.

이때 흔한 실패 원인:

  • Security Group for Pods(파드 SG) 사용 시, Pod SG 인바운드 규칙 누락
  • 서브넷 라우팅/피어링/Transit Gateway 경로 누락
  • Source NAT/Preserve client IP 설정에 따른 보안 정책 불일치

5.1 AWS VPC CNI 설정과 Pod SG 사용 여부

kubectl -n kube-system get ds aws-node -o yaml | sed -n '1,200p'
kubectl -n kube-system logs ds/aws-node --tail=200

확인 포인트:

  • ENABLE_POD_ENI, POD_SECURITY_GROUP_ENFORCING_MODE 등 환경변수
  • 특정 네임스페이스/Pod에 SG가 붙는지(어노테이션/리소스)

Pod SG를 쓰는 경우엔 “노드 SG는 열었는데도 ingress가 안 됨”이 발생합니다. 왜냐하면 실제 인바운드 판정이 Pod SG에서 이루어지기 때문입니다.

6) NetworkPolicy가 ingress만 막는 경우(특히 Calico)

EKS 기본 구성만 쓰면 NetworkPolicy는 동작하지 않지만, Calico 같은 CNI/정책 엔진을 올려두면 egress 허용 + ingress 차단 정책이 매우 흔합니다.

kubectl -n <ns> get networkpolicy
kubectl -n <ns> describe networkpolicy <policy>

대표 패턴:

  • 기본 deny ingress 정책이 있고, 특정 label/namespace에서만 허용
  • health check 소스(IP 대역) 또는 kube-system에서 오는 트래픽을 허용하지 않음

간단한 “모든 ingress 허용” 예시(진단용):

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all-ingress-temporary
  namespace: <ns>
spec:
  podSelector: {}
  policyTypes:
    - Ingress
  ingress:
    - {}

> 운영 적용 전에는 반드시 범위를 좁혀야 합니다. 진단 후 원복을 잊지 마세요.

7) 진단을 빠르게 만드는 재현/관측 도구 3가지

7.1 tcpdump로 “패킷이 노드/Pod까지 오나” 확인

노드에서:

# 예: NodePort로 들어오는지
sudo tcpdump -ni any port <nodePort>

# 예: Pod IP:port로 들어오는지
sudo tcpdump -ni any host <podIP> and port <containerPort>
  • 노드에도 패킷이 안 보이면 SG/NACL/LB 앞단 문제
  • 노드에는 보이는데 Pod로 안 가면 kube-proxy/라우팅/정책 문제

7.2 kube-proxy/conntrack 포화 확인

conntrack 테이블이 포화되면 신규 연결이 특히 ingress에서 실패합니다.

sudo sysctl net.netfilter.nf_conntrack_max
sudo cat /proc/sys/net/netfilter/nf_conntrack_count
sudo dmesg | grep -i conntrack | tail

7.3 애플리케이션 레벨: keep-alive/HTTP2/gRPC 크기

겉으로는 “ingress 실패”처럼 보이지만, 실제로는 ALB idle timeout, HTTP/2 설정, gRPC 메시지 크기 등 L7 문제일 수 있습니다.

  • 502/504가 특정 요청에서만 재현
  • 작은 요청은 되는데 큰 요청만 실패

관련 케이스:

8) 체크리스트(요약): 어디부터 볼까?

A. 내부에서 Service가 Pod로 붙나?

  • endpoints 비어있음 → selector/label/readiness
  • 내부 curl 실패 → 앱 리스닝/NetworkPolicy/DNS

B. LB 타깃은 healthy인가?

  • unhealthy → 헬스체크 path/port/protocol, SG, target type
  • ALB/NLB 이벤트/컨트롤러 로그 확인

C. instance 타깃이면 NodePort/노드 SG/NACL

  • NodePort 인바운드 허용
  • NACL ephemeral 포트/리턴 트래픽
  • kube-proxy/iptables/ipvs 상태

D. ip 타깃이면 Pod SG/CNI/라우팅

  • Pod SG 인바운드 규칙
  • VPC CNI 설정(secondary IP/ENI)

9) 마무리: “egress OK, ingress FAIL”은 경로를 그리면 풀린다

egress는 단방향으로 보이지만, ingress는 LB/노드/Pod/정책이 모두 맞아야 성립합니다. 따라서 가장 빠른 해결법은 감이 아니라:

  1. Endpoints가 존재하는지 확인하고
  2. LB 타깃 헬스/타깃 타입을 확인하고
  3. SG/NACL/NodePort(또는 Pod SG) 를 경로 기준으로 대조하는 것입니다.

이 3단계만 습관화해도 “왜 밖에서만 안 되지?” 류의 장애는 대부분 짧게 끝납니다.