Published on

EKS CoreDNS CrashLoopBackOff - upstream 타임아웃 해결

Authors

서론

EKS 운영 중 갑자기 서비스들이 DNS를 못 풀기 시작하고, kube-system의 CoreDNS Pod가 CrashLoopBackOff로 떨어지는 상황은 꽤 흔합니다. 특히 로그에 upstream 관련 타임아웃이 반복되면(예: read udp ... i/o timeout), CoreDNS 자체 문제라기보다 CoreDNS가 포워딩하는 상위 DNS(Upstream)까지 네트워크가 닿지 않는 경우가 대부분입니다.

이 글은 “CoreDNS CrashLoopBackOff + upstream 타임아웃” 조합을 재현 가능한 점검 순서로 쪼개고, EKS에서 자주 터지는 원인(노드의 /etc/resolv.conf, VPC DNS, NAT/라우팅, 보안그룹/NACL, NetworkPolicy, Corefile 루프 등)을 증상-원인-해결 형태로 정리합니다.

증상 패턴: 로그로 유형부터 분류

먼저 CoreDNS 로그를 봅니다.

kubectl -n kube-system logs -l k8s-app=kube-dns --tail=200

자주 보이는 패턴은 다음과 같습니다.

  • read udp <coredns-ip>:<port>-><upstream-ip>:53: i/o timeout
    • CoreDNS가 upstream(대개 노드의 resolv.conf에 있는 DNS, 또는 VPC Resolver 169.254.169.253)로 UDP 53을 쐈는데 응답이 없음
  • plugin/forward: no healthy upstream
    • forward 플러그인이 upstream을 모두 실패로 판단
  • plugin/loop: Loop ... 또는 too many loops
    • CoreDNS가 자기 자신(또는 클러스터 DNS)으로 포워딩하면서 무한 루프

CrashLoopBackOff는 보통 CoreDNS가 DNS 실패 자체로 죽는다기보다, 설정 오류/루프/헬스체크 실패로 재시작을 반복하는 경우가 많습니다.

1단계: CoreDNS Pod는 살아있나? (리소스/프로브 확인)

우선 Crash 원인이 단순 OOM/CPU 스로틀인지 확인합니다.

kubectl -n kube-system describe pod -l k8s-app=kube-dns | sed -n '/State:/,/Events:/p'
kubectl -n kube-system top pod -l k8s-app=kube-dns
  • OOMKilled라면 메모리 요청/제한이 너무 낮거나, 질의 폭주(예: 잘못된 앱 리트라이)일 수 있습니다.
  • Liveness probe failed가 반복되면 CoreDNS가 응답을 못 하거나(내부적으로 forward 실패), 루프 감지로 종료되는 케이스가 많습니다.

리소스가 정상인데도 upstream timeout이면 다음 단계로 갑니다.

2단계: “CoreDNS → Upstream” 경로가 막혔는지 확인

EKS에서 CoreDNS는 보통 forward . /etc/resolv.conf로 설정되어 있고, 이는 CoreDNS 컨테이너의 /etc/resolv.conf(대부분 노드 설정을 기반)로 upstream을 찾습니다.

CoreDNS ConfigMap을 확인합니다.

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

대표적인 기본값은 대략 이런 형태입니다.

.: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입니다. 즉, upstream 타임아웃은 곧 (1) resolv.conf가 이상하거나 (2) resolv.conf가 가리키는 DNS로 네트워크가 안 나가거나 둘 중 하나입니다.

2-1) CoreDNS 컨테이너의 resolv.conf 확인

# CoreDNS Pod 하나를 집어서 확인
POD=$(kubectl -n kube-system get pod -l k8s-app=kube-dns -o jsonpath='{.items[0].metadata.name}')

kubectl -n kube-system exec -it "$POD" -- cat /etc/resolv.conf

여기서 다음을 봅니다.

  • nameserver가 무엇인지
    • EKS/VPC에서는 보통 169.254.169.253(VPC DNS Resolver) 또는 노드가 받은 VPC DNS IP
  • nameserver 127.0.0.53 같은 값이 있는지
    • systemd-resolved 스텁을 가리키는 값인데, 컨테이너/네임스페이스 환경에서 제대로 동작하지 않아 타임아웃을 유발할 수 있습니다.

2-2) CoreDNS에서 upstream:53로 실제 질의가 되는지 테스트

CoreDNS 컨테이너에는 도구가 없을 수 있으니, 임시 디버그 Pod를 띄워 테스트합니다.

