当前位置: 首页 > article >正文

Coredns延迟NodeLocalDNS解决之道

#作者:邓伟

文章目录

  • 问题列表
  • 问题分析:
  • 问题分析
  • 解决方案详情
  • 方案验证
  • 部署步骤
  • 验证结论
  • 回滚方案
  • 回滚验证
  • 注意事项
  • NodeLocalDNS介绍

问题列表

近来发现K8s频繁出现5s超时问题,业务反馈收到一定影响,问题包括:

  1. coredns解析报5s延迟
    如图
    在这里插入图片描述
  2. 优化配置后,延迟有所缓解,但仍然存在5s延迟情况。

问题分析:

通过进一步查看和分析,磐基dns解析组件为coredns,支持ipv4和ipv6解析,而linux 中 glibc 的 resolver 的缺省超时时间是 5s,而导致超时的原因是内核 conntrack 模块的 bug,详解分析:

https://www.weave.works/blog/racy-conntrack-and-dns-lookup-timeouts,详细说明如下

DNS client (glibc 或 musl libc) 会并发请求 A 和 AAAA 记录,跟 DNS Server 通信自然会先 connect (建立 fd),后面请求报文使用这个 fd 来发送,由于 UDP 是无状态协议, connect 时并不会发包,也就不会创建 conntrack 表项, 而并发请求的 A 和 AAAA 记录默认使用同一个 fd 发包,send 时各自发的包它们源 Port 相同(因为用的同一个 socket 发送),当并发发包时,两个包都还没有被插入 conntrack 表项,所以 netfilter 会为它们分别创建 conntrack 表项,而集群内请求 kube-dns 或 coredns 都是访问的 CLUSTER-IP,报文最终会被 DNAT 成一个 endpoint 的 POD IP,当两个包恰好又被 DNAT 成同一个 POD IP 时,它们的五元组就相同了,在最终插入的时候后面那个包就会被丢掉,如果 dns 的 pod 副本只有一个实例的情况就很容易发生(始终被 DNAT 成同一个 POD IP),现象就是 dns 请求超时,client 默认策略是等待 5s 自动重试,如果重试成功,我们看到的现象就是 dns 请求有 5s 的延时。

问题分析

通过进一步查看和分析发现:

在使用 DNS 客户端(如 glibc 或 musl libc)时,客户端会并发请求 A 记录和 AAAA 记录,并通过同一个 socket 文件描述符(fd)发送 UDP 报文。

由于 UDP 是无状态协议,connect 不会实际发包,因此初始时不会创建 conntrack 表项。当并发请求的 A 和 AAAA 记录使用同一个 fd 发送时,它们的源端口相同,且在发送时都未被插入 conntrack 表项,导致 netfilter 会为它们分别创建表项。

在 Kubernetes 集群中,DNS 请求访问 Cluster IP 后会被 DNAT 成具体的 Endpoint Pod IP。如果两个并发请求的包被 DNAT 成同一个 Pod IP,它们的五元组(源 IP、源端口、目标 IP、目标端口、协议)将完全相同,导致后一个包在插入 conntrack 表项时被丢弃。

当 DNS 服务的 Pod 只有一个实例时,这种情况尤为常见,最终表现为 DNS 请求超时。客户端默认会等待 5 秒后重试,若重试成功,则会观察到 5 秒的延迟

解决方案详情

针对于以上问题和分析,可选的解决具体方案和优缺点如下:

  1. 将client dns请求由udp转换为tcp,避免出现conntrack 引发的问题,但是性能明显下降;

  2. 避免相同五元组 DNS 请求的并发,即配置single-request-reopen 或single-request ,可以显著降低超时,但是改动较大影响面较广;

需要说明的是1和2:不支持 alpine 基础镜像的容器,因为 apline 底层使用的 musl libc 库并不支持这些 resolv.conf 选项,所以如果使用 alpine 基础镜像构建的应用,还是无法规避超时的问题。

  1. 本地 DNS 缓存,也即:本地 DNS 缓存以 DaemonSet 方式在每个节点部署一个使用 hostNetwork 的 Pod,创建一个网卡绑上本地 DNS 的 IP,本机的 Pod 的 DNS 请求路由到本地 DNS,然后取缓存或者继续使用 TCP 请求上游集群 DNS 解析 (由于使用 TCP,同一个 socket 只会做一遍三次握手,不存在并发创建 conntrack 表项,也就不会有 conntrack 冲突),也即:NodeLocalDNS,详见文末介绍。

综合分析优缺点以及实际影响面,从影响和改动成本以及长期稳定性等因素考虑,选择第3项解决此问题,可做到部署即生效(k8s集群为iptables模式)。

方案验证

针对以上提出的问题,本方案有以下内容组成如下:

  1. 部署NodeLocalDNS

