- Published on
AWS EKS CoreDNS CrashLoopBackOff와 DNS 타임아웃 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스든 마이크로서비스든, 쿠버네티스에서 DNS는 사실상 제1의 데이터플레인입니다. EKS에서 CoreDNS가 CrashLoopBackOff로 빠지면 애플리케이션은 외부 API 호출은 물론, 같은 네임스페이스의 서비스 디스커버리조차 실패하며 i/o timeout, no such host 같은 증상으로 연쇄 장애가 납니다. 특히 장애가 간헐적이면 원인이 더 복잡해 보이는데, 대부분은 CoreDNS 자체 문제(설정/버전/리소스) 또는 **노드/네트워크 경로 문제(iptables, conntrack, SG/NACL, ENI, MTU)**로 수렴합니다.
이 글은 “CoreDNS CrashLoopBackOff + DNS 타임아웃” 조합을 재현 가능한 절차로 진단하고, 가장 자주 먹히는 해결책을 우선순위대로 정리합니다.
> 타임아웃을 다루는 방식은 본질적으로 네트워크/리트라이/관측의 문제입니다. 애플리케이션 계층의 타임아웃 대응 관점이 필요하면 OpenAI Responses API 408 타임아웃 재현과 해결 실전 가이드도 함께 참고하면, “어디서 타임아웃이 나는지”를 쪼개서 보는 감각을 얻을 수 있습니다.
증상 패턴 정리
다음 중 2개 이상이면 CoreDNS/클러스터 DNS 경로를 우선 의심합니다.
kubectl -n kube-system get pods에서coredns-*가CrashLoopBackOff또는Error.- 파드에서
nslookup kubernetes.default가connection timed out; no servers could be reached. - 애플리케이션 로그에
dial tcp: lookup xxx on 10.100.0.10:53: i/o timeout. - 노드 교체/스케일링/업그레이드 직후 발생.
- 특정 노드에 스케줄된 파드에서만 DNS 실패(노드 로컬 문제 가능성 큼).
0) 현재 상태를 5분 안에 스냅샷 뜨기
장애 대응에서 가장 먼저 할 일은 “지금 무엇이 깨졌는지”를 명령어로 고정하는 것입니다.
# CoreDNS 상태
kubectl -n kube-system get deploy,rs,po -l k8s-app=kube-dns -o wide
# CoreDNS 이벤트(크래시 이유 단서)
kubectl -n kube-system describe pod -l k8s-app=kube-dns | sed -n '/Events:/,$p'
# CoreDNS 로그
kubectl -n kube-system logs -l k8s-app=kube-dns --tail=200
# CoreDNS ConfigMap
kubectl -n kube-system get cm coredns -o yaml
# kube-dns 서비스(ClusterIP가 파드의 nameserver로 들어감)
kubectl -n kube-system get svc kube-dns -o wide
이 스냅샷만으로도 원인이 절반은 좁혀집니다. 예를 들어 로그에 plugin/loop가 보이면 루프, no such host면 업스트림 리졸버, panic이면 버전/플러그인/설정 문제 가능성이 큽니다.
1) CrashLoopBackOff의 1순위: CoreDNS 설정(Corefile) 문제
EKS에서 CoreDNS는 보통 아래와 같은 Corefile을 사용합니다(버전에 따라 약간 다름).
.: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
}
1-1) loop 플러그인으로 인한 무한 루프
대표적인 크래시 원인 중 하나가 CoreDNS가 자신(또는 클러스터 DNS)으로 다시 질의를 포워딩하는 루프입니다.
forward . /etc/resolv.conf가 가리키는 업스트림이 kube-dns의 ClusterIP(예:10.100.0.10)로 잡혀있으면 루프가 생깁니다.- 노드의
/etc/resolv.conf가 어떤 이유로 클러스터 DNS를 바라보게 되면(잘못된 DHCP/이미지/부트스트랩), CoreDNS가 그 파일을 읽는 순간 루프가 됩니다.
확인:
# CoreDNS 파드 안에서 resolv.conf 확인
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
# kube-dns ClusterIP 확인
kubectl -n kube-system get svc kube-dns -o jsonpath='{.spec.clusterIP}'; echo
해결(가장 흔한 처방): 업스트림을 명시적으로 VPC DNS(보통 169.254.169.253)나 사내 리졸버로 고정합니다.
kubectl -n kube-system edit cm coredns
예시(Corefile 일부):
forward . 169.254.169.253 {
max_concurrent 1000
}
변경 후 롤아웃:
kubectl -n kube-system rollout restart deploy coredns
kubectl -n kube-system rollout status deploy coredns
> 참고: loop 플러그인을 제거하는 방식도 있지만, 루프 탐지를 포기하는 것이므로 근본 원인(업스트림/리졸브 체인)을 먼저 바로잡는 것을 권합니다.
1-2) ndots/검색 도메인 폭증으로 인한 지연(타임아웃처럼 보임)
CrashLoop이 아니라도 DNS 타임아웃의 흔한 원인은 과도한 검색 도메인/ndots 설정입니다. 예: ndots:5 + 긴 search list면, 외부 도메인 하나 조회할 때 수차례 실패 질의가 발생해 지연이 누적됩니다.
파드의 /etc/resolv.conf 확인:
kubectl run -it --rm dns-debug --image=busybox:1.36 --restart=Never -- sh
cat /etc/resolv.conf
완화 방법:
- 애플리케이션이 외부 FQDN을 자주 조회한다면, 워크로드에
dnsConfig.options.ndots: "1"적용 고려 - CoreDNS
cacheTTL 조정(너무 짧으면 QPS 폭증)
예시(Deployment 일부):
spec:
template:
spec:
dnsConfig:
options:
- name: ndots
value: "1"
2) CrashLoopBackOff의 2순위: 리소스 부족(OOMKilled)과 QPS 폭증
CoreDNS는 작은 리소스로도 돌아가지만, 트래픽이 몰리면 메모리/CPU가 급격히 튀고 OOMKilled → 재시작 → 캐시 소실 → QPS 증가의 악순환이 생깁니다.
확인:
kubectl -n kube-system get pod -l k8s-app=kube-dns -o jsonpath='{range .items[*]}{.metadata.name} {.status.containerStatuses[0].lastState.terminated.reason}{"\n"}{end}'
kubectl -n kube-system top pod -l k8s-app=kube-dns
해결 체크리스트:
- CoreDNS replica 수 증가(노드 수/워크로드 QPS에 비례)
- requests/limits 상향(특히 memory)
- 불필요한 DNS 질의 감소(애플리케이션 캐시, ndots 조정)
예시(리소스 상향 + 레플리카 확장):
kubectl -n kube-system scale deploy coredns --replicas=4
kubectl -n kube-system patch deploy coredns --type='json' -p='[
{"op":"replace","path":"/spec/template/spec/containers/0/resources","value":{
"requests":{"cpu":"200m","memory":"256Mi"},
"limits":{"cpu":"500m","memory":"512Mi"}
}}
]'
운영 팁:
- HPA를 CoreDNS에 적용하는 경우도 있지만, DNS는 스파이크에 매우 민감하므로 최소 레플리카를 충분히 두는 것이 중요합니다.
- CoreDNS 메트릭(
:9153)을 Prometheus로 수집해coredns_dns_requests_total,coredns_cache_hits_total,coredns_forward_request_duration_seconds를 봐야 원인-결과가 연결됩니다.
3) EKS에서 특히 자주 터지는 네트워크 원인 3가지
CoreDNS가 크래시하지 않는데도 타임아웃이 나면, “CoreDNS → 업스트림(예: VPC DNS) → 응답” 경로가 막힌 경우가 많습니다.
3-1) 보안 그룹/네트워크 ACL에서 53/UDP(및 53/TCP) 누락
- 노드 보안 그룹 또는 NACL에서 UDP 53이 막히면 업스트림 포워딩이 타임아웃.
- DNS 응답이 큰 경우 TCP로 폴백하므로 TCP 53도 열어야 합니다.
검증(파드에서 직접 업스트림으로 dig):
kubectl run -it --rm dns-tools --image=ghcr.io/infobloxopen/dnstools:latest --restart=Never -- sh
# VPC DNS로 직접 질의(대개 169.254.169.253)
dig @169.254.169.253 amazon.com +time=2 +tries=1
3-2) 노드 conntrack 테이블 포화
DNS는 UDP 기반 단기 연결이 많아 conntrack이 부족하면 드롭/지연이 발생합니다. 증상은 “간헐 타임아웃”으로 나타나기 쉬워서 까다롭습니다.
노드에서 확인(SSM/SSH 필요):
# 현재 conntrack 사용량
sudo conntrack -C
# 최대치
cat /proc/sys/net/netfilter/nf_conntrack_max
# 드롭 카운터(커널 메시지)
dmesg | egrep -i 'conntrack|nf_conntrack|table full' | tail -n 50
해결:
nf_conntrack_max상향- 노드 타입/커널 튜닝
- DNS QPS를 줄이거나 NodeLocal DNSCache 도입(아래 5절)
3-3) MTU/캡슐화 문제로 인한 단편화 드롭
CNI 설정/경로 MTU가 어긋나면 큰 DNS 응답(특히 TXT 레코드, 다수 A 레코드)이 단편화되며 드롭될 수 있습니다. 이 경우도 “일부 도메인만 타임아웃”으로 보입니다.
검증 아이디어:
dig +bufsize=4096vs 기본값 비교- 노드/파드 네트워크 MTU 확인(CNI, ENI, 터널 여부)
4) 버전/애드온 불일치: EKS 애드온 CoreDNS 업데이트로 해결되는 케이스
EKS 업그레이드 후 CoreDNS가 크래시하거나 이상 동작하는 경우, 클러스터 버전과 CoreDNS 애드온 버전 호환성을 확인해야 합니다.
확인:
# EKS Add-on으로 관리 중인지 확인(eksctl 또는 aws cli)
aws eks describe-addon --cluster-name <CLUSTER> --addon-name coredns
# CoreDNS 이미지 태그 확인
kubectl -n kube-system get deploy coredns -o jsonpath='{.spec.template.spec.containers[0].image}'; echo
해결:
- EKS Add-on을 사용한다면, AWS 권장 버전으로 업데이트
- 수동 매니페스트로 운영 중이면, 릴리즈 노트 보고 버전 업 + Corefile 변경사항 반영
5) 재발 방지에 가장 효과적인 처방: NodeLocal DNSCache
트래픽이 큰 클러스터에서 DNS 안정성을 올리는 정석은 NodeLocal DNSCache입니다.
핵심 효과:
- 노드마다 로컬 캐시(일반적으로
169.254.20.10)가 떠서 CoreDNS 부하 감소 - conntrack 압력 감소(노드 내부로 단축)
- 네트워크 순간 장애에도 캐시로 버팀
적용 개요:
- NodeLocal DNSCache DaemonSet 배포
- kubelet
--cluster-dns또는 EKS 권장 방식으로 파드의 nameserver가 로컬 IP를 보게 구성
EKS는 공식 매니페스트를 기반으로 적용하는 경우가 많습니다. 적용 후 검증은 간단합니다.
# 파드 resolv.conf의 nameserver가 169.254.20.10(예시)로 바뀌었는지 확인
kubectl run -it --rm dns-debug --image=busybox:1.36 --restart=Never -- sh
cat /etc/resolv.conf
# 연속 질의로 지연/타임아웃 관찰
for i in $(seq 1 20); do nslookup kubernetes.default.svc.cluster.local >/dev/null || echo FAIL $i; done
6) 장애 상황에서 바로 쓰는 “DNS 타임아웃” 트러블슈팅 플레이북
아래 순서대로 하면 헤매는 시간을 줄일 수 있습니다.
6-1) 클러스터 DNS 자체가 살아있는지
kubectl -n kube-system get endpoints kube-dns -o wide
kubectl -n kube-system get pod -l k8s-app=kube-dns -o wide
- endpoints가 비었으면 CoreDNS 파드가 준비되지 않았거나 라벨/셀렉터 문제
6-2) 파드 → kube-dns ClusterIP:53 경로 확인
kubectl run -it --rm netshoot --image=nicolaka/netshoot --restart=Never -- sh
# kube-dns ClusterIP로 직접 질의
KUBEDNS=$(getent hosts kube-dns.kube-system.svc.cluster.local | awk '{print $1}' | head -n1)
nslookup kubernetes.default "$KUBEDNS"
6-3) CoreDNS → 업스트림 경로 확인
CoreDNS가 forward . /etc/resolv.conf라면, CoreDNS 파드 내부에서 업스트림으로 dig를 날려봅니다.
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" -- sh -lc 'cat /etc/resolv.conf; echo; nslookup amazon.com 169.254.169.253'
여기서 실패하면 SG/NACL/라우팅/conntrack/MTU 쪽으로 이동합니다.
6-4) 특정 노드에서만 실패하는지(노드 단위 격리)
# 문제가 나는 파드가 올라간 노드 확인
kubectl get pod -A -o wide | egrep -i '(<문제파드이름>|coredns)'
# CoreDNS 파드를 다른 노드로 재스케줄(노드 문제 판별)
kubectl -n kube-system delete pod -l k8s-app=kube-dns
노드가 바뀌면 해결된다면, 해당 노드의 conntrack/iptables/CNI/커널 상태를 집중 점검합니다.
7) (보너스) 애플리케이션 관점의 완충: 타임아웃/재시도/캐시
DNS가 불안정할 때 애플리케이션이 즉시 죽어버리면 장애가 증폭됩니다. DNS는 인프라 문제지만, 호출자도 방어적으로 설계해야 합니다.
- HTTP 클라이언트에 적절한 connect/read timeout
- 지수 백오프 재시도(무한 재시도 금지)
- circuit breaker로 DNS 장애 구간의 연쇄 폭발 방지
이런 “타임아웃을 재현하고, 어디서 끊기는지 분해해서, 재시도/폴백을 설계”하는 접근은 OpenAI Responses API 500·503 대응 재시도 폴백 서킷브레이커에서도 같은 결로 다루고 있으니, 운영 관점에서 같이 읽어두면 좋습니다.
결론: 가장 흔한 원인과 가장 빠른 해결 조합
현장에서 가장 자주 맞는 조합은 아래 3가지입니다.
- Corefile의 forward 업스트림이 잘못되어 loop 발생 →
forward . 169.254.169.253로 고정 + 롤아웃 - CoreDNS 리소스 부족(OOMKilled) + QPS 폭증 → replicas/메모리 상향 + ndots/캐시 점검
- 노드 네트워크(conntrack/SG/NACL/MTU) 문제로 업스트림 타임아웃 → 업스트림 dig로 경로 분리 후 노드 튜닝 또는 NodeLocal DNSCache 도입
장애가 진짜로 끝났는지 확인하려면, “CoreDNS 파드가 안정적으로 Running”만 보지 말고 다음을 함께 체크하세요.
- 파드에서
nslookup kubernetes.default20~50회 반복 시 실패 0회 - 외부 도메인(예:
amazon.com) 조회 지연이 안정적으로 낮음 - CoreDNS 메트릭에서 forward 지연/에러율이 정상 범위
이 과정을 한 번 플레이북으로 만들어두면, 다음번 CoreDNS 이슈는 10분 내 원인 분리까지 가능해집니다.