ストイックに生きたい
Nodelocal DNSCache とはなにか
Using NodeLocal DNSCache in Kubernetes Clusters に書いてある
このエントリーをはてなブックマークに追加

概要

公式ドキュメント で紹介されているとおり、DNS のキャッシュ用エージェント(CoreDNS)を DaemonSet で各 Node に配置することで、DNS のパフォーマンスを改善しようというものである。やってること自体はシンプルで、キャッシュがあればそれを返すし、なければ kube-dns に問い合わせたり、Node 上の /etc/resolv.conf を参照して名前解決をしたりする。
ソースコードは、Kubernetes DNS にある。

仕組み

公式ドキュメントの図がわかりやすいので引用した。

デフォルト(clusterDNS: kube-dns)では、クライアント側の Pod の dnsPolicy が ClusterFirst の場合、kubelet に指定されている clusterDNS で名前解決を行う。
Nodelocal DNSCache では kube-dns と同じ IP アドレスのインターフェースを作成、bind することで、kube-dns ではなく Nodelocal DNSCache へパケットをルーティングされるようにしている。169.254.20.10 は NodeLocal DNSCache の IP アドレスで、172.20.0.10 は kube-dns の IP アドレスである。

17: nodelocaldns: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default
    link/ether c6:df:93:00:8f:3a brd ff:ff:ff:ff:ff:ff
    inet 169.254.20.10/32 scope global nodelocaldns
       valid_lft forever preferred_lft forever
    inet 172.20.0.10/32 scope global nodelocaldns
       valid_lft forever preferred_lft forever

そして、流れてきた DNS クエリは、Corefile をもとに処理されていて、プレースホルダーについては後述する。
この Corefile わかることは、それぞれ cache が設定されていて、__PILLAR__DNS__DOMAIN__:53(in-addr.arpa:53、ip6.arpa:53) は __PILLAR__CLUSTER__DNS__ に forward されること、それ以外の場合は __PILLAR__UPSTREAM__SERVERS__ に forward されることである。

    __PILLAR__DNS__DOMAIN__:53 {
        errors
        cache {
                success 9984 30
                denial 9984 5
        }
        reload
        loop
        bind __PILLAR__LOCAL__DNS__ __PILLAR__DNS__SERVER__
        forward . __PILLAR__CLUSTER__DNS__ {
                force_tcp
        }
        prometheus :9253
        health __PILLAR__LOCAL__DNS__:8080
        }
    .:53 {
        errors
        cache 30
        reload
        loop
        bind __PILLAR__LOCAL__DNS__ __PILLAR__DNS__SERVER__
        forward . __PILLAR__UPSTREAM__SERVERS__
        prometheus :9253
        }

このマニフェストでは、使用者側が置換するべきプレースホルダーがいくつか用意されている。

keyexample valuedescription
PILLAR__DNS__DOMAINcluster.localクラスター内で使用されるドメインを指定する。
PILLAR__LOCAL__DNS169.254.20.10NodeLocal DNSCache が使用する IP アドレスを使用する、既存の IP アドレスと被ってなければなんでもいい。
PILLAR__DNS__SERVER172.20.0.10kube-dns の IP アドレスを指定する。
PILLAR__CLUSTER__DNS10.108.67.154-upstreamsvc に指定されたサービスの IP アドレス。デフォルトでは -upstreamsvc には kube-dns-upstream が指定されていて、手動で置換する必要はない。
PILLAR__UPSTREAM__SERVERS/etc/resolv.confデフォルトでは /etc/resolv.conf が指定されていて、手動で置換する必要はない。

ここで出てくる kube-dns-upstream とはなにかというと、ここで定義されている Service で、kube-dns の代わりである。NodeLocal DNSCache が動いている Node では、kube-dns の IP アドレスを使っても NodeLocal DNSCache へルーティングされるので、別の Service を用意することで疎通できるようにしている。

