Published on

EKS에서 Pod DNS 실패 - CoreDNS·VPC CNI 점검

Authors

서버리스나 애플리케이션 코드 문제처럼 보이지만, EKS에서 Pod DNS 장애는 대부분 클러스터 네트워킹(aws-node/VPC CNI) 또는 CoreDNS의 업스트림 경로에서 터집니다. 증상은 비슷해도 원인은 다르므로, “Pod → CoreDNS → 업스트림(VPC DNS/Route53 Resolver) → 목적지” 경로를 단계적으로 검증하는 게 가장 빠릅니다.

이 글은 다음과 같은 상황을 전제로 합니다.

  • Pod에서 nslookup/kdig가 타임아웃 또는 SERVFAIL/NXDOMAIN을 반환
  • 특정 노드/특정 AZ에서만 재현되거나, 트래픽 피크 때만 간헐적으로 발생
  • CoreDNS는 Running인데도 DNS가 불안정

아래 절차대로 진행하면 CoreDNS 설정 문제인지, VPC CNI(IP/ENI/SG/NACL) 문제인지, 노드 로컬 DNS/iptables 문제인지를 비교적 짧은 시간에 분리할 수 있습니다.

1) 장애 패턴 먼저 분류하기

DNS 장애는 “어디까지 패킷이 가는지”로 나누면 빠릅니다.

1-1. 흔한 에러 메시지

  • i/o timeout / no servers could be reached
    • 대개 CoreDNS까지 못 감(Pod→kube-dns Service/Endpoint 경로) 또는 CoreDNS가 업스트림으로 못 나감(CoreDNS→VPC DNS)
  • SERVFAIL
    • CoreDNS가 응답은 했지만 업스트림 실패(VPC DNS/Resolver, 네트워크, CoreDNS upstream 설정)
  • NXDOMAIN
    • 진짜로 레코드가 없거나, search domain/ndots로 인해 엉뚱한 FQDN을 조회 중

1-2. 재현을 위한 표준 테스트 Pod

문제 재현/진단은 항상 “클러스터 내부 DNS”와 “외부 도메인”을 나눠 테스트합니다.

kubectl run -it --rm dns-debug \
  --image=registry.k8s.io/e2e-test-images/jessie-dnsutils:1.3 \
  --restart=Never -- sh

# 클러스터 내부
nslookup kubernetes.default.svc.cluster.local

# 외부 도메인
nslookup amazon.com

# CoreDNS 서비스 IP 확인
cat /etc/resolv.conf
  • 내부 서비스(kubernetes.default...)도 실패하면 CoreDNS/Service/Endpoint/iptables 쪽 가능성이 큽니다.
  • 내부는 되는데 외부만 실패하면 CoreDNS 업스트림(VPC DNS/egress) 가능성이 큽니다.

2) Pod의 resolv.conf부터 확인(의외로 자주 원인)

Pod의 /etc/resolv.conf는 “DNS 서버가 누구인지, search/ndots가 어떤지”를 보여줍니다.

kubectl exec -it dns-debug -- cat /etc/resolv.conf

정상 예시는 대략 다음과 같습니다.

  • nameserverkube-dns Service IP(보통 172.20.0.10 같은 ClusterIP)
  • searchdefault.svc.cluster.local
  • options ndots:5 (기본)

2-1. ndots/search로 인한 지연·타임아웃

ndots:5 환경에서 api.mycorp.com 같은 도메인을 조회하면, 먼저 다음처럼 여러 번 “붙여서” 질의합니다.

  • api.mycorp.com.default.svc.cluster.local
  • api.mycorp.com.svc.cluster.local
  • ...
  • 마지막에 api.mycorp.com

업스트림이 느리거나 CoreDNS가 바쁘면 이 과정이 누적되어 간헐적 타임아웃으로 보일 수 있습니다. 이때는:

  • 애플리케이션에서 가능한 FQDN을 명확히 사용
  • 필요 시 Pod 단위로 dnsConfig 조정(무분별한 변경은 권장하지 않음)
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  dnsConfig:
    options:
      - name: ndots
        value: "2"

3) CoreDNS 상태 점검: “Running”은 의미가 약하다

CoreDNS가 Running이어도, 업스트림 타임아웃/conntrack 고갈/CPU throttling 등으로 질의가 누락될 수 있습니다.

3-1. CoreDNS 로그/지표 확인

kubectl -n kube-system get deploy coredns
kubectl -n kube-system get pods -l k8s-app=kube-dns -o wide
kubectl -n kube-system logs -l k8s-app=kube-dns --tail=200

로그에서 자주 보이는 힌트:

  • plugin/errors: 업스트림 실패
  • timeout: 업스트림 또는 네트워크 지연
  • SERVFAIL: upstream 응답 실패

