关于 GKE 的 kube-dns

如果您在 Standard 集群中运行应用,kube-dns 是默认的 DNS 提供程序,可帮助您实现服务发现和通信。本文档介绍了如何使用 kube-dns 管理 DNS,包括其架构、配置以及在 GKE 环境中优化 DNS 解析的最佳实践。

本文档适用于负责管理 GKE 中 DNS 的开发者、管理员和架构师。如需了解 Google Cloud中的常见角色和任务,请参阅常见的 GKE Enterprise 用户角色和任务

在开始之前,请确保您熟悉 Kubernetes 服务和一般 DNS 概念

了解 kube-dns 架构

kube-dns 在 GKE 集群内部运行,以实现 Pod 和服务之间的 DNS 解析。

下图展示了 Pod 如何与 kube-dns 服务互动:

图 1:图表显示了 Pod 如何将 DNS 查询发送到由 `kube-dns` Pod 支持的 `kube-dns` 服务。`kube-dns` Pod 处理内部 DNS 解析,并将外部查询转发到上游 DNS 服务器。

关键组件

kube-dns 包含以下关键组件:

  • kube-dns Pod:这些 Pod 运行 kube-dns 服务器软件。这些 Pod 的多个副本在 kube-system 命名空间中运行,可提供高可用性和冗余。
  • kube-dns 服务:这种类型为 ClusterIPKubernetes 服务会将 kube-dns Pod 分组,并将其公开为单个稳定的端点。ClusterIP 充当集群的 DNS 服务器,Pod 使用该服务器发送 DNS 查询。kube-dns 最多支持每个无头服务 1,000 个端点。
  • kube-dns-autoscaler:此 Pod 会根据集群的大小(包括节点数和 CPU 核心数)调整 kube-dns 副本的数量。这种方法有助于确保 kube-dns 可以处理不同的 DNS 查询负载。

内部 DNS 解析

当 Pod 需要解析集群网域内的 DNS 名称(例如 myservice.my-namespace.svc.cluster.local)时,会发生以下过程:

  1. Pod DNS 配置:每个节点上的 kubelet 会配置 Pod 的 /etc/resolv.conf 文件。此文件使用 kube-dns 服务的 ClusterIP 作为域名服务器。
  2. DNS 查询:Pod 向 kube-dns 服务发送 DNS 查询。
  3. 域名解析kube-dns 接收查询。它会在其内部 DNS 记录中查找相应的 IP 地址,并向 Pod 发出响应。
  4. 通信:然后,Pod 使用解析出的 IP 地址与目标服务进行通信。

外部 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 会将 Pod 配置为使用集群的内部 DNS 服务 kube-dns 作为其域名服务器。它还会填充文件中的搜索网域。借助这些搜索网域,您可以在 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 认为名称完全限定的阈值。如果名称包含五个或更多个点,则会被视为完全限定名称。

配置了 hostNetwork: true 设置的 Pod 会从主机继承其 DNS 配置,并且不会直接查询 kube-dns

自定义“kube-dns

kube-dns 提供强大的默认 DNS 解析。您可以根据具体需求调整其行为,例如提高解析效率或使用首选 DNS 解析器。通过修改 kube-system 命名空间中的 kube-dns ConfigMap,可以同时配置存根网域和上游域名服务器。

修改 kube-dns ConfigMap

如需修改 kube-dns ConfigMap,请执行以下操作:

  1. 打开 ConfigMap 进行修改:

    kubectl edit configmap kube-dns -n kube-system
    
  2. data 部分中,添加 stubDomainsupstreamNameservers 字段,以:

    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 ConfigMap 中添加了 stubDomains 部分。

此部分指定网域和相应的上游域名服务器。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 以使用自定义上游域名服务器来解析外部域名。此配置指示 kube-dns 将所有 DNS 请求(集群内部网域 [*.cluster.local] 的请求除外)转发到指定的上游服务器。您的自定义上游服务器可能无法解析 metadata.internal*.google.internal 等内部网域。如果您启用 Workload Identity Federation for GKE 或有依赖于这些网域的工作负载,请在 ConfigMap 中为 internal 添加桩网域。使用元数据服务器的 IP 地址 169.254.169.254 作为此桩网域的解析器。

管理自定义 kube-dns 部署

在标准 GKE 中,kube-dns 作为 Deployment 运行。自定义 kube-dns 部署是指,作为集群管理员,您可以控制部署并根据需要对其进行自定义,而不是使用 GKE 提供的默认部署。

自定义部署的原因

出于以下原因,请考虑自定义 kube-dns 部署:

  • 资源分配:针对 kube-dns Pod 调整 CPU 和内存资源,以优化 DNS 流量较高的集群中的性能。
  • 映像版本:使用特定版本的 kube-dns 映像或切换到其他 DNS 提供商(例如 CoreDNS)。
  • 高级配置:自定义日志记录级别、安全政策和 DNS 缓存行为。

