
쿠버네티스 클러스터가 어느 정도 커지면 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 동기화 지연으로 삽질한 얘기도 풀어볼까 한다.
'IT > Kubernets' 카테고리의 다른 글
| PDB 하나 때문에 노드 드레인이 4시간 멈췄던 이야기 (0) | 2026.05.05 |
|---|---|
| Helm lookup 함수, ArgoCD랑 같이 쓰면 함정 있다 (0) | 2026.05.04 |
| etcd MVCC와 compaction, defrag가 K8s API에 미치는 진짜 영향 (0) | 2026.05.02 |
| kubectl debug --target, 이거 모르는 분 꽤 많더라 (0) | 2026.05.02 |
| ValidatingAdmissionPolicy vs Kyverno, 정책 일부를 옮기고 나서 (0) | 2026.05.01 |