DNS是Kubernetes集群中至关重要的基础服务之一,在客户端设置不合理、集群规模较大等情况下DNS容易出现解析超时、解析失败等现象。本文介绍Kubernetes集群中DNS的最佳实践,帮助您避免此类问题。

背景信息

DNS最佳实践包含客户端和服务端的内容:

有关CoreDNS的更多信息,请参见CoreDNS官方文档

优化域名解析请求

DNS域名解析请求是Kubernetes最高频的网络行为之一,其中很多请求是可以优化和避免的。您可以通过以下方式优化域名解析请求:
  • (推荐)使用连接池:当一个容器应用需要频繁请求另一服务时,推荐使用连接池。连接池可以将请求上游服务的链接缓存在内存中,避免每次访问时域名解析和TCP建连的开销。
  • 使用DNS缓存:
    • (推荐)当您的应用无法改造成通过连接池连接另一服务时,可以考虑在应用侧缓存DNS解析结果,具体操作,请参见使用节点DNS缓存NodeLocal DNSCache
    • 如果NodeLocal DNSCache无法适用的,可以在容器内置NSCD(Name Service Cache Daemon)缓存。关于如何使用NSCD缓存,请参见在Kubernetes集群中使用NSCD
  • 优化resolv.conf文件:由于resolv.conf文件中ndotssearch两个参数的机制作用,容器内配置域名的不同写法决定了域名解析的效率,关于ndotssearch两个参数的机制详情,请参见DNS原理和配置说明
  • 优化域名配置:当容器内应用需要访问某域名时,该域名按以下原则配置,可以最大程度减少域名解析尝试次数,继而减少域名解析耗时。
    • Pod访问同命名空间的Service,优先使用<service-name>访问,其中service-name代指Service名称。
    • Pod跨命名空间访问Service,优先使用<service-name>.<namespace-name>访问,其中namespace-name代指Service所处的命名空间。
    • Pod访问集群外部域名时,优先使用FQDN类型域名访问,这类域名通过常见域名最后加半角句号(.)的方式来指定地址,可以避免search搜索域拼接带来的多次无效搜索,例如需要访问www.aliyun.com,则优先使用FQDN类型域名www.aliyun.com.来访问。

使用合适的容器镜像

Alpine容器镜像内置的musl libc库与标准glibc的实现存在以下差异:
  • 3.3及更早版本Alpine不支持search参数,不支持搜索域,无法完成服务发现。
  • 并发请求/etc/resolv.conf中配置的多个DNS服务器,导致NodeLocal DNSCache优化失效。
  • 并发使用同一Socket请求A和AAAA记录,在旧版本内核上触发Conntrack源端口冲突导致丢包问题。
关于以上问题的更多信息,请参见musl libc

当Kubernetes集群中部署的容器采用了Alpine作为基础镜像时,可能会因为上述musl libc特性而无法正常解析域名,建议尝试更换基础镜像,如Debian、CentOS等。

避免IPVS缺陷导致的DNS概率性解析超时问题

当集群使用IPVS作为kube-proxy负载均衡模式时,您可能会在CoreDNS缩容或重启时遇到DNS概率性解析超时的问题。该问题由社区Linux内核缺陷导致,具体信息,请参见IPVS

您可以通过以下任意方式降低IPVS缺陷的影响:

使用节点DNS缓存NodeLocal DNSCache

在ACK集群中部署NodeLocal DNSCache可以提升服务发现的稳定性和性能,NodeLocal DNSCache通过在集群节点上作为DaemonSet运行DNS缓存代理来提高集群DNS性能。

关于更多NodeLocal DNSCache的介绍及如何在ACK集群中部署NodeLocal DNSCache的具体步骤,请参见使用NodeLocal DNSCache

使用合适的CoreDNS版本

CoreDNS对Kubernetes版本实现了较好的向后兼容,建议您保持CoreDNS版本为较新的稳定版本。ACK组件管理中心提供了CoreDNS的安装、升级能力,您可以关注组件管理中组件状态,若CoreDNS组件显示可升级,请尽快选择业务低峰期进行升级。
CoreDNS v1.7.0以下的版本存在风险隐患,包括且不仅限于以下:
不同Kubernetes集群推荐的CoreDNS版本如下表:
Kubernetes版本 推荐CoreDNS版本
v1.14.8以下(停止维护) v1.6.2
v1.14.8及以上,1.20.4以下 v1.7.0.0-f59c03d-aliyun
1.20.4及以上 v1.8.4.1-3a376cc-aliyun
说明 Kubernetes 1.14.8以下的版本现已停止维护,请尽快升级至更高版本后,升级CoreDNS。

