亚马逊AWS官方博客

Amazon EKS 集群 DNS 性能优化实践:构建多层 DNS 网络

背景

在现代云原生架构中,DNS 服务扮演着至关重要的角色,而 CoreDNS 作为一款高度可定制且功能强大的 DNS 服务器,已成为 Kubernetes 生态系统中的标准配置。Amazon EKS(Elastic Kubernetes Service)在创建新集群时,会自动部署 CoreDNS 作为集群内部的 DNS 解析器。 默认情况下,无论您的 EKS 集群规模如何,系统都会初始化两个 CoreDNS 实例。这种配置旨在为集群中的所有 Pod 提供基本的名称解析服务。然而,随着集群规模的扩大和应用复杂度的提升,这一默认设置可能会面临挑战。 在高负载场景下,特别是当 DNS 查询量激增时,用户可能会遇到一系列性能问题。这些问题可能表现为 DNS 解析延迟增加、查询超时,甚至解析失败。这不仅会影响应用的响应速度,还可能导致服务中断或不稳定。 鉴于 DNS 服务对于集群整体健康和性能的重要性,优化 EKS 集群的 DNS 性能成为了一个关键任务,本文将深入探讨如何通过 Node Local DNS Cache、CoreDNS 和 Route53 等组件来构建多层 DNS 网络,以显著提升 EKS 集群的域名解析能力。

多层 DNS 的重要性

在大规模分布式系统中,DNS 查询的数量可能会达到惊人的规模。一个设计不当的 DNS 系统可能会成为整个应用架构的瓶颈,导致服务发现延迟、应用响应时间增加,甚至造成系统不稳定。多层 DNS 系统通过在不同层级优化 DNS 解析过程,可以有效地:

  1. 减少对中心化 DNS 服务器的压力
  2. 降低 DNS 查询延迟
  3. 提高 DNS 解析的成功率
  4. 增强系统的可扩展性和弹性

解决方案

构建多层 DNS 网络

  • 首先,使用 Node Local DNS Cache 作为 DNS 第一层缓存,将 Pod 内的流量在本地进行解析和,Node Local DNS Cache 通过 DaemonSet 在每个 EKS node 节点上部署一个 DNS 缓存代理 Pod,可以拦截 Pod 的 DNS 查询请求,Pod 的 DNS 查询首先到达本地缓存代理,如果缓存命中则直接返回结果,如果缓存未命中,代理会将请求转发到上游 kube-dns 服务,代理与 kube-dns 之间使用 TCP 连接,而不是默认的 UDP,通过这种本地缓存的机制,可以显著减少 DNS 查询延迟,提高性能并减少对集群 CoreDNS 服务的请求,以及 CoreDNS 所在 Node 的单网卡最大 1024 Package 限制。需要注意的是,使用 Node Local DNS 需要占用 node 节点上的额外资源。
  • 其次,使用 CoreDns 作为 DNS 第二层,默认情况下,无论您的 EKS 集群规模如何,系统都会初始化两个 CoreDNS 副本。这种配置旨在为集群中的所有 Pod 提供基本的名称解析服务。然而,随着集群规模的扩大和应用复杂度的提升,这一默认设置可能会面临挑战。 在高负载场景下,特别是当 DNS 查询量激增时,用户可能会遇到一系列性能问题,当您的集群管理超过 100 个节点时,我们建议将 CoreDNS 调度到单独的节点上,并且根据负载情况配置超过 2 个以上的副本数量,配置反亲和性来避免多个 CoreDNS 副本调度到同一个节点。
  • 最后,使用 Route53 作为 DNS 第三层,默认情况下,Amazon EKS 的 CoreDNS 部署后使用节点中/etc/resolv.conf 中配置的 nameserver 作为外部 DNS 解析服务器。在 VPC 中单个 EC2 的网卡的域名解析并发为 1024 个数据包,并且无法提高此限制,这样就导致单个 CoreDNS 的 pod 最大的外部 DNS 解析能力是 1024 QPS,除了增加更多的 CoreDNS 副本数量的方法外,我们还可以利用 Amazon Route53 resolver 提供的 Inbound endpoints 作为 CoreDNS 外部解析地址,突破 1024 QPS 的限制(每个 Amazon Route53 resolver 终端节点中单个 IP 地址的每秒查询数为 10000,可以增加更多的 IP 提高 QPS)。

其他优化项

监控 CoreDNS 相关指标

CoreDNS 作为 EKS 插件,在 kube-dns 服务中以 Prometheus 格式公开端口 9153 上 CoreDNS 的指标,您可以使用 Prometheus、Amazon CloudWatch agent,或任何其他工具来收集这些指标,在集群内部可以使用以下命令访问指标:

重点关注以下指标:
coredns_dns_requests_total: DNS 请求总数
coredns_dns_response_rcode_count_total: 按响应代码统计的 DNS 响应数
coredns_dns_request_duration_seconds: DNS 请求处理时间
coredns_cache_hits_total 和 coredns_cache_misses_total: 缓存命中和未命中次数

配置 Grafana Dashboard

