본문으로 건너뛰기

네트워킹 디버깅

7분 읽기
AI용 문서 안내

네트워킹 디버깅 워크플로우​

글씨가 작으면 확대해서 확인할 수 있습니다.
다이어그램

VPC CNI 디버깅​

기본 점검​

# VPC CNI Pod 상태 확인
kubectl get pods -n kube-system -l k8s-app=aws-node

# VPC CNI 로그 확인
kubectl logs -n kube-system -l k8s-app=aws-node --tail=50

# 현재 VPC CNI 버전 확인
kubectl describe daemonset aws-node -n kube-system | grep Image

IP 고갈 문제 해결​

# 서브넷별 사용 가능 IP 확인
aws ec2 describe-subnets --subnet-ids <subnet-id> \
--query 'Subnets[].{ID:SubnetId,AZ:AvailabilityZone,Available:AvailableIpAddressCount}'

# Prefix Delegation 활성화 (IP 용량 16배 확대)
kubectl set env daemonset aws-node -n kube-system ENABLE_PREFIX_DELEGATION=true

# Prefix Delegation 활성화 확인
kubectl get daemonset aws-node -n kube-system -o yaml | grep ENABLE_PREFIX_DELEGATION
Prefix Delegation이란?

기본 모드에서는 ENI당 개별 Secondary IP를 할당합니다. Prefix Delegation을 활성화하면 ENI에 /28 prefix (16개 IP)를 할당하여 동일한 ENI로 16배 많은 Pod을 실행할 수 있습니다.

예: c5.xlarge 인스턴스

  • 기본 모드: 최대 58개 Pod (4 ENI × 15 IP - 1)
  • Prefix Delegation: 최대 110개 Pod (4 ENI × 16 prefix × 16 IP)
임시 조치와 영구 설정

kubectl set env로 바꾼 VPC CNI 값의 업데이트 결과는 충돌 처리 방식에 따라 달라집니다. PRESERVE로 보존할 수 있지만 OVERWRITE는 관리되는 필드를 덮어쓸 수 있습니다. 진단 후 설정을 add-on API로 이관할 때는 현재 configurationValues 전체에 변경을 병합합니다. 새 --configuration-values에 빠진 기존 고급 설정은 기본값으로 돌아갈 수 있습니다. NetworkAddressUsageLimitExceeded가 보이면 VPC NAU를, InsufficientFreeAddressesInSubnet이 보이면 실제 선택된 AZ·서브넷의 여유를 확인합니다. 다른 서브넷의 여유가 선택된 서브넷의 용량을 보장하지는 않습니다. IP 용량 계획과 Karpenter 노드 사이징을 함께 참조합니다.

ENI 제한 및 IP 한도​

각 EC2 인스턴스 타입에 따라 연결 가능한 ENI 수와 ENI당 IP 수가 제한됩니다.

# 인스턴스 타입별 ENI 한도 조회
aws ec2 describe-instance-types \
--instance-types c5.xlarge c5.2xlarge m5.xlarge \
--query 'InstanceTypes[].[InstanceType,NetworkInfo.MaximumNetworkInterfaces,NetworkInfo.Ipv4AddressesPerInterface]' \
--output table

# 노드의 현재 ENI 사용량 확인
kubectl get nodes -o json | jq -r '.items[] | {
name: .metadata.name,
allocatable_pods: .status.allocatable.pods,
max_pods: .status.capacity.pods
}'

DNS 트러블슈팅​

CoreDNS 기본 점검​

# CoreDNS Pod 상태 확인
kubectl get pods -n kube-system -l k8s-app=kube-dns

# CoreDNS 로그 확인
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50

# DNS 해석 테스트
kubectl run -it --rm debug --image=busybox --restart=Never -- nslookup kubernetes.default

# CoreDNS 설정 확인
kubectl get configmap coredns -n kube-system -o yaml

# CoreDNS 재시작
kubectl rollout restart deployment coredns -n kube-system

CoreDNS OOM 문제​

CoreDNS가 OOMKilled되면 클러스터 전체의 DNS 해석이 실패합니다.

# CoreDNS 메모리 사용량 확인
kubectl top pods -n kube-system -l k8s-app=kube-dns

# CoreDNS 메모리 limits 증가
kubectl set resources deployment coredns -n kube-system \
--limits=memory=300Mi --requests=memory=100Mi
CoreDNS OOM 원인
  • 대규모 클러스터 (5,000+ Pod)에서 쿼리 급증
  • DNS 캐싱 미설정으로 반복 쿼리
  • 악의적인 DNS Amplification 공격

해결: 메모리 증가 + NodeLocal DNSCache 사용

ndots:5 문제 및 해결​

Kubernetes의 기본 resolv.conf 설정에서 ndots:5는 외부 도메인 접근 시 불필요한 DNS 쿼리를 발생시킵니다.

# Pod 내부의 resolv.conf 확인
kubectl exec <pod-name> -- cat /etc/resolv.conf
# nameserver 10.100.0.10
# search default.svc.cluster.local svc.cluster.local cluster.local
# options ndots:5

