Kubernetes 클러스터의 컨트롤 플레인에는 CoreDNS라는 리소스가 배포되어있다. 그리고 많은 사람들이 이 CoreDNS에 대해 궁금해 한다.
그래서 이번 아티클에서는 CoreDNS가 무엇인지, 어떤 역할을 하는지, CoreDNS에 문제가 있을 때 무슨 일이 생기고 어떻게 디버깅할 수 있는지 알아보겠다.
1. CoreDNS의 역할
우리가 Kubernetes 클러스터에 배포하는 Pod의 IP를 본 적 있는가? kubectl get pod -o wide 명령어로 쉽게 확인할 수 있는 Pod IP는 클러스터에 배포된 Pod끼리 통신할 때 사용하는 가장 기본적인 주소이다.
하지만 설정 변경이나 재배포로 언제든 금방 재생성되는 Pod의 특성상 그 IP 주소도 언제든 바뀔 수 있다. Pod의 주소가 매번 바뀌어버리면 Pod간의 통신할 때 여간 어려운 일이 아닐 것이다. Pod와 통신할 때마다 해당 Pod의 IP를 확인할 수는 없지 않은가?
그런 문제를 해결하기 위해 등장한 것이 Service 리소스이다. Pod의 라벨로 연동되는 Service는 변동적인 Pod IP 대신 사용할 수 있는 고정 IP이다. Service의 IP로 접근하면 해당 Service와 연동되는 Pod들의 IP로 트래픽이 전달되는 것이다.
Service 리소스 덕분에 고정된 IP는 얻었지만, 여기서 끝나지 않는다. Pod로 통신을 해야 할 때 연동된 Service의 IP를 일일이 직접 입력해야 한다는 어려움이 여전히 남아있는 것이다.
그래서 클러스터 리소스의 IP 대신 리소스의 이름을 도메인처럼 사용해 통신하는 시스템을 완성했고, 그 역할을 수행하는 것이 바로 CoreDNS이다.
CoreDNS는 Kubernetes 리소스의 이름과 IP의 매핑을 실시간으로 추적하고 알려주는 컴포넌트다.
2. CoreDNS는 어떻게 동작하는가
그렇다면 CoreDNS는 어떻게 리소스 이름(도메인)만으로 Pod 간 통신을 가능하게 하는 걸까? Pod에서 도메인으로 접속을 시도할 때 어떤 경로를 거쳐 해당 리소스의 IP를 얻게 되는지 차근차근 알아보겠다.
Pod로 생성되는 모든 컨테이너의 /etc/resolv.conf 파일(도메인 주소 관련 설정)에는 CoreDNS를 위한 설정들이 명시되어있다. 필자가 로컬 클러스터에서 생성한 테스트용 Pod의 /etc/resolv.conf 파일은 아래와 같이 명시되어 있었다.
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
nameserver: 이 컨테이너에서 DNS 쿼리(도메인 주소로 매핑되는 IP 주소 요청)를 보낼 네임 서버의 IP 주소를 의미한다. 테스트용 Pod 내/etc/resolv.conf파일의nameserver의 값은10.96.0.10이며, 이는 클러스터에 배포된 CoreDNS의 Service 리소스의 IP 주소이다.search: 도메인의 전체 주소를 자동으로 완성시켜주는 접미사들을 모아둔 것이다. 클러스터의 전체 도메인 이름(FQDN)은{리소스 이름}.{Namespace 이름}.{리소스 타입}.cluster.local.과 같은 형태인데, Pod에서my-service라는 이름으로만 Service에 접근을 시도해도search에 미리 정의된default.svc.cluster.local접미사가 자동으로 붙어 해당 Service로 접근이 가능하다.options ndots: FQDN이 아닌 도메인에 점(.)이 몇 개 미만일 때search에 명시된 접미사들을 순차적으로 붙여가면서 해당하는 도메인 주소를 찾아가는지 지정한다. 기본값은 5개로, 우리가my-service또는my-service.test-namespace등의 도메인으로 접속을 시도하면 점이 5개 미만이므로search에 설정된 접미사들을 순차적으로 붙여가며 매핑되는 IP 주소가 있는지 쿼리를 보내는 것이다.
필자의 로컬 클러스터의 kube-system Namespace에 배포된 CoreDNS의 Service IP를 확인해보면, 위 nameserver에 지정된 IP 주소와 동일함을 확인할 수 있다.

