- Published on
EKS에서 NodePort만 안 열릴 때 CNI·SG 점검
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
EKS 운영 중 의외로 자주 겪는 장애가 있습니다. Pod 간 통신, ClusterIP, 심지어 LoadBalancer(Service)까지는 정상처럼 보이는데 NodePort만 외부에서 절대 안 열리는 상황입니다. 이때 문제를 “서비스가 안 떠서”라고 단정하면 시간을 크게 낭비합니다. NodePort는 kube-proxy(iptables/IPVS) + 노드 네트워크(ENI, 라우팅) + 보안 경계(SG/NACL) + CNI 동작이 맞물려 동작하기 때문에, 한 군데만 어긋나도 증상이 timeout 또는 connection refused로 나타납니다.
이 글은 EKS에서 NodePort만 막힐 때를 전제로, CNI(Amazon VPC CNI)와 보안그룹/네트워크 경로를 중심으로 가설을 빠르게 줄이는 체크리스트를 제공합니다. (kubectl/iptables/ss/tcpdump까지 포함)
관련 네트워크/제어 plane 진단 글로는 EKS에서 kubectl exec·logs가 안 될 때 진단법, CNI 초기화 문제는 EKS kubelet NotReady - CNI plugin not initialized 해결, 그리고 네트워크 타임아웃류는 EKS TLS handshake timeout 해결 - IRSA·VPC·CoreDNS도 함께 참고하면 좋습니다.
먼저 증상을 “정확히” 분류하기
NodePort 장애는 크게 2가지로 갈립니다.
- 노드까지는 도달하지만, 노드에서 Pod로 포워딩이 실패 (kube-proxy/iptables/IPVS, targetPort/selector, readiness 등)
- 노드 자체로 유입이 안 됨 (SG/NACL/라우팅/서브넷 경로/인스턴스 방화벽)
가장 먼저 “노드까지 패킷이 들어오는지”를 확인해야 합니다.
재현용 테스트 서비스 배포
아래는 가장 단순한 HTTP echo(NodePort) 예시입니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo
spec:
replicas: 2
selector:
matchLabels:
app: echo
template:
metadata:
labels:
app: echo
spec:
containers:
- name: echo
image: hashicorp/http-echo:1.0
args: ["-text=ok"]
ports:
- containerPort: 5678
---
apiVersion: v1
kind: Service
metadata:
name: echo-np
spec:
type: NodePort
selector:
app: echo
ports:
- name: http
port: 80
targetPort: 5678
nodePort: 30080
적용 후 상태 확인:
kubectl apply -f echo-nodeport.yaml
kubectl get svc echo-np -o wide
kubectl get endpoints echo-np -o wide
kubectl get pod -l app=echo -o wide
endpoints가 비어 있으면 selector/labels 또는 readiness 문제입니다. 이 경우 NodePort 이전에 Service 자체가 백엔드를 못 찾는 상태입니다.
노드 내부에서 NodePort가 열렸는지 확인
노드에 SSM/SSH로 접속 가능하다면(또는 debug daemonset) 아래를 확인합니다.
# NodePort 리스닝은 "프로세스가 listen"하는 형태가 아니라 iptables/IPVS로 처리됩니다.
# 따라서 ss로 안 보일 수 있습니다.
sudo iptables -t nat -S | grep 30080 || true
sudo iptables -S | grep 30080 || true
# IPVS 모드라면
sudo ipvsadm -Ln | grep 30080 || true
- 규칙이 아예 없다면 kube-proxy 문제(또는
serviceNodePortRange/정책) 가능성이 큽니다.
1단계: kube-proxy/Service 스펙 점검 (가장 빠른 로컬 원인 제거)
NodePort가 “노드에 도달했는데도” 안 되는 경우가 많습니다. 다음을 순서대로 확인합니다.
Service/Endpoint 매칭
kubectl describe svc echo-np
kubectl get endpointslice -l kubernetes.io/service-name=echo-np -o yaml | head
Endpoints:가 비어 있거나, 포트가 기대와 다르면targetPort/컨테이너 포트 설정을 재확인합니다.
externalTrafficPolicy 영향
externalTrafficPolicy: Local이면 해당 노드에 Pod가 없을 때 NodePort가 실패합니다(특히 timeout).
kubectl get svc echo-np -o jsonpath='{.spec.externalTrafficPolicy}{"\n"}'
Local이면 테스트로Cluster로 바꾸거나, 반드시 모든 노드에 Pod가 존재하도록 DaemonSet으로 재현해 보세요.
kube-proxy 상태
kubectl -n kube-system get ds kube-proxy -o wide
kubectl -n kube-system logs ds/kube-proxy --tail=200
- iptables sync 에러, conntrack 관련 에러, 권한 문제 등이 없는지 확인합니다.
2단계: “노드로 들어오는 트래픽”이 막히는지 확인 (SG/NACL/라우팅)
NodePort는 노드 ENI로 들어오는 인바운드가 열려야 합니다. 특히 EKS에서는 “Control Plane SG”가 아니라 노드(Worker) SG가 핵심입니다.
가장 흔한 원인: 노드 SG에 NodePort 인바운드가 없음
- 인바운드 규칙에
TCP 30080(또는 NodePort 범위)이 허용되어야 합니다. - 소스는 운영 정책에 맞게 제한하되, 디버깅 시에는 임시로 테스트 클라이언트 IP 또는 VPC CIDR로 좁혀 열어 확인합니다.
권장 방식(운영):
- 특정 NodePort만 열기(예: 30080)
- 또는 NodePort 범위(기본 30000-32767)를 필요한 최소로만
Security Group Inbound 예시
- TCP 30080 from <YourClientIP>/32
- (필요 시) ICMP from <YourClientIP>/32 # 경로 확인용
> 참고: NodePort 범위를 통째로 30000-32767 열어두면 편하지만, 노출면이 커집니다. 가능하면 NLB/ALB로 수렴시키고 NodePort는 내부용으로만 쓰는 것이 일반적입니다.
NACL(네트워크 ACL)에서 ephemeral 포트가 막히는 케이스
NACL은 stateless이므로 인바운드 NodePort 허용뿐 아니라 **아웃바운드 응답 트래픽(클라이언트 ephemeral 포트)**도 허용해야 합니다.
- 클라이언트가
src: 49152~65535같은 ephemeral 포트를 쓰면, 노드가 응답할 때 NACL 아웃바운드에서 차단될 수 있습니다.
점검 포인트:
- Subnet NACL 인바운드: TCP 30080 허용
- Subnet NACL 아웃바운드: TCP ephemeral(대개 1024-65535 또는 49152-65535) 허용
퍼블릭 서브넷/IGW/라우팅
외부(인터넷)에서 노드 퍼블릭 IP로 접근하는 테스트라면:
- 노드가 퍼블릭 서브넷에 있고
- 라우팅 테이블에
0.0.0.0/0 -> igw-... - 노드에 퍼블릭 IP(EIP 또는 auto-assign public IPv4)
이 중 하나라도 빠지면 외부에서 NodePort는 당연히 안 됩니다.
반대로 프라이빗 서브넷 노드라면:
- 외부에서 직접 NodePort로 접근하는 모델 자체가 맞지 않습니다.
- 이 경우는 NLB/ALB(LoadBalancer 타입) 또는 Bastion/VPN/Direct Connect 경유로 접근해야 합니다.
3단계: CNI(Amazon VPC CNI) 관점에서의 함정
NodePort 자체는 kube-proxy가 처리하지만, 노드↔Pod 경로와 SNAT/라우팅은 CNI 설정/동작에 의해 영향을 받습니다. 특히 다음 옵션들이 NodePort 장애를 “특정 조건에서만” 만들 수 있습니다.
(1) AWS_VPC_CNI_CUSTOM_NETWORK_CFG 사용 시 서브넷/SG 분리
Pod에 별도 서브넷/SG를 붙이는 커스텀 네트워크 구성을 사용하면:
- Pod ENI 보안그룹이 별도로 적용되고
- 노드 SG만 열어서는 해결이 안 되는 것처럼 보일 수 있습니다(특히 Pod로 직접 들어가는 트래픽 시나리오).
확인:
kubectl -n kube-system get ds aws-node -o yaml | grep -E 'CUSTOM_NETWORK_CFG|ENIConfig|POD_SECURITY_GROUP' -n
kubectl -n kube-system describe ds aws-node | sed -n '1,200p'
커스텀 네트워크 구성에서는 ENIConfig 리소스 및 서브넷 라우팅/SG를 함께 점검해야 합니다.
(2) Source IP 보존과 SNAT 설정
NodePort + externalTrafficPolicy: Local 조합은 소스 IP 보존을 위해 SNAT을 줄이지만, 그만큼 노드에 로컬 엔드포인트가 없으면 실패합니다. 또한 CNI의 SNAT 설정(AWS_VPC_K8S_CNI_EXTERNALSNAT)을 바꾸면 egress 경로가 달라져 “응답이 안 오는” 형태로 보일 수 있습니다.
CNI 환경 변수 확인:
kubectl -n kube-system exec ds/aws-node -c aws-node -- env | grep -E 'SNAT|EXTERNALSNAT|WARM|PREFIX|CUSTOM'
AWS_VPC_K8S_CNI_EXTERNALSNAT=true를 사용 중이면, NAT Gateway/외부 NAT 장비가 응답 경로를 제대로 처리하는지 확인해야 합니다.
(3) kubelet/CNI 이상 징후: ipamd/노드 라우팅 문제
CNI가 불안정하면 Pod IP 할당/라우팅이 꼬이고, 결과적으로 NodePort가 백엔드로 포워딩하지 못합니다.
kubectl -n kube-system logs ds/aws-node -c aws-node --tail=200
kubectl -n kube-system logs ds/aws-node -c aws-vpc-cni-init --tail=200
# 노드 상태에서 CNI 관련 이벤트
kubectl describe node <node-name> | sed -n '/Events:/,$p'
CNI plugin not initialized 같은 증상이 보이면 CNI 자체 복구가 우선이며, 이 경우는 NodePort만이 아니라 Pod 네트워크 전반에 문제가 동반됩니다. 이 케이스는 위의 CNI 초기화 글(EKS kubelet NotReady - CNI plugin not initialized 해결)의 흐름으로 접근하는 편이 빠릅니다.
4단계: 패킷이 어디서 끊기는지 “관측”으로 확정하기
가설을 끝내려면 관측이 필요합니다. 노드에서 tcpdump를 떠서 패킷이 노드에 들어오는지/나가는지를 보면 SG/NACL/iptables 중 어디가 문제인지 빠르게 확정됩니다.
노드에서 tcpdump
# 들어오는 NodePort 패킷이 보이는지
sudo tcpdump -ni any tcp port 30080
# 특정 인터페이스(대개 eth0)로 한정
sudo tcpdump -ni eth0 tcp port 30080
해석:
- 아무 패킷도 안 보임: SG/NACL/라우팅/클라이언트 경로 문제
- SYN은 들어오는데 SYN-ACK가 안 나감: 노드 로컬 방화벽/iptables/kube-proxy/커널 설정
- SYN/SYN-ACK 이후 데이터가 끊김: MTU/경로 MTU, conntrack, 중간 장비
Pod까지 트래픽이 전달되는지
Pod가 떠 있는 노드에서 Pod IP로 직접 확인:
POD_IP=$(kubectl get pod -l app=echo -o jsonpath='{.items[0].status.podIP}')
# 노드에서 Pod로 직접 curl
curl -sS http://$POD_IP:5678
- 노드→Pod가 안 되면 CNI/라우팅/보안그룹(커스텀 네트워크 시) 문제 가능성이 커집니다.
5단계: 자주 놓치는 설정/환경 체크리스트
노드 OS 레벨 방화벽
대부분 EKS AMI는 기본적으로 iptables를 kube-proxy가 사용하지만, 별도 hardening으로 firewalld/ufw가 켜져 있으면 인바운드가 막힐 수 있습니다.
sudo systemctl status firewalld || true
sudo ufw status || true
NodePort 범위 변경
클러스터가 기본 범위(30000-32767)가 아닌 경우도 있습니다. API 서버 플래그(--service-node-port-range)는 EKS 관리형이라 직접 보기 어렵지만, 최소한 Service의 nodePort가 기대 범위인지 확인하세요.
kubectl get svc echo-np -o jsonpath='{.spec.ports[0].nodePort}{"\n"}'
NLB/ALB 뒤에서 NodePort를 쓰는 경우
LoadBalancer 타입이 내부적으로 NodePort를 사용하기도 합니다(특히 instance target). 이때는 NLB SG(또는 NLB 소스 CIDR) → 노드 SG 인바운드가 열려야 합니다.
- NLB target type이
ip인지instance인지에 따라 열어야 하는 대상이 달라집니다.
결론: “NodePort만” 안 열릴 때의 최단 루트
정리하면, EKS에서 NodePort만 안 될 때 가장 짧은 진단 루트는 아래 순서입니다.
endpoints존재 여부로 Service 백엔드부터 확정externalTrafficPolicy: Local여부 확인(특히 특정 노드에서만 실패하는지)- 노드에서 iptables/IPVS 규칙 존재 확인(kube-proxy 정상 동작)
- tcpdump로 “노드에 패킷이 들어오는지”부터 관측
- 들어오지 않으면 SG/NACL/라우팅(퍼블릭/프라이빗 서브넷 모델 포함)
- 들어오는데 Pod로 안 가면 CNI/커스텀 네트워크/라우팅/SNAT 설정 점검
NodePort는 디버깅에 유용하지만, 운영에서 외부 노출은 보안/운영 부담이 큽니다. 외부 트래픽은 가능하면 NLB/ALB로 수렴시키고, NodePort는 내부 진단/특수 케이스로 제한하는 방향이 장애와 리스크를 함께 줄입니다.