GKE の kube-dns について

Standard クラスタでアプリケーションを実行している場合、kube-dns はサービス ディスカバリと通信を有効にするデフォルトの DNS プロバイダです。このドキュメントでは、kube-dns を使用して DNS を管理する方法について説明します。これには、アーキテクチャ、構成、GKE 環境内の DNS 解決を最適化するためのベスト プラクティスが含まれます。

このドキュメントは、GKE で DNS の管理を担当するデベロッパー、管理者、アーキテクトを対象としています。 Google Cloudの一般的なロールとタスクについては、一般的な GKE Enterprise ユーザーロールとタスクをご覧ください。

始める前に、Kubernetes Service と一般的な DNS のコンセプトを理解しておいてください。

kube-dns アーキテクチャについて

kube-dns は GKE クラスタ内で動作し、Pod と Service 間の DNS 解決を有効にします。

次の図は、Pod が kube-dns Service とどのように連携するかを示しています。

図 1: Pod が `kube-dns` Pod によってバックアップされている `kube-dns` Service に DNS クエリを送信する方法を示す図。`kube-dns` Pod は内部 DNS 解決を処理し、外部クエリをアップストリーム DNS サーバーに転送します。

主要コンポーネント

kube-dns には、次の主要コンポーネントが含まれています。

  • kube-dns Pod: これらの Pod は kube-dns サーバー ソフトウェアを実行します。これらの Pod の複数のレプリカが kube-system Namespace で実行され、高可用性と冗長性が実現されます。
  • kube-dns Service: ClusterIP タイプのこの Kubernetes Service は、kube-dns Pod をグループ化し、単一の安定したエンドポイントとして公開します。ClusterIP はクラスタの DNS サーバーとして機能し、Pod はこれを使用して DNS クエリを送信します。kube-dns は、ヘッドレス サービスあたり最大 1,000 個のエンドポイントをサポートします。
  • kube-dns-autoscaler: この Pod は、クラスタのサイズ(ノード数と CPU コア数を含む)に基づいて kube-dns レプリカの数を調整します。このアプローチにより、kube-dns がさまざまな DNS クエリ負荷を処理できるようになります。

内部 DNS の解決

Pod がクラスタのドメイン内(myservice.my-namespace.svc.cluster.local など)の DNS 名を解決する必要がある場合、次のプロセスが発生します。

  1. Pod DNS 構成: 各ノードの kubelet は、Pod の /etc/resolv.conf ファイルを構成します。このファイルは、ネームサーバーとして kube-dns Service の ClusterIP を使用します。
  2. DNS クエリ: Pod が kube-dns Service に DNS クエリを送信します。
  3. 名前解決: kube-dns がクエリを受信します。内部 DNS レコードで対応する IP アドレスを検索し、Pod に応答します。
  4. 通信: Pod は、解決された IP アドレスを使用してターゲット Service と通信します。

外部 DNS の解決

Pod が外部 DNS 名またはクラスタのドメイン外の名前を解決する必要がある場合、kube-dns は再帰リゾルバとして機能します。クエリは、ConfigMap ファイルで構成されているアップストリーム DNS サーバーに転送されます。特定のドメイン(スタブドメインとも呼ばれます)のカスタム リゾルバを構成することもできます。この構成により、kube-dns はこれらのドメインのリクエストを特定のアップストリーム DNS サーバーに転送します。

Pod DNS を構成する

GKE では、各ノードの kubelet エージェントが、そのノードで実行される Pod の DNS 設定を構成します。

/etc/resolv.conf ファイルを構成する

GKE が Pod を作成すると、kubelet エージェントが Pod の /etc/resolv.conf ファイルを変更します。このファイルは、名前解決用の DNS サーバーを構成し、検索ドメインを指定します。デフォルトでは、kubelet はクラスタの内部 DNS サービス kube-dns をネームサーバーとして使用するように Pod を構成します。また、ファイル内の検索ドメインも入力します。これらの検索ドメインを使用すると、DNS クエリで非修飾名を使用できます。たとえば、Pod が myservice をクエリする場合、Kubernetes はまず myservice.default.svc.cluster.local、次に myservice.svc.cluster.local、そして search リストの他のドメインを解決しようとします。

次の例は、デフォルトの /etc/resolv.conf 構成を示しています。

nameserver 10.0.0.10
search default.svc.cluster.local svc.cluster.local cluster.local c.my-project-id.internal google.internal
options ndots:5

このファイルには次のエントリがあります。

  • nameserver: kube-dns サービスの ClusterIP を定義します。
  • search: DNS ルックアップ時に完全修飾されていない名前に付加される検索ドメインを定義します。
  • options ndots:5: GKE が名前を完全修飾と見なすしきい値を設定します。5 つ以上のドットを含む名前は、完全修飾名とみなされます。