가능하면 CoreDNS에 log 플러그인을 잠시 활성화해 “질의가 들어오는지/어디서 막히는지”를 확인합니다(장애 시 임시로만).

3-2. Corefile에서 upstream 확인

kubectl -n kube-system get configmap coredns -o yaml

EKS 기본은 대개 다음 형태입니다.

.:53 {
    errors
    health
    ready
    kubernetes cluster.local in-addr.arpa ip6.arpa {
        pods insecure
        fallthrough in-addr.arpa ip6.arpa
    }
    prometheus :9153
    forward . /etc/resolv.conf
    cache 30
    loop
    reload
    loadbalance
}

핵심은 forward . /etc/resolv.conf입니다.

  • CoreDNS Pod의 /etc/resolv.conf는 보통 노드/VPC DNS(AmazonProvidedDNS, 예: VPC CIDR + 2)로 향합니다.
  • 즉, CoreDNS → VPC DNS 경로가 막히면 외부 도메인 해석이 실패합니다.

3-3. CoreDNS Pod에서 업스트림 직접 테스트

CoreDNS Pod에 들어가서 업스트림이 살아있는지 확인합니다.

COREDNS_POD=$(kubectl -n kube-system get pod -l k8s-app=kube-dns -o jsonpath='{.items[0].metadata.name}')

kubectl -n kube-system exec -it "$COREDNS_POD" -- sh -c "cat /etc/resolv.conf; nslookup amazon.com"
  • 여기서도 실패하면 CoreDNS→업스트림 또는 노드 네트워크 문제 가능성이 큽니다.
  • 여기서는 되는데 일반 Pod에서만 실패하면 Pod→CoreDNS(Service/Endpoint/iptables) 쪽을 봐야 합니다.

4) kube-dns Service/Endpoint 경로 확인(“CoreDNS까지 못 간다”)

Pod는 보통 kube-dns Service(ClusterIP)로 질의합니다. 이 Service가 올바른 Endpoint로 라우팅되는지 확인합니다.

kubectl -n kube-system get svc kube-dns -o wide
kubectl -n kube-system get endpoints kube-dns -o wide
  • Endpoint가 비어 있으면 Selector/라벨 문제 또는 CoreDNS Pod 준비 실패
  • Endpoint가 특정 노드에만 몰려 있으면(스케줄링/anti-affinity 부재) 해당 노드 장애 시 DNS 전체가 흔들립니다.

권장:

  • CoreDNS replica를 2개 이상
  • 가능하면 AZ/노드 분산(anti-affinity)

5) AWS VPC CNI(aws-node) 관점: IP/ENI/보안정책 문제

EKS DNS 장애의 “진짜” 원인은 CoreDNS가 아니라 CNI가 만든 네트워크 조건인 경우가 많습니다.

5-1. 우선 aws-node DaemonSet 상태 확인

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 할당 실패
  • InsufficientCidrBlocks, PrivateIpAddressLimitExceeded
  • 노드별 IP 부족(=Pod IP 부족)으로 인한 이상 동작

5-2. 노드의 Pod IP 고갈은 DNS를 “간헐적으로” 깨뜨린다

Pod DNS가 실패하는데 다른 네트워크도 간헐적으로 끊긴다면, 노드가 IP 포화 상태일 수 있습니다.

  • 새 Pod가 뜨면서 IP 할당이 지연/실패
  • 기존 Pod의 네트워크도 비정상(특히 conntrack/iptables가 함께 압박받을 때)

대응:

  • 인스턴스 타입별 최대 ENI/IP 확인 후 노드 스케일
  • 서브넷 CIDR 확장 또는 서브넷 추가
  • Prefix Delegation 사용(환경에 따라)

5-3. Security Group / NACL: DNS(53)와 CoreDNS(UDP/TCP) 경로

DNS는 기본적으로 UDP 53을 쓰지만, 응답이 크거나 특정 상황에서는 TCP 53으로 전환됩니다. 따라서 UDP만 열어두면 간헐 장애가 날 수 있습니다.

점검 포인트:

  • 노드 SG/NACL에서 노드↔노드 트래픽이 충분히 허용되는지(특히 CoreDNS Pod가 있는 노드로)
  • 노드에서 VPC DNS(AmazonProvidedDNS)로 UDP/TCP 53이 허용되는지
  • NetworkPolicy를 사용한다면 CoreDNS로의 egress/ingress가 막히지 않는지

5-4. kube-proxy/iptables/conntrack 병목

kube-dns는 ClusterIP(Service)이므로, Pod의 DNS 질의는 iptables/nftables 규칙과 conntrack을 거칩니다.

  • 트래픽이 많은 클러스터에서 conntrack 테이블이 부족하면 UDP 드롭이 늘고 DNS가 먼저 체감됩니다.
  • 증상: 피크 타임에만 i/o timeout 증가