kubectl run -it --rm dns-debug \
  --image=public.ecr.aws/docker/library/busybox:1.36 \
  --restart=Never \
  -- sh

Pod 안에서:

# 1) 클러스터 DNS(CoreDNS Service)로 질의
nslookup kubernetes.default.svc.cluster.local 10.100.0.10

# 2) VPC Resolver로 직접 질의(대표값)
nslookup amazon.com 169.254.169.253
  • 2)가 타임아웃이면: VPC DNS/라우팅/보안 정책 문제 가능성이 큽니다.
  • 2)는 되는데 1)이 안 되면: CoreDNS 자체 설정/NetworkPolicy/Service 라우팅 문제를 봐야 합니다.

3단계: EKS에서 upstream 타임아웃의 대표 원인 6가지

원인 A) 노드 resolv.conf가 127.0.0.53(systemd-resolved)를 가리킴

증상

  • CoreDNS 로그: upstream 127.0.0.53:53로 타임아웃
  • CoreDNS /etc/resolv.confnameserver 127.0.0.53

해결 방향

  • CoreDNS가 /etc/resolv.conf를 따라가면 안 됩니다. VPC Resolver를 명시하거나, 노드의 resolv.conf가 올바른 nameserver를 갖도록 해야 합니다.

가장 빠른 우회(권장되는 응급처치): CoreDNS forward를 명시적으로 바꿉니다.

kubectl -n kube-system edit cm coredns

예시(환경에 맞게 1~2개 지정):

forward . 169.254.169.253 {
    max_concurrent 1000
}

적용 후 롤링 재시작:

kubectl -n kube-system rollout restart deploy coredns

주의

  • 장기적으로는 노드 AMI/부트스트랩에서 resolv.conf 구성을 바로잡는 게 좋습니다.

원인 B) 프라이빗 서브넷에서 NAT/라우팅 문제로 외부 DNS가 불가

EKS 노드가 프라이빗 서브넷에 있고, upstream이 퍼블릭 DNS(예: 8.8.8.8)인데 NAT가 없거나 라우팅이 잘못되면 UDP 53이 나가지 못합니다.

확인 포인트

  • 노드가 있는 서브넷 라우팅 테이블에 0.0.0.0/0 -> NAT Gateway가 있는지
  • NACL에서 ephemeral port(1024-65535) 아웃바운드/인바운드가 막혀 있지 않은지

해결

  • upstream을 VPC Resolver(보통 169.254.169.253)로 통일
  • NAT Gateway/Route Table/NACL 수정

원인 C) 보안그룹/네트워크 정책이 UDP/TCP 53을 차단

CoreDNS는 upstream으로 UDP 53을 주로 사용하지만, 응답이 크거나 fallback 시 TCP 53을 쓰기도 합니다.

확인

  • 노드/ENI 보안그룹 egress가 제한된 환경인지
  • Calico/Cilium NetworkPolicy로 kube-system의 CoreDNS egress가 막혔는지

NetworkPolicy 예시(허용 정책이 필요한 환경에서):

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns-egress
  namespace: kube-system
spec:
  podSelector:
    matchLabels:
      k8s-app: kube-dns
  policyTypes:
  - Egress
  egress:
  - to:
    - ipBlock:
        cidr: 169.254.169.253/32
    ports:
    - protocol: UDP
      port: 53
    - protocol: TCP
      port: 53

원인 D) CoreDNS loop(자기 자신으로 포워딩)로 Crash

증상

  • plugin/loop 관련 로그
  • Corefile에서 forward 대상이 결국 CoreDNS Service IP로 돌아오거나, /etc/resolv.conf가 클러스터 DNS를 가리킴

해결

  • /etc/resolv.conf에 클러스터 DNS(예: 10.100.0.10)가 들어가 있지 않게 구성
  • forward를 VPC Resolver로 명시
  • loop 플러그인은 원인 파악에 유용하지만, 루프가 해결되기 전까지는 Crash를 유발할 수 있으니 설정 변경 후 재기동

원인 E) VPC DNS 기능 비활성화(EnableDnsSupport/EnableDnsHostnames)

EKS는 VPC DNS에 강하게 의존합니다. VPC 설정에서 DNS 지원이 꺼져 있으면 169.254.169.253이 동작하지 않거나 기대대로 응답하지 않습니다.

확인

  • VPC의 EnableDnsSupport = true
  • VPC의 EnableDnsHostnames = true

