CoreDNS autopath + NodeLocal DNSCache, 같이 써야 진짜 빨라진다

쿠버네티스 클러스터가 어느 정도 커지면 DNS가 가장 먼저 비명을 지른다. 우리 팀도 노드 80대 규모 EKS에서 CoreDNS QPS가 2만을 넘기면서 P99 레이턴시가 200ms 가까이 튀는 걸 보고 나서야 손을 댔다. NodeLocal DNSCache는 들어봤는데, autopath는 의외로 안 쓰는 팀이 많더라. 이 둘을 같이 써야 진짜 효과가 난다.
이 글은 두 컴포넌트를 같이 도입하는 가이드다. 각각의 역할, 설정 순서, 그리고 같이 쓸 때 주의할 점까지 정리했다.
ndots:5가 만드는 N+1 쿼리 문제
쿠버네티스 파드에 들어가서 cat /etc/resolv.conf를 찍어보면 이런 게 나온다.
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10
options ndots:5
ndots:5는 도메인 이름의 점이 5개 미만이면 search 도메인을 다 붙여서 시도해보라는 뜻이다. 그래서 api.example.com을 부르면 다음 순서로 쿼리가 나간다.
api.example.com.default.svc.cluster.localapi.example.com.svc.cluster.localapi.example.com.cluster.localapi.example.com
외부 도메인 한 번 부르려고 NXDOMAIN 응답을 세 번 받는다. 클라이언트 입장에선 RTT 곱하기 4. 클러스터 입장에선 쓸데없는 트래픽 4배. 작은 클러스터에선 그냥 무시할 수 있는데, 노드가 늘고 마이크로서비스가 외부 API를 자주 호출하기 시작하면 이게 진짜 무섭다.
NodeLocal DNSCache: 일단 트래픽부터 줄인다
NodeLocal DNSCache는 모든 노드에 DaemonSet으로 경량 CoreDNS 인스턴스를 띄운다. 파드의 DNS 쿼리는 kube-proxy iptables 룰에 의해 노드 로컬(169.254.20.10)로 가로채지고, 캐시 히트면 거기서 바로 응답한다. 미스만 진짜 CoreDNS로 간다.
효과는 즉각적이다. 우리 팀 측정값으로:
- CoreDNS 들어가는 QPS: 22k → 3.5k (84% 감소)
- 파드 측 P99 lookup latency: 180ms → 12ms
- 노드 간 DNS UDP 트래픽: 사실상 0
설치는 공식 매니페스트로 한 방이다. kubectl apply -f 하기 전에 __PILLAR__DNS__SERVER__, __PILLAR__LOCAL__DNS__, __PILLAR__DNS__DOMAIN__ 세 개 변수만 sed로 바꿔준다.
kubedns=$(kubectl get svc kube-dns -n kube-system -o jsonpath='{.spec.clusterIP}')
domain="cluster.local"
localdns="169.254.20.10"
curl -L https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dns/nodelocaldns/nodelocaldns.yaml \
| sed "s/__PILLAR__LOCAL__DNS__/$localdns/g; s/__PILLAR__DNS__DOMAIN__/$domain/g; s/__PILLAR__DNS__SERVER__/$kubedns/g" \
| kubectl apply -f -
근데 이걸로 끝이 아니다. NodeLocal DNSCache는 캐시일 뿐이라서, 캐시에 없는 쿼리는 여전히 search 도메인 4개를 다 시도한다. 외부 도메인일수록 캐시 히트율이 낮아서 효과가 떨어진다.
autopath: 쿼리 자체를 줄인다
autopath 플러그인은 접근 방식이 다르다. 파드의 namespace를 보고, 첫 번째 쿼리 응답에 CNAME으로 "이 도메인은 외부야, search 도메인 더 붙이지 마"라는 힌트를 박아준다. 클라이언트는 그걸 받고 두 번째, 세 번째 쿼리를 안 날린다.
CoreDNS Corefile에 이렇게 추가한다.
.:53 {
errors
health
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods verified
fallthrough in-addr.arpa ip6.arpa
}
autopath @kubernetes
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
중요한 포인트 몇 개:
pods verified가 필수다. autopath는 파드 IP로 namespace를 알아내야 하는데, pods insecure나 디폴트 pods disabled로는 동작 안 한다. verified로 바꿔야 kubernetes 플러그인이 파드 정보를 watch하고 캐시한다.
autopath는 kubernetes 플러그인 뒤에 와도 되지만, 같은 zone에 있어야 한다. Corefile 작성 순서는 위 예시처럼 두면 된다.
메모리가 좀 더 든다. 파드 IP → namespace 매핑을 다 들고 있어야 해서. 파드 1만 개 기준 100MB 정도 추가됐다. CoreDNS 리밋이 170Mi였는데 256Mi로 올렸다.
둘이 같이 쓸 때 진짜 주의할 점
여기서부터가 본론이다. NodeLocal DNSCache + autopath를 같이 쓰면 한 가지 함정이 있다.
NodeLocal DNSCache 자체도 안에서 작은 CoreDNS가 도는데, 이 캐시가 autopath의 응답을 캐시해버린다는 거다. autopath는 파드의 namespace에 따라 다른 응답을 만들어야 하는데, A 파드 응답이 캐시되면 B 파드(다른 namespace)가 같은 답을 받게 된다. 그러면 B 파드 입장에선 CNAME 힌트가 잘못된 거고, search 도메인 검색이 깨진다.
해결책은 NodeLocal DNSCache의 cluster.local zone에서 caching을 끄는 것이다. NodeLocalDNS의 ConfigMap에서:
cluster.local:53 {
errors
cache {
success 9984 30
denial 9984 5
}
reload
loop
bind 169.254.20.10 10.96.0.10
forward . __PILLAR__CLUSTER__DNS__ {
force_tcp
}
prometheus :9253
health 169.254.20.10:8080
}
여기서 cache 블록을 빼거나, 최소한 disable success cluster.local을 추가한다. 우리 팀은 그냥 cluster.local zone에서는 캐시 자체를 빼버렸다. 어차피 파드 IP 변동이 잦아서 캐시 효과보다 stale 위험이 더 컸다.
cluster.local:53 {
errors
reload
loop
bind 169.254.20.10 10.96.0.10
forward . __PILLAR__CLUSTER__DNS__ {
force_tcp
}
prometheus :9253
health 169.254.20.10:8080
}
외부 도메인 zone(.:53)에서는 캐시 그대로 두면 된다. autopath 응답은 어차피 cluster.local zone에서만 나온다.
검증: 진짜로 줄었나?
Prometheus 메트릭으로 검증한다.
# CoreDNS가 받는 총 쿼리 (NodeLocal 도입 전후 비교)
sum(rate(coredns_dns_requests_total[5m]))
# autopath가 처리한 쿼리 비율
sum(rate(coredns_autopath_success_total[5m]))
/ sum(rate(coredns_dns_requests_total{server="dns://:53"}[5m]))
# NXDOMAIN 응답 비율 - autopath 잘 동작하면 떨어진다
sum(rate(coredns_dns_responses_total{rcode="NXDOMAIN"}[5m]))
/ sum(rate(coredns_dns_responses_total[5m]))
도입 전후 비교 (실측):
| 메트릭 | Before | After |
|---|---|---|
| CoreDNS QPS | 22,000 | 1,800 |
| 파드 P99 lookup | 180ms | 4ms |
| NXDOMAIN 비율 | 73% | 11% |
| CoreDNS 메모리 | 170Mi | 240Mi |
NXDOMAIN 비율이 73%에서 11%로 떨어진 게 핵심이다. 이게 줄었다는 건 search 도메인 무한 시도가 사라졌다는 뜻이고, 클라이언트가 체감하는 속도 차이가 크다.
한 가지 안 풀린 문제
autopath는 Windows 노드 파드에선 동작하지 않는다. Windows 컨테이너는 ndots 동작이 리눅스랑 달라서 그렇다. 우리는 Windows 노드가 없어서 신경 안 썼는데, 멀티 OS 클러스터 굴리는 분들은 NodeSelector로 Windows 파드만 별도 처리하거나, 그냥 autopath 안 쓰는 게 안전하다.
또 하나 — CoreDNS 1.12.2(2025년 6월 릴리즈)에서 autopath 슬라이스 할당 최적화가 들어갔다. 1.11 버전 이하 쓰는 분들은 가능하면 1.12.2 이상으로 올리는 게 좋다. 메모리 사용량이 눈에 띄게 줄었다.
정리
NodeLocal DNSCache 단독으로도 효과는 크다. autopath만 단독으로 써도 좋다. 근데 둘 중 하나만 쓰면 절반의 효과만 본다는 게 이번 도입에서 배운 점이다. 캐시 충돌 함정만 피하면 같이 쓰는 게 정답이다.
다음에는 같은 맥락에서 ExternalDNS와 Route53 동기화 지연으로 삽질한 얘기도 풀어볼까 한다.