TT Lab
はじめる
学ぶ 学習パス コース

CKA — Kubernetes管理者

CoreDNS — 名前はどうやって IP になるのか

TT Labで続きを見る

一言でいうと

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で終わる名前だけが、そのサーバーに行きます。

デバッグ手順: 前から順に

公式の手順を順番に移すと、次のとおりです。

  1. dnsutilsのPod(ドキュメントのregistry.k8s.io/e2e-test-images/agnhostイメージ)を起動して、nslookup kubernetes.defaultを実行してみます。答えが返ってくれば、DNSは正常です。
  2. 失敗したら、そのPodの/etc/resolv.confを先に見ます。searchパスとnameserverが、上の形になっているかを確認します。
  3. kube-systemで-l k8s-app=kube-dnsを使って、CoreDNSのPodがRunningかを見ます。なければ、アドオンがデプロイされていないということです。
  4. 同じラベルでログを見ます。正常なログには、plugin/reload: Running configurationが出力されます。
  5. kube-dnsServiceがあるか、そしてkubernetes.io/service-name=kube-dnsラベルのEndpointSliceにアドレスがあるかを見ます。エンドポイントが空なら、Serviceはあっても、背後にPodがないということです。
  6. クエリがCoreDNSまで届くかを見るには、Corefileにlogプラグインを入れて、ログにクエリの行が出力されるかを見ます。
  7. ログにSERVFAILがあれば、権限を疑います。CoreDNSは、ServiceとEndpointSliceをlist・watchできる必要があり、system:corednsClusterRoleに、discovery.k8s.ioのendpointslicesが抜けていると、このエラーが出ます。
  8. 最後に、ネームスペースを確認します。別のネームスペースの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のログが指す原因を問います。コマンドを暗記するより、「どの部品が抜けているか」を答えられればよいです。