해결

  • VPC 설정을 활성화한 뒤 CoreDNS 재시작

원인 F) 노드 로컬 DNS(NodeLocal DNSCache) 구성 불일치

NodeLocal DNSCache를 쓰는 환경에서, CoreDNS/NodeLocal의 upstream 체인이 꼬이면 타임아웃이 발생할 수 있습니다.

확인

  • kube-systemnode-local-dns DaemonSet 존재 여부
  • CoreDNS가 NodeLocal(예: 169.254.20.10)로 포워딩하는지, 반대로 NodeLocal이 CoreDNS로 포워딩하는지

해결

  • 체인을 단순화: CoreDNS → VPC Resolver, 또는 NodeLocal → VPC Resolver / CoreDNS는 클러스터 도메인만 담당
  • 구성 변경 후 둘 다 롤아웃

4단계: “CoreDNS는 정상인데 여전히 503/타임아웃”과의 구분

DNS 장애는 애플리케이션에서 503/타임아웃으로 보일 때가 많습니다. CoreDNS가 회복된 뒤에도 서비스가 503이면, 엔드포인트/레디니스/EndpointSlice 문제일 수 있습니다. 이 경우는 DNS가 아니라 트래픽 라우팅 계층을 봐야 합니다.

5단계: 재발 방지 체크리스트(운영 관점)

5-1) CoreDNS에 최소한의 관측성 추가

  • CoreDNS 메트릭(9153) 스크랩
  • 로그에서 no healthy upstream 알람
  • HPA는 DNS 장애 상황에서 역효과(스케일아웃이 문제를 해결하지 못함)일 수 있으니, 원인에 따라 신중히 적용

5-2) Corefile 변경은 GitOps로 관리

수동 kubectl edit는 응급처치로만 쓰고, 이후에는 Helm/Kustomize/GitOps로 이력을 남기는 게 안전합니다.

5-3) 노드/클러스터 네트워크 변경 시 DNS 회귀 테스트

배포 파이프라인에 다음을 포함하면 좋습니다.

kubectl run -it --rm dns-smoke \
  --image=public.ecr.aws/docker/library/busybox:1.36 \
  --restart=Never -- \
  sh -c 'nslookup kubernetes.default.svc.cluster.local && nslookup amazon.com 169.254.169.253'

6단계: 자주 묻는 질문(실전에서 많이 헷갈리는 지점)

Q1. upstream을 8.8.8.8로 박아도 되나요?

가능은 하지만 EKS/VPC 환경에서는 권장하지 않습니다.

  • 프라이빗 서브넷이면 NAT/라우팅에 의존
  • 보안 정책에서 외부 DNS가 차단될 수 있음
  • VPC 내부 도메인(Private Hosted Zone, Resolver Rule)과의 일관성이 깨질 수 있음

대부분은 VPC Resolver(169.254.169.253) 를 upstream으로 두는 게 안정적입니다.

Q2. CoreDNS가 죽으면 왜 전체가 느려지나요?

대부분의 서비스는 외부 API 호출, DB 접속, 내부 서비스 디스커버리에 DNS를 씁니다. DNS가 타임아웃 나면 애플리케이션은 재시도/백오프를 하며 스레드/커넥션 풀이 고갈되고, 결국 503/지연이 연쇄적으로 발생합니다.

Q3. 이미지 풀도 같이 실패하는데요?

DNS가 깨지면 ECR/레지스트리 도메인 해석이 실패해 ImagePullBackOff로 이어질 수 있습니다. 다만 이 경우는 DNS 외에도 IAM/ECR 토큰/프록시 이슈가 섞일 수 있어 분리해서 봐야 합니다.

결론

EKS에서 CoreDNS CrashLoopBackOffupstream 타임아웃이 함께 보이면, 핵심은 “CoreDNS가 포워딩하는 상위 DNS까지 패킷이 왕복 가능한가”입니다. 가장 빠른 분기점은 다음 두 가지입니다.

  1. CoreDNS의 /etc/resolv.conf가 무엇을 가리키는지 확인하고, 필요하면 forward . 169.254.169.253처럼 upstream을 명시한다.
  2. VPC/서브넷 라우팅, NAT, NACL/SG, NetworkPolicy로 UDP/TCP 53 및 응답 트래픽이 막히지 않았는지 확인한다.

이 순서대로만 점검해도 “원인을 못 찾고 CoreDNS만 재시작하는” 악순환을 크게 줄일 수 있습니다.