按照官方提供的K8S部署配置((根据k8s版本选择,磐基为1.23))参见:https://github.com/kubernetes/kubernetes/blob/release-1.23/cluster/addons/dns/nodelocaldns/nodelocaldns.yaml):
镜像地址:k8s.gcr.io/dns/k8s-dns-node-cache:1.21.1;
整理后完整内容为:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: node-local-dns
  namespace: kube-system
  labels:
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
---
apiVersion: v1
kind: Service
metadata:
  name: kube-dns-upstream
  namespace: kube-system
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
    kubernetes.io/name: "KubeDNSUpstream"
spec:
  ports:
  - name: dns
    port: 53
    protocol: UDP
    targetPort: 53
  - name: dns-tcp
    port: 53
    protocol: TCP
    targetPort: 53
  selector:
    k8s-app: kube-dns
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: node-local-dns
  namespace: kube-system
  labels:
    addonmanager.kubernetes.io/mode: Reconcile
data:
  Corefile: |
    cluster.local:53 {
        errors
        cache {
                success 9984 30
                denial 9984 5
        }
        reload
        loop
        bind 169.254.20.10 169.169.0.100
        forward . __PILLAR__CLUSTER__DNS__ {
                force_tcp
        }
        prometheus :9253
        health 169.254.20.10:<设定监控检查端口,确保不宿主机没占用>
        }
    in-addr.arpa:53 {
        errors
        cache 30
        reload
        loop
        bind 169.254.20.10 169.169.0.100
        forward . __PILLAR__CLUSTER__DNS__ {
                force_tcp
        }
        prometheus :9253
        }
    ip6.arpa:53 {
        errors
        cache 30
        reload
        loop
        bind 169.254.20.10 169.169.0.100
        forward . __PILLAR__CLUSTER__DNS__ {
                force_tcp
        }
        prometheus :9253
        }
    .:53 {
        errors
        cache 30
        reload
        loop
        bind 169.254.20.10 169.169.0.100
        forward . __PILLAR__CLUSTER__DNS__
        prometheus :9253
        }
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-local-dns
  namespace: kube-system
  labels:
    k8s-app: node-local-dns
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
spec:
  updateStrategy:
    rollingUpdate:
      maxUnavailable: 10%
  selector:
    matchLabels:
      k8s-app: node-local-dns
  template:
    metadata:
      labels:
        k8s-app: node-local-dns
      annotations:
        prometheus.io/port: "9253"
        prometheus.io/scrape: "true"
    spec:
      priorityClassName: system-node-critical
      serviceAccountName: node-local-dns
      hostNetwork: true
      dnsPolicy: Default  # Don't use cluster DNS.
      tolerations:
      - key: "CriticalAddonsOnly"
        operator: "Exists"
      - effect: "NoExecute"
        operator: "Exists"
      - effect: "NoSchedule"
        operator: "Exists"
      containers:
      - name: node-cache
        image: <可拉取的image地址>
        resources:
          requests:
            cpu: 25m
            memory: 5Mi
        args: [ "-localip", "169.254.20.10,169.169.0.100", "-conf", "/etc/Corefile", "-upstreamsvc", "kube-dns-upstream" ]
        securityContext:
          privileged: true
        ports:
        - containerPort: 53
          name: dns
          protocol: UDP
        - containerPort: 53
          name: dns-tcp
          protocol: TCP
        - containerPort: 9253
          name: metrics
          protocol: TCP
        livenessProbe:
          httpGet:
            host: 169.254.20.10
            path: /health
            port: <配置文件设置的健康检查端口>
          initialDelaySeconds: 60
          timeoutSeconds: 5
        volumeMounts:
        - mountPath: /run/xtables.lock
          name: xtables-lock
          readOnly: false
        - name: config-volume
          mountPath: /etc/coredns
        - name: kube-dns-config
          mountPath: /etc/kube-dns
      volumes:
      - name: xtables-lock
        hostPath:
          path: /run/xtables.lock
          type: FileOrCreate
      - name: kube-dns-config
        configMap:
          name: kube-dns
          optional: true
      - name: config-volume
        configMap:
          name: node-local-dns
          items:
            - key: Corefile
              path: Corefile.base
---
# A headless service is a service with a service IP but instead of load-balancing it will return the IPs of our associated Pods.
# We use this to expose metrics to Prometheus.
apiVersion: v1
kind: Service
metadata:
  annotations:
    prometheus.io/port: "9253"
    prometheus.io/scrape: "true"
  labels:
    k8s-app: node-local-dns
  name: node-local-dns
  namespace: kube-system
spec:
  clusterIP: None
  ports:
    - name: metrics
      port: 9253
      targetPort: 9253
  selector:
k8s-app: node-local-dns

以上假设

  1. kube-dns服务的ip地址为:169.169.0.100,且保证pod 的nameserver为169.169.0.100,否则,请按kube-dns服务实际ip更改;
  2. 部署前请检查169.254.20.10有无被占用,如有,可跟换169.254.20.10/24网段其他未被占用的ip地址