apiVersion: v1
kind: Service
metadata:
  name: kube-dns-upstream
  namespace: kube-system
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
    kubernetes.io/name: "KubeDNSUpstream"
spec:
  ports:
  - name: dns
    port: 53
    protocol: UDP
    targetPort: 53
  - name: dns-tcp
    port: 53
    protocol: TCP
    targetPort: 53
  selector:
    k8s-app: kube-dns

そして Nodelocal DNSCache は、ここで定義されているとおり、dnsPolicy は Default である。このため、kubelet に指定されている resolvConf が Pod の /etc/resolv.conf として使用される。

ここまでまとめると下記のような動作をする。

domainbehavior
内部ドメインキャッシュがあればそれを返す、なければ kube-dns-upstream へ forward
外部ドメインキャッシュがあればそれを返す、なければ Nodelocal DNSCache が /etc/resolv.conf をもとに名前解決を行う

インストール方法

通常

公式ドキュメントで紹介されている方法は、kubernetes/kubernetes で提供されているマニフェストを使う方法である。前述した一部のプレースホルダーを置換することで完了する。

$ curl -O https://raw.githubusercontent.com/kubernetes/kubernetes/v1.27.1/cluster/addons/dns/nodelocaldns/nodelocaldns.yaml
$ kubedns=$(kubectl get svc kube-dns -n kube-system -o jsonpath={.spec.clusterIP})
$ sed -i "s/__PILLAR__LOCAL__DNS__/169.254.20.10/g; s/__PILLAR__DNS__DOMAIN__/cluster.local/g; s/__PILLAR__DNS__SERVER__/$kubedns/g" nodelocaldns.yaml
$ k apply -f nodelocaldns.yaml

cilium

cilium を使っている場合は注意が必要で、Local Redirect Policy を使わないと NodeLocal DNSCache ではなく kube-dns へクエリが飛ぶので動いていない状態となる。これについてはドキュメントに記載されており、下記のように cilium から提供されているマニフェストをそのまま使うのが簡単である。

なお、hostNetwork を使っていなかったり、インターフェースが作成されないようになってるので色々違う。

$ curl -O https://raw.githubusercontent.com/cilium/cilium/v1.13/examples/kubernetes-local-redirect/node-local-dns.yaml
$ kubedns=$(kubectl get svc kube-dns -n kube-system -o jsonpath={.spec.clusterIP})
$ sed -i "s/__PILLAR__DNS__SERVER__/$kubedns/g;" node-local-dns.yaml
$ kubectl apply -f node-local-dns.yaml
$ kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/v1.13/examples/kubernetes-local-redirect/node-local-dns-lrp.yaml

検証

NodeLocal DNSCache と CoreDNS のログを出しつつ、検証用の Pod を立ててコマンドを実行して結果を見ていく。各 Pod と実行するコマンドは下記のとおりである。

PodIPcommand
CoreDNS10.244.0.29k -n kube-system logs -f -l k8s-app=kube-dns
NodeLocal DNSCache172.23.111.195k -n kube-system logs -f -l k8s-app=node-local-dns
netshoot10.244.0.38k run netshoot —image=nicolaka/netshoot -it — bash

内部ドメイン

netshoot では、デフォルトで存在する kubernetes Service の名前解決を行うことにする。

netshoot:~# dig kubernetes.default.svc.cluster.local.

NodeLocal DNSCache 側のログはこんな感じで、netshoot の IP アドレスが記録されている。

[INFO] 10.244.0.38:51834 - 38845 "A IN kubernetes.default.svc.cluster.local. udp 77 false 1232" NOERROR qr,aa,rd 106 0.001695631s

CoreDNS 側のログはこんな感じで、NodeLocal DNSCache の IP アドレスが記録されている、

[INFO] 172.23.111.195:57492 - 21023 "A IN kubernetes.default.svc.cluster.local. tcp 77 true 65535" NOERROR qr,aa,rd 106 0.00025829s

