DNS 해석 디버깅

DNS 해석 디버깅 (Debugging DNS Resolution)

이 페이지는 DNS 문제를 진단하는 힌트를 제공해요.

출처: 문서

본문

시작하기 전에 (Before you begin)

쿠버네티스 클러스터가 필요하고, kubectl 명령줄 도구가 클러스터와 통신하도록 구성돼 있어야 해요. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 두 개 이상 있는 클러스터에서 실행하는 것을 권장해요. 아직 클러스터가 없다면 minikube로 만들거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있어요.

  • iximiuz Labs
  • Killercoda
  • KodeKloud

클러스터가 CoreDNS 애드온을 사용하도록 구성돼 있어야 해요. 쿠버네티스 서버가 최소 v1.6 버전이어야 해요. 버전을 확인하려면 kubectl version을 입력하세요.

테스트 환경으로 사용할 간단한 Pod 만들기 (Create a simple Pod to use as a test environment)

apiVersion: v1
kind: Pod
metadata:
  name: dnsutils
  namespace: default
spec:
  containers:
  - name: dnsutils
    image: registry.k8s.io/e2e-test-images/agnhost:2.39
    imagePullPolicy: IfNotPresent
  restartPolicy: Always

admin/dns/dnsutils.yaml — 참고: 이 예시는 default 네임스페이스에 파드를 만들어요. 서비스의 DNS 이름 해석은 파드의 네임스페이스에 따라 달라져요. 자세한 내용은 Service와 Pod용 DNS를 참고하세요.

그 매니페스트를 사용해 Pod를 생성해요.

kubectl apply -f https://k8s.io/examples/admin/dns/dnsutils.yaml
pod/dnsutils created

...그리고 그 상태를 확인해요.

kubectl get pods dnsutils
NAME       READY     STATUS    RESTARTS   AGE
dnsutils   1/1       Running   0          <some-time>

그 Pod가 실행 중이면 그 환경에서 nslookup을 실행할 수 있어요. 다음과 같은 결과가 보이면 DNS가 올바르게 동작하고 있는 거예요.

kubectl exec -i -t dnsutils -- nslookup kubernetes.default
Server:    10.0.0.10
Address 1: 10.0.0.10

Name:      kubernetes.default
Address 1: 10.0.0.1

nslookup 명령이 실패하면 다음을 확인해 보세요.

먼저 로컬 DNS 구성 확인하기 (Check the local DNS configuration first)

resolv.conf 파일의 내용을 살펴보세요. (자세한 내용은 DNS 서비스 사용자 정의와 아래 알려진 문제 참고)

kubectl exec -ti dnsutils -- cat /etc/resolv.conf

검색 경로와 네임서버가 다음과 같이 설정돼 있는지 확인하세요 (검색 경로는 클라우드 제공업체에 따라 다를 수 있다는 점 주의):

search default.svc.cluster.local svc.cluster.local cluster.local google.internal c.gce_project_id.internal
nameserver 10.0.0.10
options ndots:5

다음과 같은 오류는 CoreDNS 애드온이나 관련 Services에 문제가 있음을 나타내요.

kubectl exec -i -t dnsutils -- nslookup kubernetes.default
Server:    10.0.0.10
Address 1: 10.0.0.10

nslookup: can't resolve 'kubernetes.default'

또는

kubectl exec -i -t dnsutils -- nslookup kubernetes.default
Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

nslookup: can't resolve 'kubernetes.default'

DNS 파드가 실행 중인지 확인하기 (Check if the DNS pod is running)

kubectl get pods 명령으로 DNS 파드가 실행 중인지 확인해요.

kubectl get pods --namespace=kube-system -l k8s-app=kube-dns
NAME                       READY     STATUS    RESTARTS   AGE
...
coredns-7b96bf9f76-5hsxb   1/1       Running   0           1h
coredns-7b96bf9f76-mvmmt   1/1       Running   0           1h
...

참고: CoreDNS의 k8s-app 라벨 값은 원래 kube-dns와의 하위 호환성을 위해 kube-dns예요.

CoreDNS 파드가 실행 중이지 않거나 파드가 실패/완료되었다면, DNS 애드온이 현재 환경에 기본으로 배포되지 않았을 수 있고 수동으로 배포해야 할 거예요.

