CKA — 쿠버네티스 관리자 · Kustomize, 리소스 사용량, CoreDNS · 이론
CoreDNS — 이름은 어떻게 IP 가 되는가
한 줄 요약
파드가 이름을 물으면 kubelet 이 써 준 /etc/resolv.conf 의 nameserver, 곧 kube-dns Service 의 ClusterIP 로 질의가 갑니다. 그 뒤에 CoreDNS 파드가 있고, CoreDNS 가 무엇을 어떻게 답할지는 coredns ConfigMap 의 Corefile 이 정합니다. 해석이 막히면 이 사슬을 앞에서부터 순서대로 확인합니다.
왜 이게 필요했나
Service 의 ClusterIP 는 지웠다 다시 만들면 바뀝니다. 파드 IP 는 더 자주 바뀝니다. 애플리케이션이 IP 를 설정에 박아 두면 재배포마다 깨지므로, 쿠버네티스는 이름을 안정된 주소로 삼고 이름에서 IP 로 가는 길을 클러스터 안에 두었습니다. 그 길이 클러스터 DNS 이고, 현재 구현이 CoreDNS 입니다.
문제는 이 길이 여러 조각으로 되어 있다는 점입니다. 파드의 resolv.conf, kube-dns Service, EndpointSlice, CoreDNS 파드, Corefile, 업스트림 DNS — 어느 한 조각만 어긋나도 "이름을 못 찾는다" 는 같은 증상이 납니다. 증상이 같으니 원인을 찍어 맞히려 하면 시간을 다 씁니다. 공식 문서 [DNS 해석 디버깅](https://kubernetes.io/docs/tasks/administer-cluster/dns-debugging-resolution/) 이 순서를 정해 둔 이유입니다.
어떻게 동작하나
파드 쪽 — resolv.conf 와 검색 순서
[서비스와 파드를 위한 DNS](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/) 문서에 따르면 kubelet 은 파드마다 resolv.conf 를 써 줍니다. 모양은 이렇습니다.
nameserver 10.32.0.10search <namespace>.svc.cluster.local svc.cluster.local cluster.localoptions ndots:5search 줄이 이름 해석 순서를 정합니다. test 네임스페이스의 파드가 data 를 물으면 먼저 data.test.svc.cluster.local 로 확장해 봅니다. 그래서 같은 네임스페이스의 Service 는 짧은 이름으로 찾아지고, 다른 네임스페이스(prod)의 Service 는 data.prod 처럼 네임스페이스를 붙여야 합니다. 문서의 표현대로 "네임스페이스를 지정하지 않은 DNS 질의는 파드의 네임스페이스로 한정" 됩니다. ndots:5 는 이름에 점이 다섯 개 미만이면 검색 도메인을 먼저 붙여 보라는 뜻이라, 바깥 도메인 하나를 찾기 위해 클러스터 도메인 세 개를 먼저 두드리는 비용이 듭니다.
어느 nameserver 를 쓸지는 파드 스펙의 dnsPolicy 가 정합니다. 문서가 "Default 는 기본값이 아니다" 라고 따로 적어 둘 만큼 헷갈리는 부분입니다.
| dnsPolicy | 동작 |
| --- | --- |
| ClusterFirst | 지정하지 않았을 때의 기본값. 클러스터 도메인이 아닌 질의는 업스트림으로 전달 |
| ClusterFirstWithHostNet | hostNetwork: true 파드가 클러스터 DNS 를 쓰려면 이것을 명시해야 함 |
| Default | 노드의 이름 해석 설정을 그대로 물려받음 |
| None | 쿠버네티스 설정을 무시하고 dnsConfig 로 전부 직접 지정 |
서버 쪽 — kube-dns Service 와 Corefile
DNS 서버는 kube-system 네임스페이스의 CoreDNS 파드이고, 앞에 kube-dns 라는 이름의 ClusterIP Service 가 있습니다(53/UDP, 53/TCP). 이름이 coredns 가 아니라 kube-dns 인 이유는 원래의 kube-dns 와의 하위 호환 때문이고, 파드 라벨 k8s-app=kube-dns 도 같은 이유로 남아 있습니다. 디버깅할 때 이 라벨로 파드를 찾습니다.
CoreDNS 는 플러그인을 조합하는 서버이고, 설정 파일이 Corefile 입니다. [DNS 서비스 사용자 정의](https://kubernetes.io/docs/tasks/administer-cluster/dns-custom-nameservers/) 문서가 보여 주는 기본 Corefile 은 이렇습니다.
.: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 cache 30 loop reload loadbalance}한 줄씩 뜻을 알아 두면 로그와 증상이 연결됩니다. errors 는 오류를 stdout 에 기록하고, health 는 8080 포트로 상태를 알리며 lameduck 5s 는 종료 전 5초 동안 비정상으로 표시해 트래픽을 빼냅니다. ready 는 8181 포트에서 모든 플러그인이 준비됐을 때 200 을 돌려줍니다. kubernetes 플러그인이 Service 와 파드 IP 를 근거로 클러스터 도메인의 질의에 답하고, ttl 30 은 응답 TTL 입니다(문서 기준 기본 5초, 최대 3600초). forward . /etc/resolv.conf 는 클러스터 도메인 밖의 질의를 CoreDNS 파드의 resolv.conf 에 적힌 업스트림으로 넘깁니다. cache 30 은 응답 캐시, loop 는 전달 루프를 감지하면 프로세스를 멈추는 안전장치, reload 는 Corefile 이 바뀌면 자동으로 다시 읽는 플러그인이고, loadbalance 는 A·AAAA·MX 레코드 순서를 섞는 라운드로빈입니다. ConfigMap 을 고친 뒤 반영까지 문서는 2분 정도를 잡으라고 합니다.
특정 도메인만 다른 서버로 보내는 stub domain 도 같은 파일에 둡니다. 문서의 예처럼 consul.local:53 서버 블록 안에 forward . 10.150.0.1 을 두면 .consul.local 로 끝나는 이름만 그 서버로 갑니다.
디버깅 절차 — 앞에서부터
공식 절차를 순서대로 옮기면 이렇습니다.
1. dnsutils 파드(문서의 registry.k8s.io/e2e-test-images/agnhost 이미지)를 띄우고 nslookup kubernetes.default 를 해 봅니다. 답이 오면 DNS 는 정상입니다.
2. 실패하면 그 파드의 /etc/resolv.conf 를 먼저 봅니다. search 경로와 nameserver 가 위 모양인지 확인합니다.
3. kube-system 에서 -l k8s-app=kube-dns 로 CoreDNS 파드가 Running 인지 봅니다. 없으면 애드온이 배포되지 않은 것입니다.
4. 같은 라벨로 로그를 봅니다. 정상 로그에는 plugin/reload: Running configuration 이 찍힙니다.
5. kube-dns Service 가 있는지, 그리고 kubernetes.io/service-name=kube-dns 라벨의 EndpointSlice 에 주소가 있는지 봅니다. 엔드포인트가 비어 있으면 Service 는 있어도 뒤에 파드가 없는 것입니다.
6. 질의가 CoreDNS 까지 오는지 보려면 Corefile 에 log 플러그인을 넣고 로그에 질의 줄이 찍히는지 봅니다.
7. 로그에 SERVFAIL 이 있으면 권한을 의심합니다. CoreDNS 는 Service 와 EndpointSlice 를 list·watch 할 수 있어야 하고, system:coredns ClusterRole 에 discovery.k8s.io 의 endpointslices 가 빠져 있으면 이 오류가 납니다.
8. 마지막으로 네임스페이스를 확인합니다. 다른 네임스페이스의 Service 는 <service>.<namespace> 로 물어야 합니다.
문서의 "알려진 문제" 도 기억해 둘 만합니다. Ubuntu 처럼 systemd-resolved 를 쓰는 배포판은 /etc/resolv.conf 를 스텁 파일로 바꿔 두어 전달 루프가 생길 수 있고, 이때는 kubelet 의 --resolv-conf 로 /run/systemd/resolve/resolv.conf 를 가리켜야 합니다(kubeadm 은 이것을 자동으로 감지합니다). 또 glibc 는 nameserver 를 3개까지만 읽는데 쿠버네티스가 그중 하나를 씁니다.
현장에서 만나는 모습
로그에 SERVFAIL 이 가득한데 파드는 멀쩡하다. 문서가 든 예가 바로 이것입니다 — 파드도 Running 이고 Service 도 있는데 응답이 SERVFAIL 이면, CoreDNS 가 EndpointSlice 를 읽을 권한이 없는 경우가 많습니다. kubectl describe clusterrole system:coredns 로 endpointslices.discovery.k8s.io 에 list·watch 가 있는지 보고, 없으면 ClusterRole 을 고칩니다.
hostNetwork 파드만 Service 이름을 못 찾는다. 노드 네트워크를 쓰는 파드는 dnsPolicy 를 지정하지 않으면 ClusterFirst 가 Default 처럼 동작해 노드의 resolv.conf 를 씁니다. 문서가 ClusterFirstWithHostNet 을 명시하라고 하는 이유이며, 로그 수집기나 노드 에이전트를 배포할 때 자주 빠뜨립니다.
다음 퀴즈에서 확인할 것
퀴즈에서는 base 와 overlay 의 관계, 두 가지 패치 방식, kubectl top 이 실패하는 이유와 metrics-server 가 읽는 엔드포인트, dnsPolicy 의 기본값, 그리고 SERVFAIL 로그가 가리키는 원인을 묻습니다. 명령을 외우기보다 "어느 조각이 빠졌는가" 를 답할 수 있으면 됩니다.