CoreDNS — 名前はどうやって IP になるのか
一言でいうと
Podが名前を問い合わせると、kubeletが書いてくれた/etc/resolv.confのnameserver、つまりkube-dnsServiceのClusterIPにクエリが行きます。その背後にCoreDNSのPodがあり、CoreDNSが何をどのように答えるかは、corednsConfigMapのCorefileが決めます。解決が詰まったら、この連鎖を前から順に確認します。
なぜ必要なのか
ServiceのClusterIPは、削除して作り直すと変わります。PodのIPは、さらに頻繁に変わります。アプリケーションがIPを設定に埋め込むと、再デプロイのたびに壊れるので、Kubernetesは名前を安定したアドレスとし、名前からIPへ向かう道を、クラスター内に用意しました。その道がクラスターDNSで、現在の実装がCoreDNSです。
問題は、この道が複数の部品でできているという点です。Podのresolv.conf、kube-dns Service、EndpointSlice、CoreDNSのPod、Corefile、アップストリームDNSのうち、どれか1つがずれても、「名前が見つからない」という同じ症状が出ます。症状が同じなので、原因を当てずっぽうで当てようとすると、時間を使い果たします。公式ドキュメントのDNS解決のデバッグが、順序を決めている理由です。
どう動くのか
Pod側: resolv.confと検索順序
ServiceとPodのDNSのドキュメントによると、kubeletはPodごとにresolv.confを書いてくれます。形は次のとおりです。
nameserver 10.32.0.10
search <namespace>.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
search行が、名前解決の順序を決めます。testネームスペースのPodがdataを問い合わせると、まずdata.test.svc.cluster.localに展開してみます。そのため、同じネームスペースのServiceは短い名前で見つかり、別のネームスペース(prod)のServiceは、data.prodのように、ネームスペースを付ける必要があります。ドキュメントの表現どおり、「ネームスペースを指定しないDNSクエリは、Podのネームスペースに限定」されます。ndots:5は、名前にドットが5つ未満なら、検索ドメインを先に付けてみるという意味なので、外部ドメイン1つを探すために、クラスタードメイン3つを先に叩くコストがかかります。
どのnameserverを使うかは、PodのスペックのdnsPolicyが決めます。ドキュメントが「Defaultはデフォルト値ではない」と別に書いているほど、紛らわしい部分です。
| dnsPolicy | 動作 |
|---|---|
ClusterFirst |
指定しなかったときのデフォルト。クラスタードメインでないクエリは、アップストリームに転送します |
ClusterFirstWithHostNet |
hostNetwork: trueのPodがクラスターDNSを使うには、これを明示する必要があります |
Default |
ノードの名前解決の設定をそのまま引き継ぎます |
None |
Kubernetesの設定を無視し、dnsConfigですべて直接指定します |
サーバー側: kube-dns ServiceとCorefile
DNSサーバーは、kube-systemネームスペースのCoreDNSのPodで、その前にkube-dnsという名前のClusterIP Serviceがあります(53/UDP、53/TCP)。名前がcorednsではなくkube-dnsである理由は、元のkube-dnsとの後方互換性のためで、Podのラベルk8s-app=kube-dnsも、同じ理由で残っています。デバッグするときは、このラベルでPodを探します。
CoreDNSは、プラグインを組み合わせるサーバーで、設定ファイルがCorefileです。DNSサービスのカスタマイズのドキュメントが示すデフォルトのCorefileは、次のとおりです。
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
1行ずつ意味を知っておくと、ログと症状がつながります。errorsは、エラーをstdoutに記録し、healthは、8080ポートで状態を知らせ、lameduck 5sは、終了前の5秒間を異常として表示して、トラフィックを抜きます。readyは、8181ポートで、すべてのプラグインの準備が整ったときに200を返します。kubernetesプラグインが、ServiceとPodのIPをもとに、クラスタードメインのクエリに答え、ttl 30は応答のTTLです(ドキュメントではデフォルト5秒、最大3600秒)。forward . /etc/resolv.confは、クラスタードメイン以外のクエリを、CoreDNSのPodのresolv.confに書かれたアップストリームに渡します。cache 30は応答のキャッシュ、loopは、転送のループを検知するとプロセスを停止させる安全装置、reloadは、Corefileが変更されると自動で読み直すプラグインで、loadbalanceは、A・AAAA・MXレコードの順序をシャッフルするラウンドロビンです。ConfigMapを修正してから反映されるまで、ドキュメントは2分程度を見込むようにと言っています。
特定のドメインだけを別のサーバーに送るstub domainも、同じファイルに置きます。ドキュメントの例のように、consul.local:53のサーバーブロックの中にforward . 10.150.0.1を置くと、.consul.localで終わる名前だけが、そのサーバーに行きます。
デバッグ手順: 前から順に
公式の手順を順番に移すと、次のとおりです。
dnsutilsのPod(ドキュメントのregistry.k8s.io/e2e-test-images/agnhostイメージ)を起動して、nslookup kubernetes.defaultを実行してみます。答えが返ってくれば、DNSは正常です。- 失敗したら、そのPodの
/etc/resolv.confを先に見ます。searchパスとnameserverが、上の形になっているかを確認します。 kube-systemで-l k8s-app=kube-dnsを使って、CoreDNSのPodがRunningかを見ます。なければ、アドオンがデプロイされていないということです。- 同じラベルでログを見ます。正常なログには、
plugin/reload: Running configurationが出力されます。 kube-dnsServiceがあるか、そしてkubernetes.io/service-name=kube-dnsラベルのEndpointSliceにアドレスがあるかを見ます。エンドポイントが空なら、Serviceはあっても、背後にPodがないということです。- クエリがCoreDNSまで届くかを見るには、Corefileに
logプラグインを入れて、ログにクエリの行が出力されるかを見ます。 - ログに
SERVFAILがあれば、権限を疑います。CoreDNSは、ServiceとEndpointSliceをlist・watchできる必要があり、system:corednsClusterRoleに、discovery.k8s.ioのendpointslicesが抜けていると、このエラーが出ます。 - 最後に、ネームスペースを確認します。別のネームスペースのServiceは、
<service>.<namespace>で問い合わせる必要があります。
ドキュメントの「既知の問題」も、覚えておく価値があります。Ubuntuのようにsystemd-resolvedを使うディストリビューションは、/etc/resolv.confをスタブファイルに変えてあるため、転送のループが生じることがあり、その場合は、kubeletの--resolv-confで、/run/systemd/resolve/resolv.confを指す必要があります(kubeadmはこれを自動で検出します)。また、glibcはnameserverを3つまでしか読みませんが、Kubernetesがそのうちの1つを使います。
現場での姿
ログにSERVFAILがいっぱいなのに、Podは正常である。ドキュメントが挙げた例が、まさにこれです。PodもRunningで、Serviceもあるのに、応答がSERVFAILなら、CoreDNSがEndpointSliceを読む権限がない場合が多くあります。kubectl describe clusterrole system:corednsで、endpointslices.discovery.k8s.ioにlist・watchがあるかを見て、なければClusterRoleを直します。
hostNetworkのPodだけがService名を見つけられない。ノードのネットワークを使うPodは、dnsPolicyを指定しないと、ClusterFirstがDefaultのように動作して、ノードのresolv.confを使います。ドキュメントがClusterFirstWithHostNetを明示するようにと言っている理由で、ログ収集器やノードエージェントをデプロイするときに、よく抜け落ちます。
次のクイズで確認すること
クイズでは、baseとoverlayの関係、2種類のパッチ方式、kubectl topが失敗する理由とmetrics-serverが読むエンドポイント、dnsPolicyのデフォルト値、そしてSERVFAILのログが指す原因を問います。コマンドを暗記するより、「どの部品が抜けているか」を答えられればよいです。