정리하자면, Pod에서 리소스 이름으로 접근을 시도할 경우 해당 리소스의 IP를 찾기 위해 /etc/resolv.conf 파일에 설정된 대로 CoreDNS의 Service IP로 DNS 쿼리를 보내는 것이다.
‘1. CoreDNS가 필요한 이유’에서 잠깐 설명한 것처럼 CoreDNS의 Service로 보내진 요청은 실제 CoreDNS Pod의 IP 주소로 보내져야 한다. 이때 각 노드에 배포된 kube-proxy가 나서서 Service IP로 들어온 요청의 목적지를 해당 Pod IP로 교체한다.
DNS 쿼리를 전달받은 CoreDNS Pod는 해당 요청에 답변하기 위해 IP 주소 탐색 작업을 시작한다.
CoreDNS는 다양한 플러그인으로 구성된 서버이다. 각 플러그인 별로 DNS 관련 역할이 다르며, Corefile이라는 설정 파일에 명시된 순서와 설정으로 작업 흐름이 구성된다.
CoreDNS의 대표적인 플러그인 3가지의 역할은 아래와 같다.
kubernetes:- k8s API Server와 연결하여 클러스터 내 리소스 이름과 IP 주소를 실시간으로 받아와 메모리에 저장
- 클러스터 내부에 DNS 쿼리와 일치하는 리소스 이름이 있는지 확인
forward: 클러스터 내부에 DNS 쿼리와 일치하는 리소스 이름이 없을 경우 외부 DNS 서버에 쿼리 전달 후 답변 반환cache: 이전에 동일한 DNS 쿼리가 들어올 경우 기억해둔 답변을 즉시 반환
CoreDNS의 Corefile은 kube-system Namespace에 배포된 coredns ConfigMap에서 확인 가능하다. 출력 예시는 아래와 같다. CoreDNS의 대표 플러그인 3인방을 찾아보자.
$ kubectl -n kube-system describe cm coredns
Name: coredns
Namespace: kube-system
Labels: <none>
Annotations: <none>
Data
====
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 {
max_concurrent 1000
}
cache 30 {
disable success cluster.local
disable denial cluster.local
}
loop
reload
loadbalance
}
3. 성능 향상과 디버깅을 위한 안내서
DNS 쿼리 속도 향상
Kubernetes 클러스터를 운영하다가 DNS 쿼리가 느려지는 경우가 있다. 가장 흔한 이유 중 하나는 불필요한 탐색이다.
/etc/resolv.conf의 설정 중 ndots:5 옵션으로 인해 FQDN이 아닌 도메인 주소의 점(.)이 5개 미만이면 search에 명시된 접미사들을 순차적으로 붙여 CoreDNS로 쿼리를 보낸다.
이를 바꿔서 이야기하면, FQDN으로 보내는 DNS 쿼리는 한 번만으로 성공/실패 결과가 나오므로 불필요한 쿼리를 줄일 수 있다는 뜻이다. 클러스터 내 도메인 쿼리 속도 향상을 원한다면 아래와 같은 양식의 FQDN을 사용해보자. (맨 끝에 점을 붙이는 것을 잊지 말자.)
{리소스 이름}.{Namespace 이름}.{리소스 타입}.cluster.local.
외부 도메인(예: google.com)도 마찬가지이다. 클러스터 내에서 외부 도메인에 요청을 보낼 때 끝에 점(예: google.com.)을 붙이면 FQDN으로 인식되어 불필요한 쿼리를 건너뛰고 곧장 외부 도메인 주소로 접속할 수 있다.
CoreDNS 디버깅 포인트
클러스터 내에 Service나 Pod 수가 대규모로 운영 중일 때 갑자기 클러스터 내부의 DNS 조회가 실패하는 경우가 있다. 예를 들면 애플리케이션 로그에 Temporary failure in name resolution 오류가 발생하는 식이다.
이럴 땐 CoreDNS Pod의 상태 및 로그를 확인하고 각 케이스에 따라 대응하면 되겠다.
OOMKilled기록이 발견될 때: CoreDNS에서는 클러스터 내 모든 리소스 정보를 메모리에 캐싱하는데, 이 리소스 수가 많아질수록 메모리 사용량도 함께 증가해 CoreDNS의 Limit으로 설정된 메모리를 초과할 수 있다.- CoreDNS의 메모리 Limit을 높여준다. 러프하게 클러스터에서 운영 중인 Service 1,000개당 50~100MB 수준으로 추가하는 것이 좋다.
- CPU 사용량이 100%를 찍을 때: CoreDNS Pod는 클러스터에 기본적으로 2개 정도 배포되기 때문에 클러스터에서 운영하는 Pod나 Service 수가 수백, 수천 개 이상 되면 거기서 발생하는 DNS 쿼리 부하를 견디지 못할 수도 있다.
- CoreDNS의 Replica 수를 직접 늘리거나, 클러스터 노드 또는 CPU 코어 수에 비례해 자동으로 늘려주는 Autoscaler를 설정한다.
마무리
이번 아티클에서 우리는 CoreDNS가 Kubernetes 클러스터의 각 Pod에게 고정된 주소를 부여하고 관리해주는 아주 중요한 역할을 수행하는 것을 알 수 있었다.
또한 CoreDNS의 쿼리 속도 향상 팁과 디버깅 포인트도 함께 알아봤는데, 이는 Kubernetes 클러스터를 운영할 때 요긴하게 활용될 수 있으므로 꼭 숙지할 수 있길 바란다.