# 문제: api.example.com 조회 시 다음 순서로 5번 쿼리 발생
# 1. api.example.com.default.svc.cluster.local (실패)
# 2. api.example.com.svc.cluster.local (실패)
# 3. api.example.com.cluster.local (실패)
# 4. api.example.com.ec2.internal (실패)
# 5. api.example.com (성공)

해결 방법 1: ndots 값 조정​

apiVersion: v1
kind: Pod
metadata:
name: app
spec:
dnsConfig:
options:
- name: ndots
value: "2" # 기본 5 → 2로 감소
containers:
- name: app
image: my-app:latest

해결 방법 2: FQDN에 trailing dot 추가​

# 애플리케이션 코드에서 외부 도메인 호출 시
curl https://api.example.com. # ← trailing dot으로 즉시 외부 DNS 조회

해결 방법 3: NodeLocal DNSCache 사용​

NodeLocal DNSCache는 각 노드에서 DNS 캐싱을 제공하여 CoreDNS 부하를 줄입니다.

# NodeLocal DNSCache 설치
kubectl apply -f https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dns/nodelocaldns/nodelocaldns.yaml

# 설치 확인
kubectl get pods -n kube-system -l k8s-app=node-local-dns
VPC DNS 스로틀링 한도

VPC DNS resolver는 ENI당 1,024 packets/sec 제한이 있습니다. 대규모 클러스터에서는 NodeLocal DNSCache로 VPC DNS 호출을 줄이는 것이 필수입니다.

Service 디버깅​

Service 연결 불가 패턴​

Pattern 1: Selector 라벨 불일치​

# Service 상태 확인
kubectl get svc <service-name>

# Endpoints 확인 (백엔드 Pod이 연결되어 있는지)
kubectl get endpoints <service-name>
# NAME ENDPOINTS
# web-service <none> ← 문제: Endpoints가 비어있음

# Service selector 확인
kubectl get svc <service-name> -o jsonpath='{.spec.selector}'
# {"app":"web","version":"v1"}

# Selector와 일치하는 Pod 확인
kubectl get pods -l app=web,version=v1
# No resources found ← 문제: 일치하는 Pod 없음

# 실제 Pod의 라벨 확인
kubectl get pods --show-labels
# NAME READY STATUS LABELS
# web-abc 1/1 Running app=web,ver=v1 ← 라벨이 "ver"로 오타

해결: Service selector를 Pod label과 일치시키기

# 방법 1: Service selector 수정
kubectl patch svc web-service -p '{"spec":{"selector":{"app":"web","ver":"v1"}}}'

# 방법 2: Pod label 수정 (Deployment template 수정 후 재배포)
kubectl set labels pod web-abc version=v1 --overwrite

Pattern 2: port vs targetPort 불일치​

# Service 설정
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
selector:
app: web
ports:
- port: 80 # ← Service가 노출하는 포트
targetPort: 8080 # ← Pod이 리스닝하는 포트 (여기가 틀리면 연결 실패)
# Pod이 실제로 리스닝하는 포트 확인
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[*].ports[*].containerPort}'
# 9090 ← 실제는 9090 포트인데 Service는 8080으로 설정됨

# Service targetPort 수정
kubectl patch svc web-service -p '{"spec":{"ports":[{"port":80,"targetPort":9090}]}}'

Pattern 3: Endpoints 확인​

# Endpoints 상세 확인
kubectl describe endpoints <service-name>

# Endpoints가 비어있으면:
# 1. Service selector와 Pod label 일치 확인
# 2. Pod이 Ready 상태인지 확인 (Not Ready Pod은 Endpoints에서 제외됨)
kubectl get pods -l app=web -o wide

일반적인 Service 문제​

증상확인 사항해결
Endpoints가 비어있음Service selector와 Pod label 불일치label 수정
ClusterIP 접근 불가kube-proxy 정상 동작 여부kubectl logs -n kube-system -l k8s-app=kube-proxy
NodePort 접근 불가Security Group에서 30000-32767 허용 여부SG Inbound 규칙 추가
LoadBalancer PendingAWS Load Balancer Controller 설치 여부controller 설치 및 IAM 권한 확인

NetworkPolicy 디버깅​

AND vs OR 셀렉터 혼동​

NetworkPolicy에서 가장 흔한 실수는 AND vs OR 셀렉터의 혼동입니다.

# AND 로직 (같은 from 항목 안에 두 셀렉터를 결합)
# "alice 네임스페이스의 client 역할 Pod" 만 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-alice-client-only
spec:
podSelector:
matchLabels:
app: web
ingress:
- from:
- namespaceSelector:
matchLabels:
user: alice
podSelector:
matchLabels:
role: client
# OR 로직 (별도의 from 항목으로 분리)
# "alice 네임스페이스의 모든 Pod" 또는 "모든 네임스페이스의 client 역할 Pod" 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-alice-or-client
spec:
podSelector:
matchLabels:
app: web
ingress:
- from:
- namespaceSelector:
matchLabels:
user: alice
- podSelector:
matchLabels:
role: client
AND vs OR 주의