https://github.com/monitoring-mixins/website/blob/master/assets/coredns/dashboards/coredns.json

调整合适的 CoreDNS 资源和副本数

由于 CoreDNS 副本在缩容或重启时可能会遇到 DNS 概率性解析超时的问题,我们并不建议使用 Horizontal Pod Autoscaling (HPA) 来进行自动扩缩,Amazon EKS 已经原生支持 CoreDNS 按照集群规模进行扩缩的 Cluster Proportional Autoscaling (CPA) 功能,通过此功能,您可以扩展 CoreDNS 以满足不断变化的服务容量需求,无需承担管理自定义解决方案产生的开销。

通过合适的反亲和性配置,避免 CoreDNS 调度到同一个节点:

podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: k8s-app
                operator: In
                values:
                - kube-dns
            topologyKey: kubernetes.io/hostname

AutoPath 插件

AutoPath 插件是 CoreDNS 中用于优化 Kubernetes 集群 DNS 查询性能的一个重要功能。它的技术实现原理主要是对域名进行智能识别和拼接,将客户端查询变成服务器端查询,AutoPath 插件能够智能识别经过 search 域拼接的 DNS 解析请求,Pod 进行 DNS 查询时通常会尝试多个 search 域,这会导致多次 DNS 查询。当 AutoPath 识别出一个查询可能涉及多个 search 域时,它会直接响应一个 CNAME 记录,并附上相应的 A 记录。这样可以在一次查询中返回所需的全部信息,减少了 DNS 查询的次数,提高了整个集群的 DNS 解析效率。

需要注意的是,启用 autopath 后,由于 coredns 需要 watch 所有的 pod,会增加 coredns 的内存消耗,根据情况适当调节 coredns 的 memory request 和 limit。

操作步骤

1. 配置 CoreDNS

确保 CoreDNS 已在 EKS 集群中正确部署,编辑 CoreDNS 的 ConfigMap

kubectl edit configmap coredns -n kube-system

启用 AutoPath 插件

apiVersion: v1
data:
  Corefile: |
    .:53 {
        errors
        health {
            lameduck 5s
          }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
          pods insecure
          fallthrough in-addr.arpa ip6.arpa
        }
        prometheus :9153
        forward . /etc/resolv.conf
        cache 30
        loop
        reload
        loadbalance
        autopath @kubernetes # 添加这一行
    }

验证启用状态

kubectl logs -n kube-system -l k8s-app=kube-dns

2. 部署 Node Local DNS Cache

下载 nodelocaldns.yaml,可参考官方文档:https://github.com/kubernetes/kubernetes/tree/master/cluster/addons/dns/nodelocaldns

wget https://raw.githubusercontent.com/kubernetes/kubernetes/refs/heads/master/cluster/addons/dns/nodelocaldns/nodelocaldns.yaml

替换集群参数

__PILLAR__DNS__SERVER__ - set to kube-dns service IP. #<===通过kubectl get svc kube-dns -n kube-system -o jsonpath={.spec.clusterIP}获取并替换 
__PILLAR__LOCAL__DNS__ - set to the link-local IP(169.254.20.10 by default). #<===无需变化 
__PILLAR__DNS__DOMAIN__ - set to the cluster domain(cluster.local by default). #<===无需变化
sed -i "s@__PILLAR__LOCAL__DNS__@$localdns@g; s@__PILLAR__DNS__DOMAIN__@$domain@g; s@__PILLAR__DNS__SERVER__@$kubedns@g" nodelocaldns.yaml

应用到集群

kubectl apply -f nodelocaldns.yaml

验证状态

kubectl get pods -n kube-system -l k8s-app=node-local-dns

3. 配置 Route53

  1. 在 AWS 控制台创建一个新的 Route53 托管区域或使用现有的 Hosted zones
  2. 创建必要的 DNS 记录以及 Route53 resolver inbound endpoints

4. 集成 Amazon Route53 resolver

apiVersion: v1
data:
  Corefile: |
    .:53 {
        errors
        health {
            lameduck 5s
          }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
          pods insecure
          fallthrough in-addr.arpa ip6.arpa
        }
        prometheus :9153
        forward . 192.168.64.179 192.168.76.64
        cache 30
        loop
        reload
        loadbalance
        autopath @kubernetes
    }

其中 192.168.56.21 和 192.168.1.68 是 Amazon Route53 resolver 的入栈终端节点,需要注意使用 Amazon Route53 resolver 会有少量额外成本 https://aws.amazon.com/cn/route53/pricing/

附录

https://kubernetes.io/docs/tasks/administer-cluster/nodelocaldns/

https://coredns.io/plugins/autopath/

https://aws.amazon.com/about-aws/whats-new/2024/05/amazon-eks-native-support-autoscaling-coredns-pods/

本篇作者

陈汉卿

亚马逊云科技解决方案架构师,负责基于亚马逊云科技云计算方案的咨询、架构设计及落地,拥有多年移动互联网研发经验,在云原生微服务以及云迁移等方向有丰富的实践经验。

杨冬冬

亚马逊云科技资深容器解决方案架构师,在云原生领域深耕多年,拥有丰富的行业经验。