DNS 파드에서 오류 확인하기 (Check for errors in the DNS pod)

kubectl logs 명령으로 DNS 컨테이너의 로그를 확인해요. CoreDNS의 경우:

kubectl logs --namespace=kube-system -l k8s-app=kube-dns

건강한 CoreDNS 로그의 예시는 다음과 같아요.

.:53
2018/08/15 14:37:17 [INFO] CoreDNS-1.2.2
2018/08/15 14:37:17 [INFO] linux/amd64, go1.10.3, 2e322f6
CoreDNS-1.2.2
linux/amd64, go1.10.3, 2e322f6
2018/08/15 14:37:17 [INFO] plugin/reload: Running configuration MD5 = 24e6c59e83ce706f07bcc82c31b1ea1c

로그에 의심스럽거나 예상치 못한 메시지가 있는지 확인해 보세요.

DNS 서비스가 떠 있나요? (Is DNS service up?)

kubectl get service 명령으로 DNS 서비스가 떠 있는지 확인해요.

kubectl get svc --namespace=kube-system
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)             AGE
...
kube-dns     ClusterIP   10.0.0.10      <none>        53/UDP,53/TCP        1h
...

참고: CoreDNS의 Service는 원래 kube-dns와의 하위 호환성을 위해 kube-dns라는 이름을 가져요.

Service를 만들었는데도 나타나지 않거나 기본 생성돼야 하지만 나타나지 않는다면, Service 디버깅을 참고하세요.

DNS 엔드포인트가 노출돼 있나요? (Are DNS endpoints exposed?)

kubectl get endpointslice 명령으로 DNS 엔드포인트가 노출됐는지 확인할 수 있어요.

kubectl get endpointslice -l kubernetes.io/service-name=kube-dns --namespace=kube-system
NAME             ADDRESSTYPE   PORTS   ENDPOINTS                  AGE
coredns-zxoja    IPv4          53      10.180.3.17,10.180.3.17    1h

엔드포인트가 보이지 않으면, Service 디버깅 문서의 엔드포인트 섹션을 참고하세요.

DNS 쿼리가 수신/처리되고 있나요? (Are DNS queries being received/processed?)

CoreDNS 구성(일명 Corefile)에 log 플러그인을 추가해 CoreDNS가 쿼리를 수신하고 있는지 확인할 수 있어요. CoreDNS Corefile은 coredns라는 ConfigMap에 있어요. 이를 편집하려면 다음 명령을 사용하세요.

kubectl -n kube-system edit configmap coredns

그런 다음 아래 예시처럼 Corefile 섹션에 log를 추가하세요.

apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        log
        errors
        health
        kubernetes cluster.local in-addr.arpa ip6.arpa {
          pods insecure
          upstream
          fallthrough in-addr.arpa ip6.arpa
        }
        prometheus :9153
        forward . /etc/resolv.conf
        cache 30
        loop
        reload
        loadbalance
    }

변경 사항을 저장한 후, 쿠버네티스가 이 변경 사항을 CoreDNS 파드에 전파하는 데 1~2분 걸릴 수 있어요. 다음으로 이 문서의 위 섹션들에 따라 몇 가지 쿼리를 만들고 로그를 확인해 보세요. CoreDNS 파드가 쿼리를 수신하고 있다면 로그에서 볼 수 있을 거예요.

로그의 쿼리 예시는 다음과 같아요.

.:53
2018/08/15 14:37:15 [INFO] CoreDNS-1.2.0
2018/08/15 14:37:15 [INFO] linux/amd64, go1.10.3, 2e322f6
CoreDNS-1.2.0
linux/amd64, go1.10.3, 2e322f6
2018/09/07 15:29:04 [INFO] plugin/reload: Running configuration MD5 = 162475cdf272d8aa601e6fe67a6ad42f
2018/09/07 15:29:04 [INFO] Reloading complete
172.17.0.18:41675 - [07/Sep/2018:15:29:11 +0000] 59925 "A IN kubernetes.default.svc.cluster.local. udp 54 false 512" NOERROR qr,aa,rd,ra 106 0.000066649s

CoreDNS가 충분한 권한을 가지고 있나요? (Does CoreDNS have sufficient permissions?)

