Published on

EKS CoreDNS SERVFAIL·NXDOMAIN 간헐 해결 9가지

Authors

서버/클라이언트 모두 “가끔만” 터지는 DNS 장애는 가장 까다롭습니다. 특히 EKS에서 SERVFAIL(해석 실패)이나 NXDOMAIN(도메인 없음)이 간헐적으로 섞여 나오면, CoreDNS 자체 문제라기보다 네트워크 경로, 업스트림(VPC DNS), 노드 커널 리소스, 캐시/부하, 엔드포인트 변동이 복합적으로 얽혀 있는 경우가 많습니다.

이 글은 “증상 → 확인 명령 → 원인 추정 → 조치” 흐름으로, EKS에서 CoreDNS 간헐 장애를 가장 자주 유발하는 9가지를 우선순위대로 정리합니다. (마지막에 최소 변경으로 적용 가능한 튜닝 예시도 포함)

> 함께 보면 좋은 글: DNS/네트워크 이슈가 결국 STS/PrivateLink 타임아웃으로 번지는 케이스가 많습니다. EKS Pod STS AssumeRole 타임아웃 - NAT·PrivateLink·DNS

0) 먼저: SERVFAIL vs NXDOMAIN을 분리해서 보기

  • NXDOMAIN: “그 이름이 없다”는 정상 응답일 수도 있습니다. 검색 도메인(search domain), ndots, 잘못된 FQDN 조합 때문에 “의도치 않은 이름”을 조회해 NXDOMAIN이 날 수 있습니다.
  • SERVFAIL: CoreDNS가 업스트림에 질의했는데 실패했거나, 플러그인/네트워크/리소스 문제로 해석 자체가 실패했을 때 자주 나옵니다.

간헐 장애는 대부분 SERVFAIL 쪽이며, NXDOMAIN은 설정/쿼리 패턴 문제로 섞여 나타나는 경우가 많습니다.

빠른 재현/확인용 디버그 파드

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

# 파드 내부에서
cat /etc/resolv.conf
nslookup kubernetes.default.svc.cluster.local
nslookup google.com
for i in $(seq 1 50); do dig +time=1 +tries=1 kubernetes.default.svc.cluster.local; done
  • /etc/resolv.confsearch, options ndots: 값을 반드시 확인하세요.
  • dig +tries=1 +time=1로 타임아웃을 짧게 두면 간헐 실패가 더 잘 드러납니다.

1) CoreDNS 리소스 부족(CPU/메모리) + HPA 미적용/오토스케일 지연

전형적인 징후

  • 트래픽 피크 시간에만 SERVFAIL 증가
  • CoreDNS Pod의 RESTARTS가 증가하거나, 응답 지연이 튀면서 타임아웃
  • kubectl top pod -n kube-system에서 CoreDNS CPU가 지속적으로 80~100% 근접

확인

kubectl -n kube-system get deploy coredns -o wide
kubectl -n kube-system top pod -l k8s-app=kube-dns
kubectl -n kube-system describe hpa coredns 2>/dev/null || true
kubectl -n kube-system logs -l k8s-app=kube-dns --tail=200

해결

  • CoreDNS에 requests/limits를 현실적으로 잡고(너무 낮으면 스케줄링/스로틀로 지연), 필요 시 replica를 늘립니다.
  • HPA를 쓰더라도 DNS는 “스파이크에 민감”하므로, 최소 replica를 2 이상(가급적 3+)로 두는 편이 안전합니다.

예시(개념):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: coredns
  namespace: kube-system
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: coredns
        resources:
          requests:
            cpu: 200m
            memory: 256Mi
          limits:
            cpu: 500m
            memory: 512Mi

> 참고: 오토스케일링이 “폭주/진동”하는 환경이면 DNS도 같이 흔들립니다. 트래픽 특성상 HPA가 불안정하다면 큐 기반으로 안정화하는 접근도 고려하세요. EKS HPA 폭주를 KEDA 큐기반 오토스케일링으로 안정화

2) Node의 conntrack 테이블 포화로 DNS 패킷 드랍

DNS는 UDP/53이 많고 짧은 연결이 반복됩니다. 노드에서 nf_conntrack이 포화되면 “다른 트래픽은 멀쩡한데 DNS만 가끔 죽는” 현상이 발생할 수 있습니다.

확인

# 노드에서(SSM/SSH)
sudo sysctl net.netfilter.nf_conntrack_max
sudo cat /proc/sys/net/netfilter/nf_conntrack_count