2 回目は、NodeLocal DNSCache だけでログが出ていて、キャッシュされているので早いことがわかる。

[INFO] 10.244.0.38:37972 - 54802 "A IN kubernetes.default.svc.cluster.local. udp 77 false 1232" NOERROR qr,aa,rd 106 0.000112795s

外部ドメイン

netshoot では、外部ドメインとして example.com. の名前解決を行うことにする。

netshoot:~# dig example.com.

外部ドメインの場合は、NodeLocal DNSCache で名前解決が行われるので、NodeLocal DNSCache だけでログが出ている。

[INFO] 10.244.0.38:56892 - 40871 "A IN example.com. udp 52 false 1232" NOERROR qr,rd,ra,ad 56 0.355279087s

2 回目はキャッシュされているので早いことがわかる。

[INFO] 10.244.0.38:42148 - 17723 "A IN example.com. udp 52 false 1232" NOERROR qr,aa,rd,ra,ad 56 0.000070897s

cilium

さて、cilium を使っている場合は、hubble observe を使うと簡単に経路を確認できる。

k -n kube-system exec -it cilium-p89s9 -c cilium-agent -- hubble observe --since 3m --pod kube-system/node-local-dns-f2mr6 -

内部ドメインの場合は、kube-dns で名前解決が行われていることが確認できる。

Apr 18 15:01:09.059: default/netshoot:47939 (ID:39341) -> kube-system/node-local-dns-f2mr6:53 (ID:49464) to-endpoint FORWARDED (UDP)
Apr 18 15:01:09.060: kube-system/node-local-dns-f2mr6:58070 (ID:49464) -> kube-system/coredns-5d78c9869d-xh4ql:53 (ID:6868) to-endpoint FORWARDED (TCP Flags: SYN)
Apr 18 15:01:09.060: kube-system/node-local-dns-f2mr6:58070 (ID:49464) <- kube-system/coredns-5d78c9869d-xh4ql:53 (ID:6868) to-endpoint FORWARDED (TCP Flags: SYN, ACK)
Apr 18 15:01:09.060: kube-system/node-local-dns-f2mr6:58070 (ID:49464) -> kube-system/coredns-5d78c9869d-xh4ql:53 (ID:6868) to-endpoint FORWARDED (TCP Flags: ACK)
Apr 18 15:01:09.060: kube-system/node-local-dns-f2mr6:58070 (ID:49464) -> kube-system/coredns-5d78c9869d-xh4ql:53 (ID:6868) to-endpoint FORWARDED (TCP Flags: ACK, PSH)
Apr 18 15:01:09.062: kube-system/node-local-dns-f2mr6:58070 (ID:49464) <- kube-system/coredns-5d78c9869d-xh4ql:53 (ID:6868) to-endpoint FORWARDED (TCP Flags: ACK, PSH)
Apr 18 15:01:09.063: default/netshoot:47939 (ID:39341) <- kube-system/node-local-dns-f2mr6:53 (ID:49464) to-endpoint FORWARDED (UDP)

外部ドメインの場合は、NodeLocal DNSCache で名前解決が行われていることが確認できる。

Apr 18 15:02:09.529: default/netshoot:37900 (ID:39341) -> kube-system/node-local-dns-f2mr6:53 (ID:49464) to-endpoint FORWARDED (UDP)
Apr 18 15:02:09.530: kube-system/node-local-dns-f2mr6:35227 (ID:49464) -> 10.188.1.1:53 (world) to-stack FORWARDED (UDP)
Apr 18 15:02:09.530: kube-system/node-local-dns-f2mr6:35227 (ID:49464) <- 10.188.1.1:53 (world) to-endpoint FORWARDED (UDP)
Apr 18 15:02:09.531: default/netshoot:37900 (ID:39341) <- kube-system/node-local-dns-f2mr6:53 (ID:49464) to-endpoint FORWARDED (UDP)
© Ryota Sakamoto, 2023