『Kubernetes と名前解決』という記事が非常に面白くためになったため、自分なりに実験したものもまとめさせていただきます。
[目次]
まとめ
- ドメインの最後の . は『ルートドメイン』を表し trailing dot ともいう
- k8s 内部での理想的なホスト名指定(通常は無視できる範囲な気がする)
- 外部 Host: trailing dot 付きで fqdn を指定する
- Service 名: 『サービス名だけで指定する』or『trailing dot 付きで .local』まで含めて完全指定
- 微妙なホスト名指定の例
- 外部 Host:
github.com
- Service 名:
dns-test.default.svc.cluster.local
- 外部 Host:
DNS をみるための準備
自分は k8s 初心者であり色々と試行錯誤したため、実験のための最小構成をメモしておきます。
環境
kind と kubectl と helm があれば大丈夫だと思います。
自分は以下の環境でテストしました。
❯ kind version kind v0.20.0 go1.20.5 darwin/arm64 ❯ kubectl version Client Version: v1.28.4 Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3 Server Version: v1.27.3 ❯ helm version version.BuildInfo{Version:"v3.13.2", GitCommit:"2a2fb3b98829f1e0be6fb18af2f6599e0f4e8243", GitTreeState:"clean", GoVersion:"go1.21.4"}
サービスを持つ minimum の k8s 構成を kind と helm で作成する
# kind を使ってクラスターを作る。 kind create cluster --name dns-host-test # コンテキストがクラスターを向いていることを確認。 ❯ kubectl config get-contexts CURRENT NAME CLUSTER AUTHINFO NAMESPACE * kind-dns-host-test kind-dns-host-test kind-dns-host-test # helm を使ったプロジェクトの初期化。 helm create dns-test # 何も変えずにとりあえず適応する。 helm install dns-test ./dns-test # pod が起動していることを確認する。 ❯ kubectl get po NAME READY STATUS RESTARTS AGE dns-test-b5574b58d-s7ktf 1/1 Running 0 35s
helm create で作成された初期構成のチャートの適応により、以下のようにdns-test の Service が作成されます。
❯ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE dns-test ClusterIP 10.96.227.223 <none> 80/TCP 5m22s kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 7m19s
今回はこの Service、『dns-test』に対する名前解決で遊んでみます。
サービス名で名前解決されるということ
まずは dnsutils という pod を立ててみます。 公式でも紹介されてるので安心して使っていきます。
# 便利なツールを入れる。 kubectl apply -f https://k8s.io/examples/admin/dns/dnsutils.yaml # pod が立っていることを確認。 ❯ kubectl get po NAME READY STATUS RESTARTS AGE dns-test-b5574b58d-s7ktf 1/1 Running 0 21m dnsutils 1/1 Running 0 17m
exec することで外からコマンドを叩いてみます。
❯ kubectl exec dnsutils -- nslookup dns-test Server: 10.96.0.10 Address: 10.96.0.10#53 Name: dns-test.default.svc.cluster.local Address: 10.96.227.223
サービス名として登録した名前が、DNS 名前解決できました。
注目してほしいのが、Name が dns-test.default.svc.cluster.local
となっているところです。
DNS for Services and Pods のページで詳しく書かれていますが、pod と service には特別な A レコードが付与されます。
今回のケースでは、dns-test というサービス名が default という Namespace に svc(service) という形で登録されていたことがわかります。
これらをうまく名前解決し、対象とする service/pod にシームレスにアクセスするために pod 内のコンテナは resolver の設定に特殊な加工が施されています!
# pod 内のコンテナの resolver の設定をみてみる。 ❯ kubectl exec dnsutils -- cat /etc/resolv.conf search default.svc.cluster.local svc.cluster.local cluster.local nameserver 10.96.0.10 options ndots:5
tcpdump で DNS クエリをのぞく
pod に入って dns クエリを確認するために tcpdump の準備をします。 (nslookup, dig などだと service 名に対するクエリが正しく出なかった)
# dnsutils のシェルに入る。 ❯ kubectl exec -it dnsutils /bin/bash # 立ち上げたシェルの中で操作。 # tcpdump をインストール。 root@dnsutils:/# apt install tcpdump # DNS クエリに使われる 53 番ポートをのぞく。 root@dnsutils:/# tcpdump -n -i eth0 port 53
この状態で dnsutils から DNS 名前解決をしてみます。
# host からで大丈夫。 ❯ kubectl exec dnsutils -- nslookup dns-test
この時以下のような出力が2行だけ、tcpdump で待ち受けてる端末に表示されます。
16:35:48.237948 IP 10.244.0.6.36450 > 10.96.0.10.53: 10671+ A? dns-test.default.svc.cluster.local. (52) 16:35:48.240955 IP 10.96.0.10.53 > 10.244.0.6.36450: 10671*- 1/0/0 A 10.96.227.223 (102)
dns-test.default.svc.cluster.local.
で名前解決しに向かってることがわかります。
では、最初から dns-test.default.svc.cluster.local.
で名前解決させることは可能でしょうか。
もちろんこの時も名前解決が完璧にされ、tcpdump の結果も2行だけです。
❯ kubectl exec dnsutils -- nslookup dns-test.default.svc.cluster.local. Server: 10.96.0.10 Address: 10.96.0.10#53 Name: dns-test.default.svc.cluster.local Address: 10.96.227.223
ではもしこの時、最後の . (trailing dot) を忘れたらどうなるでしょうか?
以下のように問題なく名前解決されます。
❯ kubectl exec dnsutils -- nslookup dns-test.default.svc.cluster.local Server: 10.96.0.10 Address: 10.96.0.10#53 Name: dns-test.default.svc.cluster.local Address: 10.96.227.223
しかし tcpdump には 8 行も表示され、見るからに不毛なクエリも発行されています。 Zenn の記事で説明されているように『これ以上補完が必要ないことを明示』するために、最後に . をつけることが必要なことがわかります。
16:42:04.943676 IP 10.244.0.6.34513 > 10.96.0.10.53: 60367+ A? dns-test.default.svc.cluster.local.default.svc.cluster.local. (78) 16:42:04.946116 IP 10.96.0.10.53 > 10.244.0.6.34513: 60367 NXDomain*- 0/1/0 (171) 16:42:04.946361 IP 10.244.0.6.34539 > 10.96.0.10.53: 43826+ A? dns-test.default.svc.cluster.local.svc.cluster.local. (70) 16:42:04.947851 IP 10.96.0.10.53 > 10.244.0.6.34539: 43826 NXDomain*- 0/1/0 (163) 16:42:04.948199 IP 10.244.0.6.51367 > 10.96.0.10.53: 2778+ A? dns-test.default.svc.cluster.local.cluster.local. (66) 16:42:04.948418 IP 10.96.0.10.53 > 10.244.0.6.51367: 2778 NXDomain*- 0/1/0 (159) 16:42:04.948717 IP 10.244.0.6.51560 > 10.96.0.10.53: 24312+ A? dns-test.default.svc.cluster.local. (52) 16:42:04.949399 IP 10.96.0.10.53 > 10.244.0.6.51560: 24312*- 1/0/0 A 10.96.227.223 (102)
つまり、サービスをホスト名に指定して利用するときは、以下のどちらかにすることが理想的といえそうです。
- Service 名のみ
dns-test
~svc.cluster.local
まで含めた完全な名称で指定し trailing dot を必ずつけるdns-test.default.svc.cluster.local.
ちなみに参考にした Zenn の記事では外部サービス名の末尾には trailing dot をつけることが勧められています。
- . をつける
github.com.
おわりに
たかが . 1つなのに、状況によっては挙動が全く変わるところが面白かったです。 (そういったところに注意を払えるエンジニアになりたい。)
この . の例のように、『普段は何気なく省略されているが、いい感じに補われてるために意識することがない』+『ふとしたときに違いが現れてしまう』ような事象があれば知りたいです。