hostNetwork: true 設定で構成された Pod は、ホストから DNS 構成を継承し、kube-dns を直接クエリしません。

kube-dns をカスタマイズ

kube-dns は、堅牢なデフォルトの DNS 解決を提供します。解決効率の向上や優先 DNS リゾルバの使用など、特定のニーズに合わせて動作を調整できます。スタブドメインとアップストリーム ネームサーバーの両方を構成するには、kube-system Namespace の kube-dns ConfigMap を変更します。

kube-dns ConfigMap を変更する

kube-dns ConfigMap を変更する手順は次のとおりです。

  1. ConfigMap を開いて編集します。

    kubectl edit configmap kube-dns -n kube-system
    
  2. data セクションで、stubDomains フィールドと upstreamNameservers フィールドを次のように追加します。

    apiVersion: v1
    kind: ConfigMap
    metadata:
      labels:
        addonmanager.kubernetes.io/mode: EnsureExists
      name: kube-dns
      namespace: kube-system
    data:
      stubDomains: |
        {
          "example.com": [
            "8.8.8.8",
            "8.8.4.4"
          ],
          "internal": [ # Required if your upstream nameservers can't resolve GKE internal domains
            "169.254.169.254" # IP of the metadata server
          ]
        }
      upstreamNameservers: |
        [
          "8.8.8.8", # Google Public DNS
          "1.1.1.1" # Cloudflare DNS
        ]
    
  3. ConfigMap を保存します。kube-dns は構成を自動的に再読み込みします。

スタブドメイン

スタブドメインを使用すると、特定のドメインのカスタム DNS リゾルバを定義できます。Pod がそのスタブ ドメイン内の名前をクエリすると、kube-dns はデフォルトの解決メカニズムを使用する代わりに、指定されたリゾルバにクエリを転送します。

kube-dns ConfigMapstubDomains セクションを含めます。

このセクションでは、ドメインと対応するアップストリーム ネームサーバーを指定します。kube-dns は、そのドメイン内の名前に関するクエリを指定されたサーバーに転送します。たとえば、internal.mycompany.com のすべての DNS クエリを 192.168.0.10 にルーティングし、"internal.mycompany.com": ["192.168.0.10"]stubDomains に追加できます。

example.com などのスタブドメインにカスタム リゾルバを設定すると、kube-dns は、*.example.com などのサブドメインを含む、そのドメインのすべての名前解決リクエストを指定されたサーバーに転送します。

アップストリーム ネームサーバー

カスタム アップストリーム ネームサーバーを使用して外部ドメイン名を解決するように kube-dns を構成できます。この構成では、クラスタの内部ドメイン(*.cluster.local)のリクエストを除くすべての DNS リクエストを指定されたアップストリーム サーバーに転送するように kube-dns に指示します。metadata.internal*.google.internal などの内部ドメインは、カスタム アップストリーム サーバーで解決できない場合があります。Workload Identity Federation for GKE を有効にする場合、またはこれらのドメインに依存するワークロードがある場合は、ConfigMapinternal のスタブ ドメインを追加します。このスタブ ドメインのリゾルバとして、メタデータ サーバーの IP アドレスである 169.254.169.254 を使用します。

カスタム kube-dns Deployment を管理する

Standard GKE では、kube-dns は Deployment として実行されます。カスタム kube-dns デプロイとは、クラスタ管理者がデフォルトの GKE 提供のデプロイを使用するのではなく、デプロイを制御してニーズに合わせてカスタマイズできることを意味します。

カスタム デプロイの理由

次の理由から、カスタム kube-dns デプロイを検討してください。

  • リソース割り当て: kube-dns Pod の CPU とメモリリソースを微調整して、DNS トラフィックが多いクラスタのパフォーマンスを最適化します。
  • イメージ バージョン: 特定のバージョンの kube-dns イメージを使用するか、CoreDNS などの代替 DNS プロバイダに切り替えます。
  • 高度な構成: ロギングレベル、セキュリティ ポリシー、DNS キャッシュ保存動作をカスタマイズします。

カスタム Deployment の自動スケーリング

組み込みの kube-dns-autoscaler は、デフォルトの kube-dns Deployment で動作します。カスタム kube-dns Deployment を作成した場合、組み込みのオートスケーラーはそれを管理しません。そのため、カスタム Deployment のレプリカ数をモニタリングして調整するように特別に構成された別のオートスケーラーを設定する必要があります。このアプローチでは、クラスタに独自のオートスケーラー構成を作成してデプロイします。

カスタム Deployment を管理する場合は、オートスケーラー イメージを最新の状態に保つなど、すべてのコンポーネントを管理する必要があります。古いコンポーネントを使用すると、パフォーマンスの低下や DNS の障害につながる可能性があります。

独自の kube-dns デプロイを構成して管理する方法の詳細な手順については、カスタム kube-dns Deployment の設定をご覧ください。