CoreDNS는 서비스 이름을 올바르게 해석하려면 service와 endpointslice 관련 리소스를 나열할 수 있어야 해요.

샘플 오류 메시지:

2022-03-18T07:12:15.699431183Z [INFO] 10.96.144.227:52299 - 3686 "A IN serverproxy.contoso.net.cluster.local. udp 52 false 512" SERVFAIL qr,aa,rd 145 0.000091221s

먼저 system:coredns의 현재 ClusterRole을 가져와요.

kubectl describe clusterrole system:coredns -n kube-system

예상 출력:

PolicyRule:
  Resources                        Non-Resource URLs  Resource Names  Verbs
  ---------                        -----------------  --------------  -----
  endpoints                        []                 []              [list watch]
  namespaces                       []                 []              [list watch]
  pods                             []                 []              [list watch]
  services                         []                 []              [list watch]
  endpointslices.discovery.k8s.io  []                 []              [list watch]

권한이 빠져 있다면 ClusterRole을 편집해 추가해요.

kubectl edit clusterrole system:coredns -n kube-system

EndpointSlices 권한 삽입 예시:

...
- apiGroups:
  - discovery.k8s.io
  resources:
  - endpointslices
  verbs:
  - list
  - watch
...

서비스에 대해 올바른 네임스페이스에 있나요? (Are you in the right namespace for the service?)

네임스페이스를 지정하지 않는 DNS 쿼리는 파드의 네임스페이스로 제한돼요. 파드와 서비스의 네임스페이스가 다르다면 DNS 쿼리에 서비스의 네임스페이스가 포함돼야 해요.

이 쿼리는 파드의 네임스페이스로 제한돼요.

kubectl exec -i -t dnsutils -- nslookup <service-name>

이 쿼리는 네임스페이스를 지정해요.

kubectl exec -i -t dnsutils -- nslookup <service-name>.<namespace>

이름 해석에 대해 더 자세히 알아보려면 Service와 Pod용 DNS를 참고하세요.

알려진 문제 (Known issues)

일부 Linux 배포판(예: Ubuntu)은 기본적으로 로컬 DNS 해석기(systemd-resolved)를 사용해요. Systemd-resolved는 /etc/resolv.conf를 스텁 파일로 이동하고 교체하는데, 이는 업스트림 서버에서 이름을 해석할 때 치명적인 전달 루프를 일으킬 수 있어요. 이는 kubelet의 --resolv-conf 플래그를 사용해 올바른 resolv.conf(systemd-resolved에서는 /run/systemd/resolve/resolv.conf)를 가리키도록 수정해 수동으로 고칠 수 있어요. kubeadm은 systemd-resolved를 자동으로 감지하고 그에 따라 kubelet 플래그를 조정해요.

쿠버네티스 설치물은 기본적으로 노드의 resolv.conf 파일이 클러스터 DNS를 사용하도록 구성하지 않아요. 그 과정이 본질적으로 배포판별로 다르기 때문이에요. 이는 언젠가 구현되어야 할 수도 있어요.

Linux의 libc(일명 glibc)는 DNS nameserver 레코드를 기본적으로 3개로 제한하고, 쿠버네티스는 nameserver 레코드 1개를 소비해야 해요. 이는 로컬 설치가 이미 3개의 nameserver를 사용한다면 그 항목 중 일부가 손실된다는 뜻이에요. 이 제한을 우회하려면 노드에서 더 많은 nameserver 항목을 제공하는 dnsmasq를 실행할 수 있어요. kubelet의 --resolv-conf 플래그를 사용할 수도 있어요.

기본 이미지로 Alpine 버전 3.17 이하를 사용한다면 Alpine의 설계 문제로 DNS가 제대로 동작하지 않을 수 있어요. musl 버전 1.24까지는 DNS 스텁 해석기에 TCP 폴백이 포함되지 않아 512바이트를 넘는 DNS 호출은 모두 실패했어요. 이미지를 Alpine 버전 3.18 이상으로 업그레이드하세요.

다음 단계 (What's next)

  • 클러스터에서 DNS 서비스 자동 확장 보기
  • Service와 Pod용 DNS 읽기

더 알아보기 (Learn more)