🐳

K8sクラスター内で複数の(Core)DNSを使用する

2023/02/03に公開

クラスター内で複数のDNSを使用する

クラスター内で複数のDNSを使いたいケースがあるのかあまりわかりませんが、どうやったら出来るのか気になったので調べてみました。DNSの挙動テストなどでわざわざ新しいクラスターを用意できない/既存のアプリを使用してテストしたいみたいなときに使えるかもしれませんね。。。

背景

自分は今container platformの開発チームに所属しています。主な業務はkubernetes clusterの開発・運用ですが、業務の一部としてtenantのtrouble shootingを手伝うことがそれなりにあります。例えばtenant側のapplicationでwebAPIとの通信に異常が見られるときhost machineやcniに異常が無いかなどの調査依頼を受けます。
そういった依頼のひとつに、tenantのアプリでかなり低頻度で名前解決に失敗する異常がありCoreDNS側の不具合を調べて欲しいというものがありました。たとえばこれが特定のversionのCoreDNSに起因しているかをtestするために、現在使用しているCoreDNSと別のversionのCoreDNSをdeployしてテストすることは可能なのだろうか?と疑問に思ったのがきっかけです。

Kubernetes での名前解決

そもそもkubernetesでの名前解決がどのように行われているのか見てみましょう。
containerがkubernetes.default.cluster.localにアクセスするときもちろんCoreDNSにリクエストが行くわけですが、containerはどうやってCoreDNSのendpointを見つけるのでしょうか?
CoreDNSのエンドポイントを確認しておきます。

$ k get svc -nkube-system kube-dns
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
kube-dns   ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP,9153/TCP   26d

それでは任意のcontainer内の/etc/resolv.conを見てみます。

$ cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local

kube-dns(CoreDNS)のendpointがnameserverに指定されています。つまりkubernetesコンポーネントの誰かがこのファイルをcontainer内に作ることで各pod/containerはCoreDNSで名前解決を行えるようになっています。
実際にはkubeletがこの役割を担っており--cluster-dns flagにて指定されたaddressがnameserverとして指定されるようになっています。
kubelet-cluster-dns-flag

さて、ではCoreDNSはこれらの(in-clusterな)recordをどうやって取得しているのでしょうか?
これはCoreDNSがendpointslicesオブジェクトを監視することで取得しています。(ソースとなるページが見つけられなかったのでどなたか知っている方いたら教えてください)
ちなみにin-clusterでないドメインはデフォルトではCoreDNSが走っているhost machineの/etc/resolv.confにforwardされるようになっています。(corednsのconfigmapを参照)

実際にやってみる

とりあえず現状で使用しているCoreDNSのマニフェストをそのままパクッてきて名前とかだけを変えて二個目のCoreDNSを立ててみます。
configmapやserviceaccountは既存のものを使いまわしてdeploymentとserviceだけ新しくします。(deploymentすら使いまわしでもok)

$ k get deploy
NAME        READY   UP-TO-DATE   AVAILABLE   AGE
coredns     2/2     2            2           26d
coredns-2   2/2     2            2           109m

deployの方は名前を変えるだけでなくmatchLabelsも新しくするのを忘れないようにしてください。

$ k get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
kube-dns     ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP,9153/TCP   26d
kube-dns-2   ClusterIP   10.96.0.20   <none>        53/UDP,53/TCP,9153/TCP   108m

serviceの方は名前を変えるだけでなく新しくclusterIPを指定するのを忘れないようにしてください。今回は10.96.0.20を使用します。
これで二個目のDNSの準備ができました。

container内の/etc/resolv.confの書き換えはkubeletの--cluster-dns flagを使うとnode内のpod丸ごと影響受けてしまうので今回は特定のpodだけ二個目のDNSを使うようにします。dnsPolicyとdnsConfigによってpodのマニフェスト内でDNSの指定ができるようになっているのでそれを使用します。

apiVersion: v1
kind: Pod
metadata:
  name: dnsutils
  namespace: default
spec:
  containers:
  - name: dnsutils
    image: registry.k8s.io/e2e-test-images/jessie-dnsutils:1.3
    command:
      - sleep
      - "infinity"
    imagePullPolicy: IfNotPresent
  restartPolicy: Never
  dnsPolicy: None
  dnsConfig:
    nameservers:
      - 10.96.0.20  # 新しいCoreDNSのエンドポイント

こんな感じのpodを用意して実際に新しいDNSが使えるかどうかnslookupしてみます。

$ k exec dnsutils -- nslookup google.com
Server:         10.96.0.20
Address:        10.96.0.20#53

Non-authoritative answer:
Name:   google.com
Address: 216.58.220.142

できました。ちゃんとServerが新しいDNSのものになっています。
もちろんin-clusterなドメインも解決できます。

$ k exec dnsutils -- nslookup kubernetes.default.svc.cluster.local
Server:         10.96.0.20
Address:        10.96.0.20#53

Name:   kubernetes.default.svc.cluster.local
Address: 10.96.0.1

おわりに

kubernetesでの名前解決の実装がかなりシンプルで分かりやすかったですね。
CoreDNSのconfig mapではカスタムドメインの登録とかできるので自分用のCoreDNSをカスタマイズして遊べそうですね。endpoineslicesのlist/watchのrbacが必要なので、admin的なuserで無い人は自namespace内だけの名前解決とかになっちゃいそうですが。。。おわり。

Discussion