トラブルシューティング

kube-dns のトラブルシューティングについては、次のページをご覧ください。

DNS 解決を最適化する

このセクションでは、GKE での DNS 管理に関する一般的な問題とベスト プラクティスについて説明します。

Pod の dnsConfig 検索ドメインの上限

Kubernetes では、DNS 検索ドメインの数は 32 個に制限されています。Pod の dnsConfig で 32 個を超える検索ドメインを定義しようとすると、kube-apiserver は Pod を作成しません。次のようなエラーが発生します。

The Pod "dns-example" is invalid: spec.dnsConfig.searches: Invalid value: []string{"ns1.svc.cluster-domain.example", "my.dns.search.suffix1", "ns2.svc.cluster-domain.example", "my.dns.search.suffix2", "ns3.svc.cluster-domain.example", "my.dns.search.suffix3", "ns4.svc.cluster-domain.example", "my.dns.search.suffix4", "ns5.svc.cluster-domain.example", "my.dns.search.suffix5", "ns6.svc.cluster-domain.example", "my.dns.search.suffix6", "ns7.svc.cluster-domain.example", "my.dns.search.suffix7", "ns8.svc.cluster-domain.example", "my.dns.search.suffix8", "ns9.svc.cluster-domain.example", "my.dns.search.suffix9", "ns10.svc.cluster-domain.example", "my.dns.search.suffix10", "ns11.svc.cluster-domain.example", "my.dns.search.suffix11", "ns12.svc.cluster-domain.example", "my.dns.search.suffix12", "ns13.svc.cluster-domain.example", "my.dns.search.suffix13", "ns14.svc.cluster-domain.example", "my.dns.search.suffix14", "ns15.svc.cluster-domain.example", "my.dns.search.suffix15", "ns16.svc.cluster-domain.example", "my.dns.search.suffix16", "my.dns.search.suffix17"}: must not have more than 32 search paths.

kube-apiserver は、Pod の作成試行に対する応答としてこのエラー メッセージを返します。この問題を解決するには、構成から追加の検索パスを削除します。

kube-dns のアップストリーム nameservers の上限

kube-dns では、upstreamNameservers 値の数が 3 つに制限されます。3 つを超える数を定義すると、Cloud Logging に次のようなエラーが表示されます。

Invalid configuration: upstreamNameserver cannot have more than three entries (value was &TypeMeta{Kind:,APIVersion:,}), ignoring update

このシナリオでは、kube-dnsupstreamNameservers 構成を無視し、以前の有効な構成を引き続き使用します。この問題を解決するには、kube-dns ConfigMap から余分な upstreamNameservers を削除します。

スケールアップ kube-dns

Standard クラスタでは、クラスタノードがスケールアップしたときに kube-dns Pod がさらに作成されるように、nodesPerReplica に小さい値を使用できます。Kubernetes API を監視する kube-dns Pod が多いことが原因で GKE コントロール プレーン仮想マシン(VM)が過負荷状態にならないように、max フィールドに明示的な値を設定することを強くおすすめします。

max フィールドの値をクラスタ内のノード数に設定できます。クラスタのノード数が 500 を超える場合は、max フィールドの値を 500 に設定します。

kube-dns-autoscaler ConfigMap を編集することで、kube-dns レプリカの数を変更できます。

kubectl edit configmap kube-dns-autoscaler --namespace=kube-system

出力は次のようになります。

linear: '{"coresPerReplica":256, "nodesPerReplica":16,"preventSinglePointFailure":true}'

kube-dns レプリカの数は、次の式で計算されます。

replicas = max( ceil( cores * 1/coresPerReplica ) , ceil( nodes * 1/nodesPerReplica ) )

スケールアップするには、nodesPerReplica フィールドの値を小さくし、max フィールドの値を含めます。

linear: '{"coresPerReplica":256, "nodesPerReplica":8,"max": 15,"preventSinglePointFailure":true}'

この構成では、クラスタ内の 8 ノードごとに 1 つの kube-dns Pod が作成されます。24 ノードクラスタには 3 つのレプリカがあり、40 ノードクラスタには 5 つのレプリカがあります。クラスタが 120 ノードを超えても、kube-dns レプリカの数は max フィールドの値である 15 を超えることはありません。

クラスタで DNS の可用性のベースライン レベルを確保するには、kube-dns フィールドの最小レプリカ数を設定します。

min フィールドが構成された kube-dns-autoscaler ConfigMap の出力は次のようになります。

linear: '{"coresPerReplica":256, "nodesPerReplica":8,"max": 15,"min": 5,"preventSinglePointFailure":true}'

DNS ルックアップ時間を短縮する