部署步骤

  1. 确定image,ip以及端口后,将以上yaml保存为nodelocaldns.yaml(文件名可以自定义);

  2. 在磐基管理机上部署执行:
    kubectl apply -f nodelocaldns.yaml
    执行输出以下内容,则表示创建和部署成功成功:
    在这里插入图片描述
    在这里插入图片描述
    Node-cache 进程监听相应ip端口(53,9253,9353,28080),优先接收响应流量,进而代理coredns解析,起到被动缓存的作用
    在这里插入图片描述
    Pod node-local日志
    在这里插入图片描述
    在这里插入图片描述
    以上说明,nodelocaldns在当前宿主机正常监听169.169.0.100流量 ,即代理kube-dns的解析转发到新的kube-dns-upstream服务(代理coredns服务,详解配置文件)。

验证结论

正常接收dns域名解析请求:

在这里插入图片描述
在这里插入图片描述
性能验证,经过多轮,每轮至少5次压测脚本执行,平均以及99耗时已有明显提升:
在这里插入图片描述
至此 nodelocaldns部署完成,正常提供解析功能。

回滚方案

相对应地,如果正常下线nodelocaldns(不可强制下线),则可以执行:
kubectl delete -f nodelocaldns.yaml
在这里插入图片描述
下线成功:
在这里插入图片描述

回滚验证

执行上述回滚命令后,宿主机已无node-cache服务监听 169.169.0.100和169.254.20.10的相关端口了:
在这里插入图片描述
且 iptable kube-dns规则正常生效:
在这里插入图片描述
回滚后,原coredns正常解析:
在这里插入图片描述
表明:回滚完成,nodelocaldns已正常下线

注意事项

  1. 确保所用ip以及相应端口不被占用,以免端口或ip冲突,造成启动失败或者启动后运行异常;
  2. 确保镜像为官方拉取的镜像,并部署时可正常拉取;
  3. 资源使用最小配置(request):cpu 35m,memory 30Mi,最大限制(limit)根据实际情况可适当放宽;
  4. 确保执行正常部署和混滚命令,避免强制删除,并注意部署和回滚验证;
  5. 确保尽可能接入监控平台并配置,并配置报警
  6. 确保宿主机有足够的pod额度

NodeLocalDNS介绍

NodeLocal DNSCache 通过在集群节点上运行一个 DaemonSet 来提高 clusterDNS 性能和可靠性。
处于 ClusterFirst 的 DNS 模式下的 Pod 可以连接到 kube-dns 的 serviceIP 进行 DNS 查询。通过 kube-proxy 组件添加的 iptables 规则将其转换为 CoreDNS 端点。通过在每个集群节点上运行 DNS 缓存,NodeLocal DNSCache 可以缩短 DNS 查找的延迟时间、使 DNS 查找时间更加一致,以及减少发送到 kube-dns 的 DNS 查询次数,流程示意图如下:
在这里插入图片描述
参考链接
1、https://github.com/kubernetes/kubernetes/blob/release-1.23/cluster/addons/dns/nodelocaldns/nodelocaldns.yaml
2、https://kubernetes.io/zh-cn/docs/tasks/administer-cluster/nodelocaldns/
3、案例:https://www.ctyun.cn/document/10025153/10110270
4、问题剖析:https://www.weave.works/blog/racy-conntrack-and-dns-lookup-timeouts
5、附件物料可咨询博主


http://www.kler.cn/a/566159.html

相关文章:

  • ES6笔记总结
  • Microsoft.Office.Interop.Excel 的简单操作
  • HTTP 请求时传递多部分表单数据
  • 2025年光电科学与智能传感国际学术会议(ICOIS 2025)
  • AI技术为旅行社打开新流量入口
  • 探究高空视频全景AR技术的实现原理
  • 【湖北省计算机信息系统集成协会主办,多高校支持 | ACM出版,EI检索,往届已见刊检索】第二届边缘计算与并行、分布式计算国际学术会议(ECPDC 2025)
  • mysql5.7离线安装及问题解决
  • Flink与Spark对比:大数据领域的“双雄争霸
  • 二、QT和驱动模块实现智能家居----2、编译支持QT的系统
  • MySQL 数据库安全配置最佳实践
  • DeepSeek能画流程图吗?分享一种我正在使用的DeepSeek画流程图教程
  • 11.【线性代数】——矩阵空间,秩1矩阵,小世界图
  • ESP 32控制无刷电机2
  • 【折线图 Line】——1
  • 通过百度构建一个智能体
  • Docker容器日常维护常用命令大全
  • cs224w课程学习笔记-第5课
  • 清华大学 Deepseek学习手册-第五弹pdf
  • Maven 插件的使用(一)