监控CoreDNS运行状态

监控指标

CoreDNS通过标准的Promethues接口暴露出解析结果等健康指标,第一时间发现CoreDNS服务端甚至上游DNS服务器的异常。

阿里云应用实时监控服务ARMS Promethues监控默认内置了CoreDNS相关的指标监控和告警规则,您可以在容器服务管理控制台开启Prometheus及仪表盘功能。具体操作,请参见ARMS Prometheus监控

若您是自建Prometheus监控Kubernetes集群,可以在Prometheus观测相关指标并对以下重点指标设置告警。具体操作,请参见CoreDNS Prometheus官方文档。重点指标如下:
指标类型 指标说明 告警设置
coredns_dns_requests_total 请求次数 可针对总量进行告警,判断当前域名解析QPS是否过高。
coredns_dns_responses_total 响应次数 可针对不同状态码RCODE的响应次数进行告警,例如服务端异常SERVFAIL出现时,可进行告警。
coredns_panics_total CoreDNS程序异常退出的次数 大于0则说明异常发生,应进行告警。
coredns_dns_request_duration_seconds 域名解析延迟 延迟过高时应进行告警。

运行日志

在DNS异常发生的情况下,CoreDNS日志有助于您快速诊断异常根因。建议您开启CoreDNS域名解析日志和其SLS日志采集,具体操作,请参见分析和监控CoreDNS日志

合理调整集群CoreDNS部署状态

CoreDNS应部署于您的Kubernetes集群中,默认情况下与您的业务容器运行在同样的集群节点上,注意事项如下:

合理调整CoreDNS副本数

建议您在任何情况下设置CoreDNS副本数应至少为2,且副本数维持在一个合适的水位以承载整个集群的解析。

CoreDNS所能提供的域名解析QPS与CPU消耗成正相关,开启缓存的情况下,单个CPU可以支撑10000+ QPS的域名解析请求。不同类型的业务对域名请求的QPS需求存在较大差异,您可以观察每个CoreDNS副本的峰值CPU使用量,如果其在业务峰值期间占用CPU大于一核,建议您对CoreDNS进行副本扩容。无法确定峰值CPU使用量时,可以保守采用副本数和集群节点数1:8的比值来部署,即每扩容8个集群节点,增加一个CoreDNS副本,但副本数不应大于10。针对100节点以上的集群,推荐使用节点DNS缓存NodeLocal DNSCache。具体操作,请参见使用节点DNS缓存NodeLocal DNSCache

说明 UDP报文缺少重传机制,在CoreDNS副本停止过程中,CoreDNS任意副本缩容或重启可能会导致接收到的UDP报文丢失,触发整个集群域名解析超时或异常。当集群节点存在IPVS UDP缺陷导致的丢包风险时,CoreDNS任意副本缩容或重启可能会导致长达五分钟的整个集群域名解析超时或异常。关于IPVS缺陷导致解析异常的解决方案,请参见IPVS缺陷导致解析异常

合理分配CoreDNS副本运行的位置

建议您在部署CoreDNS副本时,应将CoreDNS副本打散在不同可用区、不同集群节点上,避免单节点、单可用区故障。CoreDNS默认配置了按节点的弱反亲和性,可能会因为节点资源不足导致部分或全部副本部署在同一节点上,如果遇到这种情况,请删除Pod重新触发其调度来调整。

CoreDNS所运行的集群节点应避免CPU、内存用满的情况,否则会影响域名解析的QPS和响应延迟。

手动扩容副本数

当集群节点数长时间较为固定时,您可以通过以下命令扩容CoreDNS副本数。
kubectl scale --replicas={target} deployment/coredns -n kube-system
说明 将目标副本数{target}设置成目标值。

自动扩容副本数(cluster-autoscaler)

当集群节点数不断增长时,您可以通过以下YAML部署集群水平伸缩器cluster-proportional-autoscaler动态扩容副本数量:
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dns-autoscaler
  namespace: kube-system
  labels:
    k8s-app: dns-autoscaler
