クラスタDNSを実際に掘ってみる
このラボは本物のKubernetes上で動きます
VMの中でk3sが1台、実際に起動しています。CoreDNSが本当に動き、クエリをログに残し、Podは実際のコンテナです。そのため、「名前を1つ探すのに、クエリが何回出るか」のようなことを、数えて確認できます。
最初に起動するまでに、2分ほどかかります。VMが起動して、k3sをインストールするからです。
目標
Podが名前を探す経路を最初から最後までたどり、ndots:5が作り出す隠れたコストを自分で数えて確認したうえで、dnsConfigで直します。
なぜ重要なのか
クラスターでの「遅い」という報告の相当数が、DNSです。ところが、アプリケーションのメトリクスにはなかなか現れません。リクエスト1つが、名前1つを探すのにクエリを5回投げて、そのうち4回がNXDOMAINでも、アプリケーションの立場では、ただの「少し遅いリクエスト」です。CoreDNSの負荷は静かに上がり、ある日Podの数が増えたときに、そこで破綻します。
原因は、resolv.confの1行です。ndots:5は、ドットが5個未満の名前は、先に検索ドメインを付けて試すという意味です。example.comはドットが1つなので、example.com.default.svc.cluster.localから問い合わせることになります。クラスター内の名前を短く書けるようにするための便利さが、外へ出ていく名前には、そのままコストになります。
ステップ
- CoreDNSがどこにあって何をしているかを確認してください(保存先:
/root/k8sdns/coredns.txt)。kube-dnsServiceのClusterIPと、Corefileの全文が入っている必要があります。 - Pod内の
/etc/resolv.confをそのまま保存してください(保存先:/root/k8sdns/resolv.txt)。nameserver・search・optionsの3行がすべてある必要があります。 webというDeployment(レプリカ2、nginx:1.27-alpine、ポート名http)と、同じ名前のServiceを作成し、長い名前と短い名前の両方をルックアップして保存してください(保存先:/root/k8sdns/svc-a.txt)。同じClusterIPが出る必要があります。- CoreDNSのCorefileで
logプラグインを有効にして、example.comを2種類のツールでそれぞれルックアップし、CoreDNSのログのクエリ数を数えて保存してください(保存先:/root/k8sdns/ndots.txt)。queries_nslookup=とqueries_getaddrinfo=の2行で始めて、その下にログを貼り付けます。- busyboxの
nslookupを1回 curlを1回(curlimages/curl:8.10.1) 2つの数字が大きく違います。なぜ違うのかが、このステップの核心です。
- busyboxの
web-hというヘッドレスService(clusterIP: None)を作成して、ルックアップ結果を保存してください(保存先:/root/k8sdns/headless.txt)。PodのIPがそのまま出る必要があります。webのSRVレコードをルックアップして保存してください(保存先:/root/k8sdns/srv.txt)。lowdotsというPodを、dnsConfigでndots: 1を指定して起動し、ステップ4のcurlと同じツールでクエリ数をもう一度数えて保存してください(保存先:/root/k8sdns/fixed.txt、queries=の行を含めます)。3回以下である必要があります。ツールを変えると、比較が成立しません。dns_ip=、queries_before=、queries_after=の3行と一緒に、ndotsがなぜ問題だったのか、そして、nslookupで測るとなぜ問題が見えないのかを、/root/k8sdns/report.mdに書いてください。
参考
- 名前解決のツールはPodの中で使います。
kubectl run q --rm -it --image=busybox:1.36 --restart=Never -- nslookup <이름>が最も簡単です(プレースホルダーは名前です)。 - Corefileは
kubectl -n kube-system get cm coredns -o yamlで見ます。直すときは、kubectl -n kube-system edit cm corednsのあとに、kubectl -n kube-system rollout restart deploy corednsを実行してください。 - ログは
kubectl -n kube-system logs deploy/corednsで見ます。数える前にログを一度空にしたければ、CoreDNSを再起動すればよいです。 - SRVの名前は、
_<포트이름>._<프로토콜>.<서비스>.<네임스페이스>.svc.cluster.localです(プレースホルダーはポート名、プロトコル、Service、ネームスペースです)。ポートに名前がないと、SRVは作られません。 - ステップ4の2つの数字が違う理由: busyboxの
nslookupは、DNSプロトコルを直接話すので、searchとndotsを読みません。普通のアプリケーションはgetaddrinfoを呼び、そちらはresolv.confにそのまま従い、AとAAAAをそれぞれ問い合わせます。 - ログを数えるときは、数える直前にCoreDNSを再起動してください。再起動が、そのままログを空にする方法で、そうすれば、前のステップのクエリが混ざりません。
- よくある間違い1: 名前の末尾にドットを付けること(
example.com.)。絶対名なので検索ドメインをスキップしてしまい、学ぼうとしたことが消えます。 - よくある間違い2: ステップ5で
clusterIP: Noneを外すこと。そうすると普通のServiceになり、ClusterIPが1つだけ返ってきます。
CoreDNSはどこにあるのか
CoreDNSがどこにあって何をしているかを確認してください(保存先: /root/k8sdns/coredns.txt)。kube-dnsServiceのClusterIPと、Corefileの全文が入っている必要があります。
kube-dnsという名前のServiceをkube-systemで探し、corednsConfigMapのCorefileを一緒に入れます。名前がCoreDNSなのにService名がkube-dnsなのは、以前の実装の名前をそのまま引き継いだからです。
Podは何を見て名前を探すのか
Pod内の/etc/resolv.confをそのまま保存してください(保存先: /root/k8sdns/resolv.txt)。nameserver・search・optionsの3行がすべてある必要があります。
Pod1つを起動して、その中の/etc/resolv.confをそのまま読みます。VM自身のresolv.confではなく、Podの中のものである必要があります。2つはまったく違います。
Service名がアドレスになる瞬間
webというDeployment(レプリカ2、nginx:1.27-alpine、ポート名http)と、同じ名前のServiceを作成し、長い名前と短い名前の両方をルックアップして保存してください(保存先: /root/k8sdns/svc-a.txt)。同じClusterIPが出る必要があります。
DeploymentとServiceを作成したあと、同じネームスペースのPodから、webとweb.default.svc.cluster.localをそれぞれルックアップします。短い名前が使えるのは、searchドメインのおかげです。
名前1つにクエリが何回か
CoreDNSのCorefileでlogプラグインを有効にして、example.comを2種類のツールでそれぞれルックアップし、CoreDNSのログのクエリ数を数えて保存してください(保存先: /root/k8sdns/ndots.txt)。queries_nslookup=とqueries_getaddrinfo=の2行で始めて、その下にログを貼り付けます。
- busyboxの
nslookupを1回 curlを1回(curlimages/curl:8.10.1) 2つの数字が大きく違います。なぜ違うのかが、このステップの核心です。
Corefileにlogを1行入れて、CoreDNSを再起動したあと、Podからexample.comを1回だけルックアップして、ログを数えます。末尾にドットを付けると(example.com.)、検索ドメインをスキップするので、付けないでください。
ヘッドレスは何を返すのか
web-hというヘッドレスService(clusterIP: None)を作成して、ルックアップ結果を保存してください(保存先: /root/k8sdns/headless.txt)。PodのIPがそのまま出る必要があります。
clusterIP: NoneのServiceを作成して、ルックアップします。普通のServiceと違い、アドレスが複数出ます。
ポート番号を名前で探す
webのSRVレコードをルックアップして保存してください(保存先: /root/k8sdns/srv.txt)。
SRVの名前は_<포트이름>._tcp.<서비스>.<네임스페이스>.svc.cluster.localです(プレースホルダーはポート名、Service、ネームスペースです)。ステップ3で、ポートにhttpという名前を付けておきました。
ndotsを下げて直す
lowdotsというPodを、dnsConfigでndots: 1を指定して起動し、ステップ4のcurlと同じツールでクエリ数をもう一度数えて保存してください(保存先: /root/k8sdns/fixed.txt、queries=の行を含めます)。3回以下である必要があります。ツールを変えると、比較が成立しません。
PodのspecのdnsConfig.optionsに{name: ndots, value: "1"}を入れます。そうすると、ドットが1つだけでも、検索ドメインを通りません。
何を学んだか
dns_ip=、queries_before=、queries_after=の3行と一緒に、ndotsがなぜ問題だったのか、そして、nslookupで測るとなぜ問題が見えないのかを、/root/k8sdns/report.mdに書いてください。
dns_ip=、queries_before=、queries_after=の3行と一緒に、ndotsがなぜ問題だったのかを、本文に書きます。