自定义部署的自动扩缩

内置的 kube-dns-autoscaler 可与默认的 kube-dns 部署搭配使用。如果您创建自定义 kube-dns 部署,内置的自动扩缩器不会管理该部署。因此,您必须设置一个单独的自动扩缩器,专门用于监控和调整自定义 Deployment 的副本数量。此方法需要在集群中创建并部署您自己的自动扩缩器配置。

当您管理自定义部署时,需要负责其所有组件,例如确保自动扩缩器映像保持最新状态。使用过时的组件可能会导致性能下降或 DNS 故障。

如需详细了解如何配置和管理自己的 kube-dns 部署,请参阅设置自定义 kube-dns 部署

问题排查

如需了解如何排查 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-dnsupstreamNameservers 值的数量限制为 3 个。如果您定义的数量超过三个,Cloud Logging 会显示类似于以下内容的错误:

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

在这种情况下,kube-dns 会忽略 upstreamNameservers 配置,并继续使用之前的有效配置。如需解决此问题,请从 kube-dns ConfigMap 中移除多余的 upstreamNameservers

纵向扩容 kube-dns

在 Standard 集群中,您可以针对 nodesPerReplica 使用较低的值,以便在集群节点纵向扩容时创建更多 kube-dns Pod。我们强烈建议您为 max 字段设置明确的值,以确保 GKE 控制平面虚拟机 (VM) 不会因监控 Kubernetes API 的 kube-dns Pod 数量太多而不堪重负。

您可以将 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 个节点创建一个 kube-dns Pod。24 个节点的集群有 3 个副本,40 个节点的集群有 5 个副本。如果集群增加超过 120 个节点,kube-dns 副本数不会增加到超过 15(即 max 字段的值)。

为确保集群中 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 虚拟机或抢占式虚拟机上运行 kube-dns,这可能会导致意外的节点删除。
  • 查询量过高,超出 kube-dns Pod 中 dnsmasq 实例的容量。在 GKE 1.31 版及更高版本中,单个 kube-dns 实例最多可建立 200 个并发 TCP 连接;在 GKE 1.30 版及更低版本中,最多可建立 20 个并发 TCP 连接。

如需缩短 DNS 查找时间,请执行以下操作:

  • 避免在 Spot 虚拟机或抢占式虚拟机上运行 kube-dns 等关键系统组件。创建至少一个包含标准虚拟机且不包含 Spot 虚拟机或抢占式虚拟机的节点池。使用污点和容忍机制有助于确保关键工作负载调度到这些可靠的节点上。
  • 启用 NodeLocal DNSCache。NodeLocal DNSCache 直接在每个节点上缓存 DNS 响应,从而缩短延迟时间并减轻 kube-dns 服务的负载。如果您启用 NodeLocal DNSCache 并使用具有默认拒绝规则的网络政策,请添加一项政策,以允许工作负载向 node-local-dns Pod 发送 DNS 查询。
  • 纵向扩容 kube-dns
  • 请确保您的应用使用基于 dns.resolve* 的函数,而非基于 dns.lookup 的函数,因为 dns.lookup 是同步的。
  • 使用完全限定域名 (FQDN),例如 https://google.com./,而不是 https://google.com/

在 GKE 集群升级期间,由于控制平面组件(包括 kube-dns)的并发升级,可能会发生 DNS 解析失败。这些故障通常只会影响一小部分节点。在将集群升级应用到生产集群之前,请先在非生产环境中全面测试这些升级。

确保服务可被发现

kube-dns 只会为具有端点的服务创建 DNS 记录。如果某个 Service 没有端点,kube-dns 就不会为该 Service 创建 DNS 记录。

管理 DNS TTL 差异

如果 kube-dns 从具有较大或无限 TTL 的上游 DNS 解析器接收 DNS 响应,则会保留此 TTL 值。此行为可能会导致缓存条目与实际 IP 地址之间出现差异。

GKE 在特定控制平面版本(例如 1.21.14-gke.9100 及更高版本或 1.22.15-gke.2100 及更高版本)中解决了此问题。这些版本针对 TTL 较高的任何 DNS 响应,将 TTL 值上限设置为 30 秒。此行为类似于 NodeLocal DNSCache。

查看 kube-dns 个指标

您可以直接从 kube-dns Pod 中检索有关集群中 DNS 查询的指标。

  1. kube-system 命名空间中查找 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 中的指标:

    • 端口 10055 公开 kube-dns 指标。
    • 端口 10054 公开 dnsmasq 指标。

    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
    

后续步骤