spec:
  selector:
    matchLabels:
      k8s-app: dns-autoscaler
  template:
    metadata:
      labels:
        k8s-app: dns-autoscaler
    spec:
      serviceAccountName: admin
      containers:
      - name: autoscaler
        image: registry.cn-hangzhou.aliyuncs.com/acs/cluster-proportional-autoscaler:1.8.4
        resources:
          requests:
            cpu: "200m"
            memory: "150Mi"
        command:
        - /cluster-proportional-autoscaler
        - --namespace=kube-system
        - --configmap=dns-autoscaler
        - --nodelabels=type!=virtual-kubelet
        - --target=Deployment/coredns
        - --default-params={"linear":{"coresPerReplica":64,"nodesPerReplica":8,"min":2,"max":100,"preventSinglePointFailure":true}}
        - --logtostderr=true
        - --v=9
上述使用线程伸缩策略中,CoreDNS副本数的计算公式为replicas = max (ceil (cores × 1/coresPerReplica), ceil (nodes × 1/nodesPerReplica) ),且CoreDNS副本数受到maxmin限制。线程伸缩策略参数如下。
{
      "coresPerReplica": 64,
      "nodesPerReplica": 8,
      "min": 2,
      "max": 100,
      "preventSinglePointFailure": true
}

基于CPU负载指标自动扩容副本数(HPA)

由于HPA会频繁触发CoreDNS副本缩容,建议您不要使用容器水平扩缩容(HPA),如果您的场景下必须依赖于HPA,请参考以下基于CPU负载的策略配置:
---
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
  name: coredns-hpa
  namespace: kube-system
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: coredns
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      targetAverageUtilization: 50
说明 关于HPA使用方式的更多信息,请参见容器水平伸缩(HPA)

合理配置CoreDNS

容器服务ACK仅提供CoreDNS的默认配置,您应关注配置中的各个参数、优化其配置,以使CoreDNS可以为您的业务容器正常提供DNS服务。CoreDNS的配置非常灵活,具体操作,请参见DNS原理和配置说明CoreDNS官方文档

早期版本随Kubernetes集群部署的CoreDNS默认配置可能存在一些风险,推荐您按以下方式检查和优化:

关闭kube-dns服务的亲和性配置

亲和性配置可能导致CoreDNS不同副本间存在较大负载差异,建议按以下步骤关闭:
  • 控制台操作方式
    1. 登录容器服务管理控制台
    2. 在控制台左侧导航栏中,单击集群
    3. 集群列表页面中,单击目标集群名称或者目标集群右侧操作列下的详情
    4. 在集群管理页左侧导航栏中,选择网络 > 服务
    5. 在kube-system命名空间下,单击服务kube-dns右侧的查看YAML
      • 如果发现sessionAffinity字段为None,则无需进行以下步骤。
      • 如果发现sessionAffinityClientIP,则进行以下步骤。
    6. 删除sessionAffinitysessionAffinityConfig及所有子键,然后单击更新
      #删除以下所有内容。
      sessionAffinity: ClientIP
        sessionAffinityConfig:
          clientIP:
            timeoutSeconds: 10800
    7. 再次单击服务kube-dns右侧的查看YAML,校验sessionAffinity字段是否为None,为None则Kube-DNS服务变更成功。
  • 命令行操作方式
    1. 执行以下命令查看kube-dns服务配置信息。
      kubectl -n kube-system get svc kube-dns -o yaml
      • 如果发现sessionAffinity字段为None,则无需执行以下步骤。
      • 如果发现sessionAffinityClientIP,则执行以下步骤。
    2. 执行以下命令打开并编辑名为kube-dns的服务。
      kubectl -n kube-system edit service kube-dns
    3. 删除sessionAffinity相关设置(sessionAffinitysessionAffinityConfig及所有子键),并保存退出。
      #删除以下所有内容。
      sessionAffinity: ClientIP
        sessionAffinityConfig:
          clientIP:
            timeoutSeconds: 10800
    4. 修改完成后,再次运行以下命令查看sessionAffinity字段是否为None,为None则Kube-DNS服务变更成功。
      kubectl -n kube-system get svc kube-dns -o yaml

