Naver Engineering Day 2024 - Kubernetes에서 DNS 다루는 방법
https://d2.naver.com/helloworld/2905424
https://youtu.be/1UBgSARBdBc?si=07YbUP3GK5zpnGjT
문제
k8s 클러스터 운영 도중, 일부 노드에서 특정 도메인 nslookup이 안 되는 현상 발생.
- 원인: 해당 노드들의 망 구성이 달라서, 다른 nameserver 사용중이었음. nameserver는 변경할 수 없는 상황.
- 특정 도메인일 경우에만 nameserver를 다르게 설정하려면 어떻게 해야 하나??
간단한 k8s 배경지식
- pod는 고유한 ip를 갖는다.
- pod는 동적으로 생성되며, 언제든 삭제될 수 있다. 재생성될 때, ip는 동적으로 변경된다.
- Service를 사용해서, 동적으로 바뀌는 ip 집합인 여러 pod에 접근할 수 있는 단일 endpoint를 구성할 수 있다.
수많은 pod과 service를 직접 관리하기는 불편하기 때문에, domain name을 사용해서 pod나 service에 접근할 수 있게 되어 있음.
- k8s 클러스터 내에 DNS 서버를 두고, pod과 service에 DNS record를 자동으로 생성, 관리.
- 예시 - A record: ..svc.
Domain Resolving
pod 내부에서 도메인 조회하는 nslookup 명령어를 입력해보면
- resolv.conf 에 정의된 nameserver ip주소를 토대로 DNS resolver 서버와 통신한다.
pod가 떠 있는 노드에 접근해서 nslookup 명령어를 입력해보면
- resolv.conf에 저장된 nameserver ip주소가 pod과 다른 것을 확인할 수 있다.
pod에서 ip address로 검색해보면, 노드에서 DNS server로 사용한 Ip주소와 동일한 ip가 nodelocaldns 인터페이스에서 확인된다.
- nodelocaldns: Nodelocal DNSCache 라는, k8s Daemonset으로부터 생성된 임시 인터페이스.
NodeLocalDNS는
- 모든 노드에 Daemonset 형태로 실행되고
- 모든 노드에서 로컬로 DNS query 실행해주기 위해 k8s Service도 배포되어 있고
- 필요한 설정을 담고 있는 configmap도 배포되어 있다.
각 노드의 host network namespace에서 동작하고, nodelocaldns 라는 임시 인터페이스를 생성하고 있음.
정확히 뭘 실행하고 있는 건가? -> CoreDNS를 캐시 모드로 실행하고 있다.
- coreDNS: DNS서버 역할을 할 수 있는 애플리케이션.
- Corefile을 지정하면, 다양한 플러그인을 설정할 수 있다.
nodelocaldns의 configmap에 마운트되어 있는 Corefile을 확인해보면
- cluster.local:53 = cluster.local 존의 53포트 (DNS) 로 들어오는 DNS query 처리하기 위한 설정.
- cache: 성공한 응답 9984패킷은 30초 캐싱, 실패한 응답은 5초 캐싱.
- bind: 169.254.20.10 주소로부터 온 DNS 쿼리를 처리한다. (pod 내부에서 조회할 수 있었던 nameserver 주소)
- forward: 모든 DNS 쿼리를 10.96.0.10 TCP 통신으로 전송.
cluster.local 외의 DNS query는 노드에 정의된 /etc/resolv.conf 설정을 따라간다.
즉, kubernetes 내부 DNS query는 10.96.0.10이라는 ip로 TCP 포워딩하고
kubernetes 외부의 DNS query는 노드에 정의된 /etc/resolv.conf 설정을 따라간다.
pod에서 외부 DNS로 query 요청 시
Pod에서 외부 도메인으로 query 요청 시
- Pod에 있는 nodelocaldns에 query 전달
- cache 있을 경우 pod 내에서 처리
- cache 없을 경우, pod가 배포된 node에 있는 Nameserver로 DNS query가 전달된다.
pod에서 내부 DNS로 query 요청 시
pod에서 내부 도메인으로 query 요청 시 (예컨대 UDP)
- Pod에 있는 nodelocaldns에 query 전달
- cache 있을 경우 응답
- cache 없을 경우, 요청을 TCP로 전환해서 10.96.0.10 (k8s service) 으로 포워딩.
- 목적지 pod에서 응답을 받으면, pod내부의 nodelocaldns가 UDP로 전환해서 pod에 전달해준다.
의문점
- 10.96.0.10은 뭐하는 녀석인가
- 왜 TCP로 변환해주나?
10.96.0.10: kube-system에 배포된 kube-dns라는 k8s Service의 ip주소.
- 이 Service는 어떤 pod들을 관리하고 있는지 selector로 확인해보면
{"k8s-app":"kube-dns"}
를 확인할 수 있고 - 이 label값을 가진 pod는 coreDNS deployment에서 관리중이다.
CoreDNS Deployment
k8s 클러스터 내부의 기본 DNS 서버. 클러스터 애드온으로, k8s 설치될 때 자동으로 배포 / 시작된다.
- service나 pod 정보가 바뀌면, k8s apiserver 상태 변경을 확인해서 해당 리소스의 DNS record를 업데이트한다.
- 클러스터 내부 / 외부 통신을 위한 DNS query를 처리하는 역할.
k8s 1.13 이전 버전까지는 kubeDNS라는 걸 썼었기 때문에, 하위호환을 위해 Service 이름은 kube-dns로 유지되고 있음.
- DNS 서버의 추상화 의미도 있음. 구현체 무관하게 kube-dns로.
- nodelocaldns와 마찬가지로 configmap으로 설정파일 관리중.
CoreDNS의 Corefile
nodelocaldns와 달리, 모든 DNS query를 처리할 수 있게 cluster.local 설정이 없다.
- kubernetes: k8s apiserver와 통신해서 리소스 변경을 실시간으로 감지하고, DNS record를 변경한다.
- hosts: 특정 호스트명과 ip를 static하게 매핑할 수도 있음. 노드의 /etc/hosts 파일에 값을 입력해주면 된다.
- 노드에 있는 /etc/resolv.conf 설정파일을 참고해서 외부 DNS서버로 query 포워딩한다.
따라서 pod에서 외부 DNS로 query를 보낼 경우
- pod가 떠 있는 노드의 nodelocaldns에는 로그가 남지만, coreDNS에는 로그가 남지 않는다.
- 즉 클러스터 외부로 나가는 query는 coreDNS가 아니라, 해당 pod가 배포된 노드의 nameserver가 처리한다.
pod에서 내부 DNS로 query를 보낼 경우
- pod의 nodelocaldns 캐시가 있을 경우 바로 응답.
- nodelocaldns에 캐시가 없을 경우 coreDNS가 처리한다.
의문점
Q. 어차피 결국 coreDNS에서 도메인 관련 내용 전부 처리하는데.. nodelocal의 DNSCache는 왜 필요한가?
- 내부 DNS 정보도 응답해주고, 외부 DNS의 경우 노드의 nameserver로 넘겨주는 로직이 다 있으니
A. 그래서 Nodelocal DNSCache 컴포넌트는 필수 구성요소가 아니다. 그럼에도 쓸만한 이유는..
성능
- 클러스터 규모가 커지면, 매번 네트워크 통신으로 ip / domain 정보 확인할수록 성능이 떨어진다.
- 네트워크 거쳐가는 것보다, 노드 내에서 캐시된 값을 받는 게 DNS 응답속도가 훨씬 빠르다.
안정성
- coreDNS에 장애가 발생해도, 캐시된 DNS정보를 활용하면 서비스 중단 가능성을 줄일 수 있다.
- DNS query로 들어오는 트래픽을 분산하는 역할
- 일단 각 노드에 캐시된 걸로 처리... 트래픽 분산 / 과부하 방지.
- 리눅스에서 SNAT 사용했을 때의 문제 중 하나인 conntrack race condition 완화.
- TCP 프로토콜 활용 -> 패킷손실 발생 시 재시도 가능.
DNS query 감당하기 위한 CoreDNS pod 개수는 어떻게 설정하는 게 좋을까?
cluster-proportional-autoscaler를 활용한다.
- 클러스터 노드 / core 개수 기반으로 coreDNS pod개수 조정
- autoscale을 위한 metric 확인 / pod개수 조정을 위한 RBAC 권한을 부여받는다. (serviceAccount)
- 다양한 파라미터 조건을 조합해서 autoscale이 동작하도록 만들 수 있다.
Pod가 DNS에 안정적으로 query할 수 있도록 하는 정책들
네 가지 Policy가 있음.
- default: pod가 배포된 노드 설정을 따라간다.
- clusterFirst: 클러스터의 DNS서버를 활용한다. 따로 설정해주지 않을 경우 이게 기본 policy로 설정됨.
일반 Pod는 ClusterFirst 쓰면 되고, coreDNS와 nodelocaldns cache pod의 경우 default를 써야 한다.
- 얘네는 DNS 작업을 처리하는 애들이므로, 배포된 노드의 DNS nameserver 세팅을 따라가야 하기 때문.
문제 해결
외부 DNS query는 nodelocal DNSCache에서 담당하므로, 노드의 Corefile을 수정한다.
- 예컨대 abcxyz.com 이 문제라면, 이 도메인의 DNS query가 들어올 경우 관련 nameserver를 명시하는 식으로.
만약 nodelocal DNSCache를 안 쓰고 있다면?
- coreDNS가 참조하는 Corefile을 변경해주면 된다.