위 두 YAML은 indent 한 레벨 차이로 완전히 다른 보안 정책이 됩니다. AND 로직에서는 namespaceSelector와 podSelector가 같은 - from 항목 안에 있고, OR 로직에서는 별도의 - from 항목으로 분리됩니다.

NetworkPolicy 차단 디버깅​

# 모든 NetworkPolicy 확인
kubectl get networkpolicy -n <namespace>

# 특정 Pod에 적용된 NetworkPolicy 확인
kubectl describe pod <pod-name> -n <namespace>

# NetworkPolicy가 트래픽을 차단하는지 테스트
kubectl run -it --rm debug --image=nicolaka/netshoot --restart=Never -- bash
# 내부에서:
curl -v http://<target-service>.<namespace>.svc.cluster.local

Default Deny 후 Allow 누락​

# Default Deny (모든 ingress 차단)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
# ingress 규칙 없음 → 모든 ingress 차단
# Allow 규칙 추가 (특정 트래픽 허용)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-frontend
namespace: production
spec:
podSelector:
matchLabels:
app: backend
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
Default Deny는 신중하게

Default Deny NetworkPolicy를 적용하면 명시적으로 허용하지 않은 모든 트래픽이 차단됩니다. 프로덕션에 적용하기 전에 필요한 Allow 규칙을 모두 작성하고 테스트 환경에서 검증하세요.

netshoot 활용법​

netshoot은 네트워크 디버깅에 필요한 모든 도구가 포함된 컨테이너 이미지입니다.

# 기존 Pod에 ephemeral container로 추가
kubectl debug <pod-name> -it --image=nicolaka/netshoot

# 독립 디버깅 Pod 실행
kubectl run tmp-shell --rm -i --tty --image nicolaka/netshoot

# 내부에서 사용할 수 있는 도구 예시:
# - curl, wget: HTTP 테스트
# - dig, nslookup: DNS 테스트
# - tcpdump: 패킷 캡처
# - iperf3: 대역폭 테스트
# - ss, netstat: 소켓 상태 확인
# - traceroute, mtr: 경로 추적

실전 디버깅 시나리오: Pod 간 통신 확인​

# netshoot Pod에서 다른 Service로 연결 테스트
kubectl run tmp-shell --rm -i --tty --image nicolaka/netshoot -- bash

# DNS 해석 확인
dig <service-name>.<namespace>.svc.cluster.local

# TCP 연결 테스트
curl -v http://<service-name>.<namespace>.svc.cluster.local:<port>/health

# 패킷 캡처 (특정 Pod IP로의 트래픽)
tcpdump -i any host <pod-ip> -n

# 경로 추적
traceroute <pod-ip>

# 소켓 상태 확인
ss -tunap

Ingress / LoadBalancer 디버깅​

AWS Load Balancer Controller 문제​

# Controller 상태 확인
kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller

# Controller 로그 확인
kubectl logs -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller --tail=100

# Ingress 상태 확인
kubectl describe ingress <ingress-name>

Target Group Health Check 실패​

AWS Load Balancer에서 Target Group의 Health Check가 실패하는 경우는 Health Check 불일치 문서를 참조하세요.

일반적인 원인:

  • Health Check path가 Pod의 실제 endpoint와 불일치
  • Health Check port가 Pod의 containerPort와 불일치
  • Security Group에서 Health Check 포트 미허용
  • readinessProbe 실패로 Pod이 NotReady 상태
# Target Group Health 확인
aws elbv2 describe-target-health \
--target-group-arn <tg-arn>

# Security Group Inbound 규칙 확인
aws ec2 describe-security-groups \
--group-ids <sg-id> \
--query 'SecurityGroups[].IpPermissions'

네트워킹 문제 체크리스트​

Layer 3/4 (기본 연결성)​

  • Pod이 IP를 할당받았는가? (kubectl get pod -o wide)
  • 서브넷에 사용 가능한 IP가 있는가?
  • Security Group이 필요한 포트를 허용하는가?
  • NetworkPolicy가 트래픽을 차단하지 않는가?

Layer 7 (애플리케이션)​

  • Service selector와 Pod label이 일치하는가?
  • Service port와 Pod containerPort가 일치하는가?
  • DNS 해석이 정상인가? (nslookup <service-name>)
  • Pod의 readinessProbe가 성공하는가?
  • Ingress Health Check path가 올바른가?

DNS 특화​

  • CoreDNS Pod이 Running 상태인가?
  • CoreDNS가 OOMKilled되지 않았는가?
  • ndots 설정이 적절한가? (외부 도메인 다수 호출 시 ndots:2 권장)
  • NodeLocal DNSCache가 설치되어 있는가? (대규모 클러스터)

관련 문서​