dmesg | egrep -i 'conntrack|nf_conntrack' | tail

nf_conntrack_countnf_conntrack_max에 자주 근접하거나, 커널 로그에 drop 메시지가 보이면 유력합니다.

해결

  • conntrack 상향/타임아웃 조정
  • 트래픽 특성에 따라 노드 스케일아웃, NLB/프록시 구성 점검

> conntrack 포화는 EKS에서 매우 흔한 “간헐 끊김” 원인입니다. 더 깊게는 EKS conntrack 테이블 포화로 연결 끊김 해결법도 같이 보세요.

3) CoreDNS → 업스트림(VPC DNS) 경로 문제(NACL/SG/라우팅/MTU)

EKS의 CoreDNS는 보통 VPC의 AmazonProvidedDNS(예: 169.254.169.253 또는 VPC CIDR+2)로 포워딩합니다. 이 경로에서 UDP가 드랍되면 CoreDNS는 SERVFAIL을 반환하거나 타임아웃을 유발합니다.

확인 포인트

  • CoreDNS Pod가 떠 있는 노드의 보안그룹/네트워크 ACL에서 UDP/53, TCP/53이 허용되는지
  • 노드 ↔ VPC DNS 경로에 비대칭 라우팅/특수 라우팅이 있는지
  • MTU 이슈(특히 CNI/터널링/온프렘 연동)로 큰 응답이 조각화되며 드랍되는지

Corefile에서 업스트림 확인

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

대개 다음과 같은 형태입니다.

.:53 {
  forward . /etc/resolv.conf
  cache 30
}

해결

  • 네트워크 정책/방화벽/NACL에서 UDP 53 + TCP 53 모두 허용(큰 응답은 TCP로 폴백)
  • MTU가 의심되면 tracepath, ping -M do 등으로 경로 MTU 확인 후 조정

4) CoreDNS 캐시/포워더 튜닝 부재로 인한 지연 증폭

기본값으로도 동작은 하지만, 트래픽이 많거나 업스트림이 느릴 때는 CoreDNS의 cache, forward 설정이 체감 안정성에 큰 영향을 줍니다.

개선 아이디어

  • cache TTL을 적절히(너무 길면 레코드 변경 반영 지연, 너무 짧으면 업스트림 부하 증가)
  • forwardmax_concurrent를 둬서 폭주 시 큐잉/타임아웃을 제어
  • errors, log를 일시적으로 켜서 실패 유형을 분류

예시(Corefile 일부):

.:53 {
  errors
  health
  ready

  kubernetes cluster.local in-addr.arpa ip6.arpa {
    pods insecure
    fallthrough in-addr.arpa ip6.arpa
    ttl 30
  }

  prometheus :9153

  forward . /etc/resolv.conf {
    max_concurrent 1000
  }

  cache 30
  reload
}
  • max_concurrent는 환경에 따라 과하면 메모리/FD를 압박할 수 있으니 점진적으로 올리세요.

5) ndots/search domain으로 인한 “의도치 않은 NXDOMAIN 폭탄”

애플리케이션이 api 같은 짧은 이름을 조회하면, 리졸버는 ndotssearch 목록을 조합해 여러 FQDN을 순차 조회합니다. 이때 다수의 NXDOMAIN이 발생하고, 그 자체가 CoreDNS/업스트림 부하를 만들 수 있습니다.

확인

# 파드 내부
cat /etc/resolv.conf
# 예: options ndots:5

ndots:5는 쿠버네티스 기본값으로 흔하지만, 워크로드 특성에 따라 외부 도메인 조회가 많으면 불리할 수 있습니다.

해결

  • 가능하면 애플리케이션에서 FQDN 사용(예: service.namespace.svc.cluster.local)
  • 외부 도메인은 항상 점(.)으로 끝나는 FQDN(example.com.)을 써서 search 확장을 막기
  • Pod의 dnsConfigndots를 조정(주의: 전사적으로 바꾸기 전 영향도 테스트)

예시:

spec:
  dnsConfig:
    options:
    - name: ndots
      value: "2"

6) 엔드포인트 변동(롤링 업데이트/스케일링) + 짧은 TTL로 인한 순간 실패

서비스 뒤의 엔드포인트가 빠르게 바뀌는 환경(잦은 롤링/오토스케일)에서는, 클라이언트가 이전 IP를 잡고 있다가 연결 실패를 겪고 이를 “DNS 문제”로 오인하는 경우가 많습니다. 반대로 TTL이 너무 짧으면 DNS 질의량이 폭증합니다.

확인