デフォルトの kube-dns プロバイダで DNS ルックアップのレイテンシが高くなったり、DNS の解決に失敗したりする原因はいくつかあります。アプリケーションでは、これらの問題が getaddrinfo EAI_AGAIN エラーとして発生する可能性があります。これは、名前解決の一時的な失敗を示します。原因は次のとおりです。

  • ワークロード内で DNS ルックアップの実行頻繁が高い。
  • ノードあたりの Pod 密度が高い。
  • Spot VM またはプリエンプティブル VM で kube-dns を実行している。これにより、予期しないノード削除が発生する可能性があります。
  • kube-dns Pod 内の dnsmasq インスタンスの容量を超える大量のクエリ。1 つの kube-dns インスタンスの同時 TCP 接続数の上限は、GKE バージョン 1.31 以降では 200、GKE バージョン 1.30 以前では 20 です。

DNS ルックアップ時間を改善するには、次の操作を行います。

  • Spot VM またはプリエンプティブル VM では、kube-dns などの重要なシステム コンポーネントを実行しないでください。標準 VM を含み、Spot VM またはプリエンプティブル VM を含まないノードプールを 1 つ以上作成します。taint と toleration を使用して、重要なワークロードがこれらの信頼性の高いノードにスケジュールされるようにします。
  • NodeLocal DNSCache を有効にします。NodeLocal DNSCache は、各ノードで DNS レスポンスを直接キャッシュに保存するため、レイテンシと kube-dns サービスの負荷が軽減されます。NodeLocal DNSCache を有効にして、デフォルト拒否ルールでネットワーク ポリシーを使用する場合は、ワークロードが node-local-dns Pod に DNS クエリを送信できるようにポリシーを追加します。
  • kube-dns をスケールアップします。
  • アプリケーションでは dns.lookup ベースの関数ではなく dns.resolve* ベースの関数を使用する(dns.lookup は同期的であるため)。
  • https://google.com/ ではなく、完全修飾ドメイン名(FQDN)(例: https://google.com./)を使用します。

GKE クラスタのアップグレード中に、kube-dns などのコントロール プレーン コンポーネントの同時アップグレードにより、DNS 解決に失敗することがあります。通常、このような障害の影響を受けるノードはごく一部です。本番環境のクラスタに適用する前に、非本番環境でクラスタのアップグレードを徹底的にテストします。

Service の検出可能性を確保する

kube-dns は、エンドポイントを持つ Service の DNS レコードのみを作成します。Service にエンドポイントがない場合、kube-dns はその Service の DNS レコードを作成しません。

DNS TTL の不一致を管理する

TTL が大きいまたは無限のアップストリーム DNS リゾルバから DNS レスポンスを受け取った場合、kube-dns はこの TTL 値を保持します。この動作により、キャッシュに保存されたエントリと実際の IP アドレスの間に不一致が生じる可能性があります。

GKE は、1.21.14-gke.9100 以降、1.22.15-gke.2100 以降などの特定のコントロール プレーン バージョンでこの問題を解決します。これらのバージョンでは、TTL が大きい DNS レスポンスの最大 TTL 値を 30 秒に設定します。この動作は NodeLocal DNSCache と同様です。

kube-dns 指標を表示する

クラスタ内の DNS クエリに関する指標は、kube-dns Pod から直接取得できます。

  1. kube-system Namespace で kube-dns Pod を見つけます。

    kubectl get pods -n kube-system --selector=k8s-app=kube-dns
    

    出力は次のようになります。

    NAME                        READY     STATUS    RESTARTS   AGE
    kube-dns-548976df6c-98fkd   4/4       Running   0          48m
    kube-dns-548976df6c-x4xsh   4/4       Running   0          47m
    
  2. Pod のいずれかを選択し、その Pod から指標にアクセスするようにポート転送を設定します。

    • ポート 10055kube-dns 指標を公開します。
    • ポート 10054dnsmasq 指標を公開します。

    POD_NAME は、選択した Pod の名前に置き換えます。

    POD_NAME="kube-dns-548976df6c-98fkd" # Replace with your pod name
    kubectl port-forward pod/${POD_NAME} -n kube-system 10055:10055 10054:10054
    

    出力は次のようになります。

    Forwarding from 127.0.0.1:10054 -> 10054
    Forwarding from 127.0.0.1:10055 -> 10055
    
  3. 新しいターミナル セッションで、curl コマンドを使用して指標エンドポイントにアクセスします。

    # Get kube-dns metrics
    curl http://127.0.0.1:10055/metrics
    
    # Get dnsmasq metrics
    curl http://127.0.0.1:10054/metrics
    

    出力は次のようになります。

    kubedns_dnsmasq_errors 0
    kubedns_dnsmasq_evictions 0
    kubedns_dnsmasq_hits 3.67351e+06
    kubedns_dnsmasq_insertions 254114
    kubedns_dnsmasq_max_size 1000
    kubedns_dnsmasq_misses 3.278166e+06
    

次のステップ