쿠버네티스 클러스터에서 NodeLocal DNSCache 사용하기
쿠버네티스 클러스터에서 NodeLocal DNSCache 사용하기 (Using NodeLocal DNSCache in Kubernetes Clusters)
기능 상태: Kubernetes v1.18부터 Stable.
이 페이지는 쿠버네티스의 NodeLocal DNSCache 기능에 대한 개요를 제공해요.
출처: 문서
본문
시작하기 전에 (Before you begin)
쿠버네티스 클러스터가 필요하고, kubectl 명령줄 도구가 클러스터와 통신하도록 구성돼 있어야 해요. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 두 개 이상 있는 클러스터에서 실행하는 것을 권장해요. 아직 클러스터가 없다면 minikube로 만들거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있어요.
- iximiuz Labs
- Killercoda
- KodeKloud
버전을 확인하려면 kubectl version을 입력하세요.
소개 (Introduction)
NodeLocal DNSCache는 클러스터 노드에서 DNS 캐싱 에이전트를 DaemonSet으로 실행해 클러스터 DNS 성능을 개선해요. 현재 아키텍처에서 'ClusterFirst' DNS 모드의 Pod는 DNS 쿼리를 위해 보통 CoreDNS clusterIP에 접근해요. 이것은 kube-proxy에 의해 CoreDNS 엔드포인트로 변환돼요. 이 새 아키텍처에서는 Pod가 같은 노드에서 실행되는 DNS 캐싱 에이전트에 접근하므로 DNAT 규칙과 연결 추적(connection tracking)을 피할 수 있어요. 로컬 캐싱 에이전트는 클러스터 호스트네임의 캐시 미스("cluster.local" 접미사가 기본)에 대해 CoreDNS 서비스를 조회해요.
동기 (Motivation)
- 현재 DNS 아키텍처에서는 가장 높은 DNS QPS를 가진 Pod가 로컬 CoreDNS 인스턴스가 없다면 다른 노드에 접근해야 할 수 있어요. 로컬 캐시가 있으면 그러한 시나리오에서 대기 시간을 개선하는 데 도움이 돼요.
- iptables DNAT와 연결 추적을 건너뛰면 conntrack 경합을 줄이고 UDP DNS 항목이 conntrack 테이블을 채우는 것을 피할 수 있어요.
- 로컬 캐싱 에이전트에서 CoreDNS 서비스로의 연결은 TCP로 업그레이드할 수 있어요. TCP conntrack 항목은 연결이 닫히면 제거되는 반면, UDP 항목은 타임아웃해야 하므로(기본
nf_conntrack_udp_timeout은 30초) 차이가 있어요. - DNS 쿼리를 UDP에서 TCP로 업그레이드하면 보통 최대 30초(재시도 3회 + 10초 타임아웃)의 드롭된 UDP 패킷과 DNS 타임아웃으로 인한 꼬리 대기 시간(tail latency)을 줄일 수 있어요. nodelocal cache가 UDP DNS 쿼리를 수신하므로 애플리케이션을 변경할 필요가 없어요.
- 노드 레벨에서 DNS 요청에 대한 메트릭과 가시성.
- 네거티브 캐싱(negative caching)을 다시 활성화할 수 있어 CoreDNS 서비스에 대한 쿼리 수를 줄여요.
아키텍처 다이어그램 (Architecture Diagram)
NodeLocal DNSCache가 활성화된 후 DNS 쿼리가 따르는 경로는 다음과 같아요.
Nodelocal DNSCache 흐름
이 이미지는 NodeLocal DNSCache가 DNS 쿼리를 어떻게 처리하는지 보여줘요.
구성 (Configuration)
참고: NodeLocal DNSCache의 로컬 리슨 IP 주소는 클러스터의 기존 IP와 충돌하지 않도록 보장할 수 있는 어떤 주소든 될 수 있어요. 로컬 범위의 주소를 사용하는 것이 권장돼요. 예를 들어 IPv4의 'link-local' 범위 '169.254.0.0/16' 또는 IPv6의 'Unique Local Address' 범위 'fd00::/8'에서요.
이 기능은 다음 단계로 활성화할 수 있어요.
샘플 nodelocaldns.yaml과 비슷한 매니페스트를 준비해 nodelocaldns.yaml로 저장해요.
IPv6를 사용한다면 CoreDNS 구성 파일은 'IP:Port' 형식으로 사용할 때 모든 IPv6 주소를 대괄호 안에 넣어야 해요. 앞선 지점의 샘플 매니페스트를 사용한다면 구성 줄 L70을 이렇게 수정해야 해요: "health [__PILLAR__LOCAL__DNS__]:8080"
매니페스트의 변수를 올바른 값으로 치환해요.
# 역사적 이유로 CoreDNS의 Service는 "kube-dns"라고 불림
coredns=`kubectl get svc kube-dns -n kube-system -o jsonpath={.spec.clusterIP}`
domain=<cluster-domain>
localdns=<node-local-address>
<cluster-domain>은 기본적으로 "cluster.local"이에요. <node-local-address>는 NodeLocal DNSCache를 위해 선택한 로컬 리슨 IP 주소예요.
kube-proxy가 IPTABLES 모드에서 실행 중이라면:
sed -i "s/__PILLAR__LOCAL__DNS__/$localdns/g; s/__PILLAR__DNS__DOMAIN__/$domain/g; s/__PILLAR__DNS__SERVER__/$coredns/g" nodelocaldns.yaml
__PILLAR__CLUSTER__DNS__와 __PILLAR__UPSTREAM__SERVERS__는 node-local-dns 파드가 채울 거예요. 이 모드에서 node-local-dns 파드는 CoreDNS 서비스 IP와 <node-local-address> 모두에서 수신하므로, 파드는 어느 IP 주소로든 DNS 레코드를 조회할 수 있어요.
kube-proxy가 IPVS 모드에서 실행 중이라면:
sed -i "s/__PILLAR__LOCAL__DNS__/$localdns/g; s/__PILLAR__DNS__DOMAIN__/$domain/g; s/,__PILLAR__DNS__SERVER__//g; s/__PILLAR__CLUSTER__DNS__/$coredns/g" nodelocaldns.yaml
이 모드에서 node-local-dns 파드는 <node-local-address>에서만 수신해요. node-local-dns 인터페이스는 IPVS 로드밸런싱에 사용되는 인터페이스가 이미 이 주소를 사용하므로 CoreDNS 클러스터 IP를 바인딩할 수 없어요. __PILLAR__UPSTREAM__SERVERS__는 node-local-dns 파드가 채울 거예요.
kubectl create -f nodelocaldns.yaml을 실행해요.
kube-proxy를 IPVS 모드로 사용한다면, kubelet의 --cluster-dns 플래그를 NodeLocal DNSCache가 수신하는 <node-local-address>를 사용하도록 수정해야 해요. 그렇지 않으면 NodeLocal DNSCache가 CoreDNS 서비스 IP와 <node-local-address> 모두에서 수신하므로 --cluster-dns 플래그의 값을 수정할 필요가 없어요.
활성화하면 node-local-dns Pod가 클러스터의 각 노드의 kube-system 네임스페이스에서 실행돼요. 이 Pod는 CoreDNS를 캐시 모드로 실행하므로, 다른 플러그인이 노출하는 모든 CoreDNS 메트릭이 노드별로 사용 가능해요.
DaemonSet을 제거해 이 기능을 비활성화할 수 있어요: kubectl delete -f <manifest>. kubelet 구성에 한 어떤 변경도 원래대로 되돌려야 해요.
스텁 도메인 구성 (Stub domain configuration)
node-local-dns ConfigMap의 Corefile을 stubDomain 구성으로 수정할 수 있어요. 일부 클라우드 제공업체는 node-local-dns ConfigMap을 직접 수정하는 것을 허용하지 않을 수 있어요. 그런 경우 전통적으로 kube-dns가 사용하던 형식의 kube-dns ConfigMap을 대신 사용할 수 있어요.
apiVersion: v1