K8s CoreDNS란?
CoreDNS는 Kubernetes 클러스터의 기본 DNS 서버로, 서비스 디스커버리의 핵심입니다. Pod가 my-service.default.svc.cluster.local로 요청을 보내면 CoreDNS가 해당 Service의 ClusterIP를 반환합니다. 대부분의 개발자가 CoreDNS를 “그냥 동작하는 것”으로 여기지만, 커스텀 설정을 통해 성능 최적화, 외부 DNS 연동, 트러블슈팅까지 세밀하게 제어할 수 있습니다.
이 글에서는 CoreDNS의 Corefile 플러그인 체인, Stub Domain과 Upstream DNS 설정, NodeLocal DNSCache, 그리고 DNS 장애 디버깅 패턴까지 심층적으로 다룹니다.
Corefile 플러그인 체인 이해
CoreDNS의 모든 동작은 Corefile에 정의된 플러그인 체인으로 결정됩니다. Kubernetes에서는 kube-system 네임스페이스의 coredns ConfigMap으로 관리합니다:
kubectl -n kube-system get configmap coredns -o yaml
기본 Corefile을 분석해봅시다:
.:53 {
errors # 에러를 표준 출력에 로깅
health { # /health 헬스체크 엔드포인트
lameduck 5s # 종료 전 5초간 unhealthy 응답
}
ready # /ready 레디니스 엔드포인트
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure # Pod A/AAAA 레코드 활성화
fallthrough in-addr.arpa ip6.arpa
ttl 30 # DNS 캐시 TTL (초)
}
prometheus :9153 # Prometheus 메트릭 노출
forward . /etc/resolv.conf # 클러스터 외부 쿼리 → 노드 DNS로 포워딩
cache 30 # 응답 캐시 30초
loop # 무한 루프 감지
reload # Corefile 변경 시 자동 리로드
loadbalance # A 레코드 라운드로빈 순서 섞기
}
플러그인 실행 순서는 Corefile에 나열된 순서가 아니라, CoreDNS 바이너리에 컴파일된 고정 순서를 따릅니다. 예를 들어 cache는 항상 forward보다 먼저 체크됩니다.
Stub Domain: 사내 DNS 연동
온프레미스 환경에서 사내 도메인(*.corp.internal)을 별도의 DNS 서버로 라우팅해야 할 때 Stub Domain을 설정합니다:
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
# 사내 도메인 → 전용 DNS 서버로 포워딩
corp.internal:53 {
errors
cache 30
forward . 10.0.0.53 10.0.0.54 {
max_concurrent 1000
policy round_robin
}
}
# AWS 내부 DNS → VPC DNS로 포워딩
aws.internal:53 {
errors
cache 60
forward . 169.254.169.253
}
# 기본 클러스터 DNS
.:53 {
errors
health { lameduck 5s }
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
prefer_udp
}
cache 30
loop
reload
loadbalance
}
이렇게 하면 db.corp.internal은 사내 DNS(10.0.0.53)로, google.com은 노드의 upstream DNS로, my-svc.default.svc.cluster.local은 kubernetes 플러그인이 처리합니다.
Rewrite 플러그인: DNS 쿼리 변환
rewrite 플러그인으로 DNS 쿼리를 변환할 수 있습니다. 짧은 도메인을 FQDN으로 매핑하거나, 레거시 도메인을 리다이렉트할 때 유용합니다:
.:53 {
# 짧은 별칭 → 실제 서비스로 매핑
rewrite name api.myapp.com api-gateway.production.svc.cluster.local
rewrite name redis.local redis-master.cache.svc.cluster.local
# 레거시 도메인 → 새 도메인으로 리다이렉트
rewrite name old-api.example.com new-api.default.svc.cluster.local
# 서브도메인 패턴 매칭 (regex)
rewrite name regex (.*).legacy.com {1}.default.svc.cluster.local
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
forward . /etc/resolv.conf
cache 30
reload
}
NodeLocal DNSCache: 성능 최적화
대규모 클러스터에서 모든 DNS 쿼리가 CoreDNS Pod로 집중되면 병목과 타임아웃이 발생합니다. NodeLocal DNSCache는 각 노드에 DNS 캐시 데몬을 배치하여 이 문제를 해결합니다:
# NodeLocal DNSCache DaemonSet 배포
kubectl apply -f https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dns/nodelocaldns/nodelocaldns.yaml
동작 방식:
- 각 노드에 DaemonSet으로
node-local-dnsPod가 배포됩니다. - Pod의
/etc/resolv.conf는 노드 로컬 IP(169.254.20.10)를 가리킵니다. - 캐시 히트 시 CoreDNS까지 가지 않고 즉시 응답합니다.
- 캐시 미스 시 CoreDNS로 포워딩합니다.
| 구성 | DNS 레이턴시 | CoreDNS 부하 |
|---|---|---|
| 기본 (CoreDNS만) | 1~5ms | 높음 (모든 쿼리 집중) |
| NodeLocal DNSCache | 0.1~0.5ms (캐시 히트) | 낮음 (미스만 포워딩) |
| NodeLocal + 긴 TTL | <0.1ms | 매우 낮음 |
ndots 최적화: 불필요한 쿼리 제거
Kubernetes Pod의 기본 /etc/resolv.conf는 ndots:5로 설정됩니다. 이는 도메인에 점(.)이 5개 미만이면 search 도메인을 먼저 시도한다는 뜻입니다:
# Pod 내부 /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
# google.com 조회 시 실제 발생하는 쿼리 (ndots:5)
# 1. google.com.default.svc.cluster.local → NXDOMAIN
# 2. google.com.svc.cluster.local → NXDOMAIN
# 3. google.com.cluster.local → NXDOMAIN
# 4. google.com → 성공!
# → 외부 도메인 하나에 DNS 쿼리 4번!
외부 API를 많이 호출하는 서비스에서는 ndots를 줄이거나 FQDN에 마침표를 붙여 최적화합니다:
# 방법 1: Pod spec에서 ndots 조정
apiVersion: v1
kind: Pod
spec:
dnsConfig:
options:
- name: ndots
value: "2" # 점 2개 미만만 search 도메인 시도
# 방법 2: 코드에서 FQDN 사용 (끝에 점)
# "google.com." → search 도메인 무시, 바로 조회
# "api.stripe.com." → 1번의 쿼리로 해결
DNS 장애 디버깅
DNS 문제는 “연결이 안 된다”로 나타나지만 원인은 다양합니다. 체계적인 디버깅 방법:
# 1) CoreDNS Pod 상태 확인
kubectl -n kube-system get pods -l k8s-app=kube-dns
kubectl -n kube-system logs -l k8s-app=kube-dns --tail=50
# 2) 디버그 Pod에서 DNS 쿼리 테스트
kubectl run dns-debug --image=nicolaka/netshoot -it --rm -- bash
# 클러스터 내부 서비스 조회
nslookup kubernetes.default.svc.cluster.local
dig +short my-service.default.svc.cluster.local
# 외부 도메인 조회
nslookup google.com
dig +trace google.com
# DNS 응답 시간 측정
dig @10.96.0.10 my-service.default.svc.cluster.local | grep "Query time"
# 3) CoreDNS 로그 레벨 높이기 (Corefile에 log 플러그인 추가)
# .:53 {
# log # 모든 DNS 쿼리 로깅
# errors
# ...
# }
# 4) CoreDNS 메트릭으로 병목 확인
kubectl -n kube-system port-forward svc/kube-dns 9153:9153
curl http://localhost:9153/metrics | grep coredns_dns_requests_total
curl http://localhost:9153/metrics | grep coredns_dns_responses_total
흔한 DNS 장애 패턴과 해결
| 증상 | 원인 | 해결 |
|---|---|---|
| 간헐적 DNS 타임아웃 | CoreDNS Pod 부하 과다 | NodeLocal DNSCache 도입, 레플리카 증가 |
| 외부 도메인만 느림 | ndots:5로 불필요한 쿼리 | ndots:2 또는 FQDN 끝에 점 추가 |
| 특정 도메인 NXDOMAIN | upstream DNS 장애 | forward에 복수 upstream 지정, health_check 활성화 |
| Pod 시작 시 DNS 실패 | CoreDNS 미준비 상태 | 앱에 DNS 재시도 로직 추가 |
| conntrack 테이블 가득 참 | UDP DNS 패킷 conntrack 누수 | NodeLocal DNSCache (TCP 업그레이드) |
CoreDNS 오토스케일링
클러스터가 커지면 CoreDNS 레플리카도 늘려야 합니다. dns-autoscaler를 사용하면 노드/코어 수에 비례하여 자동 스케일링됩니다:
# dns-autoscaler ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: dns-autoscaler
namespace: kube-system
data:
linear: |
{
"coresPerReplica": 256,
"nodesPerReplica": 16,
"min": 2,
"max": 10,
"preventSinglePointFailure": true
}
# 동작 방식:
# replicas = max(ceil(cores / 256), ceil(nodes / 16))
# 예: 100코어, 20노드 → max(1, 2) = 2 레플리카
# 예: 500코어, 50노드 → max(2, 4) = 4 레플리카
마무리
CoreDNS는 Kubernetes 네트워킹의 보이지 않는 기반입니다. 기본 설정으로도 잘 동작하지만, Stub Domain으로 사내 DNS를 연동하고, NodeLocal DNSCache로 성능을 최적화하고, ndots를 튜닝하여 불필요한 쿼리를 제거하면 DNS 관련 장애를 대부분 예방할 수 있습니다.
관련 글로 K8s NetworkPolicy 네트워크 격리와 K8s Ingress 라우팅 심화도 함께 참고하세요.