노드에서 확인(권한 필요):

# 노드에 접속 후
sudo sysctl net.netfilter.nf_conntrack_max
sudo cat /proc/sys/net/netfilter/nf_conntrack_count

# 드롭/에러 카운터(환경에 따라 명령 상이)
sudo conntrack -S || true

대응은 워크로드 특성에 따라 다르지만, 일반적으로:

  • 노드 스케일 아웃(트래픽 분산)
  • conntrack 튜닝(운영 정책에 따라 신중히)
  • CoreDNS 수평 확장 및 리소스 상향

6) CoreDNS 스케일/리소스/캐시 전략

DNS 문제는 “네트워크”만큼이나 “리소스 부족”도 흔합니다.

6-1. CoreDNS 리소스 요청/제한 확인

kubectl -n kube-system get deploy coredns -o yaml | sed -n '/resources:/,/imagePullPolicy/p'

CPU limit이 너무 낮으면 throttling으로 타임아웃이 늘 수 있습니다. 트래픽이 많다면:

  • replica 증가
  • CPU request/limit 상향
  • cache TTL 조정(너무 길면 변경 반영 지연, 너무 짧으면 upstream 부하)

6-2. 간단한 HPA 예시(환경에 맞게 조정)

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: coredns
  namespace: kube-system
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: coredns
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

(메트릭 수집이 되어 있어야 하며, 운영 환경에서는 DNS QPS 기반 지표가 더 유용할 수 있습니다.)

7) 장애를 더 빨리 좁히는 “체크리스트”

아래 순서대로 보면 보통 10~20분 내로 범위를 좁힙니다.

  1. Pod에서 /etc/resolv.conf 확인: nameserver가 kube-dns인지
  2. 내부 도메인 vs 외부 도메인 분리 테스트
  3. kube-dns Service/Endpoints 확인
  4. CoreDNS 로그에서 timeout/SERVFAIL 확인
  5. CoreDNS Pod에서 업스트림(nslookup) 테스트
  6. aws-node 로그/상태 확인(IP/ENI 문제)
  7. SG/NACL/NetworkPolicy에서 UDP+TCP 53 및 노드 간 통신 확인
  8. 피크 타임이면 conntrack/리소스 병목 의심 → CoreDNS 스케일/노드 스케일

8) 자주 같이 터지는 EKS 네트워크 이슈

DNS가 흔들릴 때는 “다른 제어 플레인 경로도 같이” 문제가 보이곤 합니다. 예를 들어 kubectl logs/exec가 갑자기 불안정해지면 노드 네트워크/보안그룹/프록시 경로를 함께 의심해야 합니다. 이 경우는 아래 글의 점검 루틴이 바로 도움이 됩니다.

또한 DNS 자체 문제로 보였는데 실제로는 Pod의 AWS API 호출이 시간/네트워크 문제로 실패하는 케이스도 있습니다(예: S3 업로드 오류가 DNS/시간 동기화 문제와 함께 관측). 네트워크 이상 징후를 함께 볼 때 참고할 만합니다.

클러스터 오토스케일링(Karpenter) 도입 후 특정 노드군에서만 DNS가 실패한다면, 노드가 충분히 늘지 않아 과밀(리소스/conntrack/IP) 상태가 되었을 가능성도 큽니다.

9) 결론: “CoreDNS 문제처럼 보여도 CNI부터 의심하라”

EKS의 Pod DNS 장애는 표면적으로 nslookup timeout 하나로 보이지만, 실제로는 다음 두 축에서 갈립니다.

  • CoreDNS 자체(리소스/설정/업스트림 forward)
  • AWS VPC CNI(노드 IP/ENI, SG/NACL, conntrack, 노드 간 통신)

가장 효율적인 접근은 “Pod → kube-dns Service → CoreDNS Pod → VPC DNS”를 한 단계씩 끊어서 확인하는 것입니다. 특히 CoreDNS Pod 안에서 업스트림 조회가 되는지는 원인 분리에 결정적입니다.

운영 환경에서는 재발 방지를 위해 아래를 권장합니다.

  • CoreDNS replica 2+ 및 AZ 분산
  • CoreDNS 리소스 상향 및 필요 시 HPA
  • 노드 IP/ENI 여유 확보(서브넷/인스턴스 타입/Prefix Delegation 검토)
  • SG/NACL에서 DNS UDP+TCP 53 및 노드 간 통신 명확히 허용
  • 피크 트래픽 클러스터는 conntrack/노드 스케일링 계획 포함

이 정도까지 정리해두면, “갑자기 특정 Pod만 DNS가 안 된다” 같은 사건도 로그 몇 줄과 몇 번의 nslookup으로 빠르게 수습할 수 있습니다.