kubectl get endpointslice -A | head
kubectl -n <ns> describe svc <svc>

해결

  • readinessProbe/terminationGracePeriod를 조정해 엔드포인트 교체를 부드럽게
  • CoreDNS kubernetes 플러그인의 ttl을 적절히(너무 낮으면 쿼리 폭증)

7) CoreDNS Pod의 노드 배치 문제(편향/단일 AZ/단일 노드)

CoreDNS replica가 있어도, 스케줄링이 한 노드/AZ에 몰리면 그 노드 장애/네트워크 이슈가 곧 DNS 장애가 됩니다.

확인

kubectl -n kube-system get pod -l k8s-app=kube-dns -o wide

노드가 분산되어 있는지, AZ가 분산되어 있는지 확인합니다.

해결

  • topologySpreadConstraints 또는 anti-affinity로 분산

예시(개념):

spec:
  template:
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                k8s-app: kube-dns
            topologyKey: kubernetes.io/hostname

8) NetworkPolicy로 DNS(e.g., kube-dns) egress/ingress가 간헐 차단

네임스페이스별 NetworkPolicy를 강하게 적용하면, 특정 파드/네임스페이스에서만 DNS가 실패합니다. “간헐”처럼 보이는 이유는 트래픽이 특정 파드로 라우팅될 때만 실패하기 때문입니다.

확인

kubectl get netpol -A
# 영향을 받는 네임스페이스에서
kubectl -n <ns> describe netpol

해결

  • 애플리케이션 네임스페이스에서 kube-dns(CoreDNS)로의 egress UDP/TCP 53 허용
  • kube-system의 CoreDNS에 대한 ingress도 정책에 따라 허용

9) 로그/메트릭 부재로 “원인 미상”이 반복되는 문제(관측성 보강)

간헐 장애는 재현이 어렵기 때문에, 증상이 있을 때의 증거를 남겨야 다음 번에 끝낼 수 있습니다.

CoreDNS 메트릭 켜기

Corefile에 prometheus :9153가 있는지 확인하고, 없다면 추가합니다.

kubectl -n kube-system get cm coredns -o yaml | sed -n '1,120p'

즉시 유용한 지표

  • coredns_dns_request_count_total
  • coredns_dns_response_rcode_count_total{rcode="SERVFAIL"}
  • coredns_forward_request_duration_seconds_bucket
  • coredns_cache_hits_total / coredns_cache_misses_total

장애 시점에 같이 수집하면 좋은 것

# CoreDNS 로그(일시적으로 log 플러그인 켠 뒤)
kubectl -n kube-system logs -l k8s-app=kube-dns --since=10m

# 노드 리소스/커널 이벤트
kubectl get events -A --sort-by=.lastTimestamp | tail -n 50

관측이 쌓이면 “업스트림 지연형(SERVFAIL)”인지, “쿼리 폭증형(NXDOMAIN 다발)”인지, “노드 커널 드랍형”인지 빠르게 갈라집니다.

적용 순서(실전 추천)

  1. CoreDNS Pod 분산 + replica 3(가용성)
  2. CoreDNS 리소스 상향 + 메트릭(prometheus) 활성화(병목 확인)
  3. conntrack 포화 여부 확인(간헐 드랍 1순위)
  4. 업스트림(VPC DNS) 경로의 UDP/TCP 53, MTU 점검
  5. ndots/search로 인한 NXDOMAIN 폭증 여부 확인
  6. forward/cache 튜닝(max_concurrent, cache TTL)
  7. NetworkPolicy 예외 규칙 정리

마무리: “DNS 문제”를 DNS로만 보지 말기

EKS에서 CoreDNS의 SERVFAIL/NXDOMAIN이 간헐적으로 보이면, CoreDNS 자체 버그보다 노드 커널 리소스(conntrack), 업스트림 경로(UDP/TCP/MTU), 쿼리 패턴(ndots/search), 배치 편향이 원인인 경우가 훨씬 많습니다. 위 9가지를 순서대로 체크하면 “재부팅/재배포로만 버티는 DNS”에서 벗어나, 재발 방지까지 가능한 형태로 정리할 수 있습니다.

추가로, DNS 이슈가 AWS API 호출(예: STS, ECR, PrivateLink) 타임아웃으로 확대되는 패턴이라면 앞서 언급한 글인 EKS Pod STS AssumeRole 타임아웃 - NAT·PrivateLink·DNS도 함께 점검해 보세요. DNS는 증상이고, 병목은 네트워크 경로/노드 상태인 경우가 많습니다.