关闭Autopath插件

部分早期版本的CoreDNS开启了Autopath插件,该插件在一些极端场景下会导致解析结果出错,请确认其是否处于开启状态,并编辑配置文件将其关闭。更多信息,请参见#3765

说明 关闭Autopath插件后,客户端发起的域名解析请求QPS最高会增加3倍,解析单个域名耗时最高增加3倍,请关注CoreDNS负载和业务影响。
  1. 执行kubectl -n kube-system edit configmap coredns命令,打开CoreDNS配置文件。
  2. 删除autopath @kubernetes一行后保存退出。
  3. 检查CoreDNS Pod运行状态和运行日志,运行日志中出现reload字样后说明修改成功。

配置CoreDNS优雅退出

说明 CoreDNS刷新配置过程中,可能会占用额外内存。修改CoreDNS配置项后,请观察Pod运行状态,如果出现Pod内存不足的情况,请及时修改CoreDNS Deployment中容器内存限制,建议将内存调整至2 GB。
  • 控制台操作方式
    1. 登录容器服务管理控制台
    2. 在控制台左侧导航栏中,单击集群
    3. 集群列表页面中,单击目标集群名称或者目标集群右侧操作列下的详情
    4. 在集群管理页左侧导航栏中,选择配置管理 > 配置项
    5. 在kube-system命名空间下,单击配置项coredns右侧的YAML编辑
    6. 参考下列的YAML文件,保证health插件开启,并调整lameduck参数为15s,然后单击确定
      .:53 {
              errors       
              #health插件在不同的CoreDNS版本中可能有不同的设置情形。
              #情形一:默认未启用health插件。   
              #情形二:默认启用health插件,但未设置lameduck时间。
              health      
              #情形三:默认启用health插件,设置了lameduck时间为5s。   
              health {
                  lameduck 5s
              }      
              #对于以上三种情形,应统一修改成以下,调整lameduck参数为15s。
              health {
                  lameduck 15s
              }       
              #其它插件不需要修改,此处省略。
          }

    如果CoreDNS Pod正常运行则说明CoreDNS优雅退出的配置更新成功。如果CoreDNS Pod出现异常,可以通过查看Pod事件及日志定位原因。

  • 命令行操作方式
    1. 执行以下命令打开CoreDNS配置文件。
      kubectl -n kube-system edit configmap coredns
    2. 参考下列的YAML文件,保证health插件开启,并调整lameduck参数为15s
      .:53 {
              errors     
              #health插件在不同的CoreDNS版本中可能有不同的设置情形。
              #情形一:默认未启用health插件。     
              #情形二:默认启用health插件,但未设置lameduck时间。
              health
              #情形三:默认启用health插件,设置了lameduck时间为5s。   
              health {
                  lameduck 5s
              }
              #对于以上三种情形,应统一修改成以下,调整lameduck参数为15s。
              health {
                  lameduck 15s
              }
              #其它插件不需要修改,此处省略。
          }
    3. 修改CoreDNS配置文件后保存退出。

      如果CoreDNS正常运行则说明CoreDNS优雅退出的配置更新成功。如果CoreDNS Pod出现异常,可以通过查看Pod事件及日志定位原因。

配置Forward插件与上游VPC DNS服务器的默认协议

NodeLocal DNSCache采用TCP协议与CoreDNS进行通信,CoreDNS会根据请求来源使用的协议与上游DNS服务器进行通信。因此默认情况下,来自业务容器的集群外部域名解析请求会依次经过NodeLocal DNSCache、CoreDNS,最终以TCP协议请求VPC内DNS服务器,即ECS上默认配置的100.100.2.136和100.100.2.13两个 IP。

VPC内DNS服务器对TCP协议支持有限,如果您使用了NodeLocal DNSCache,您需要修改CoreDNS配置,让其总是优先采用UDP协议与上游DNS服务器进行通信,避免解析异常。建议您使用以下方式修改CoreDNS配置文件,即修改命名空间kube-system下名为coredns的ConfigMap。在forward插件中指定请求上游的协议为perfer_udp,修改之后CoreDNS会优先使用UDP协议与上游通信。修改方式如下所示:
#修改前
forward . /etc/resolv.conf
#修改后
forward . /etc/resolv.conf {
  prefer_udp
}