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、附件物料可咨询博主

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/980113.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

由堆栈异常引发的一系列问题启发

文章目录 背景分析现象排查定位数据比对解决方案反思背景 背景:今天下午初始化经营体数据到预发环境,需要将生产环境的经营体表的数据同步到预发环境,同步结束后。再调用批量禁用接口,目的是将原来一级经营体【中国移动】下的所有经营体禁用掉,然后导入Excel文件将新的省…

[2/11]C#性能优化-不要使用空析构函数-每个细节都有示例代码

前言 在C#开发中&#xff0c;性能优化是提升系统响应速度和资源利用率的关键环节。 当然&#xff0c;同样是所有程序的关键环节。 通过遵循下述建议&#xff0c;可以有效地减少不必要的对象创建&#xff0c;从而减轻GC的负担&#xff0c;提高应用程序的整体性能。记住&#xf…

Llama-factory详细基础使用说明

最近很多小伙伴在使用llama-factory时遇到了不少问题&#xff0c;特别是基础不太好的童鞋&#xff0c;对各种参数或者webui界面的内容不理解&#xff0c;总是一会一个问题&#xff0c;网上大部分的教程&#xff0c;都只是教到能跑起来就行&#xff0c;对于细的调整没有做介绍&a…

excel单、双字节字符转换函数(中英文输入法符号转换)

在Excel中通常使用函数WIDECHAR和ASC来实现单、双字节字符之间的转换。其中 WIDECHAR函数将所有的字符转换为双字节&#xff0c;ASC函数将所有的字符转换为单字节 首先来解释一下单双字节的含义。单字节一般对应英文输入法的输入&#xff0c;如英文字母&#xff0c;英文输入法…

VS Code 如何搭建CC++开发环境

VS Code 如何搭建C/C开发环境 文章目录 VS Code 如何搭建C/C开发环境1. VS Code是什么2. VS Code的下载和安装2.1 下载和安装2.2 环境的介绍 3. VS Code配置C/C开发环境3.1 下载和配置MinGW-w64编译器套件3.2 安装C/C插件3.3 重启VS Code 4. 在VS Code上编写C语言代码并编译成功…

间隙波导阵列天线 学习笔记2 使用加载脊U型辐射层的一种Ka波段高效率圆极化卫星天线的发展

摘要&#xff1a; 本文提出了一种低剖面&#xff0c;高增益背腔槽阵列天线&#xff0c;基于RGW技术&#xff0c;工作在Ka频段。天线阵列包含两层。上层是一个U型槽阵列&#xff0c;在金属脊上腔体的位置上方&#xff0c;同时下层是RGW公共分布网络&#xff0c;给每个背腔槽等辐…

【软考-架构】1.1、计算机硬件-CPU校验码

GitHub地址&#xff1a;https://github.com/tyronczt/system_architect ✨资料&文章更新✨ 计算机硬件组成 中央处理单元 运算器 算术逻辑单元ALU&#xff08;实现对数据的算术和逻辑运算&#xff09;;累加寄存器AC&#xff08;运算结果或源操作数的存放区&#xff09;;数…

【Spring】Spring AOP原理

目录 前言 代理模式 静态代理 优缺点 动态代理 JDK动态代理 工作原理 JDK动态原理实现关键步骤 CGLib动态代理 CGLIB动态代理实现关键步骤 总结 前言 在上一篇中&#xff0c;我们讲解了什么是AOP&#xff0c;以及Spring AOP是如何使用的&#xff0c;那么本篇我们就…

SpringBoot五:JSR303校验

精心整理了最新的面试资料和简历模板&#xff0c;有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 松散绑定 意思是比如在yaml中写的是last-name&#xff0c;这个和lastName意思是一样的&#xff0c;-后的字母默认是大写的 JSR303校验 就是可以在字段增加…

uniapp 系统学习,从入门到实战(六)—— 样式与布局

全篇大概 4700 字(含代码)&#xff0c;建议阅读时间 30min &#x1f4da; 目录 Flex 布局在 UniApp 中的应用响应式设计与适配多端使用 SCSS 提升样式开发效率实战案例演示总结 1. Flex 布局在 UniApp 中的应用 1.1 基础布局实现 通过 display: flex 快速构建弹性容器&#…

Redis---缓存穿透,雪崩,击穿

文章目录 缓存穿透什么是缓存穿透&#xff1f;缓存穿透情况的处理流程是怎样的&#xff1f;缓存穿透的解决办法缓存无效 key布隆过滤器 缓存雪崩什么是缓存雪崩&#xff1f;缓存雪崩的解决办法 缓存击穿什么是缓存击穿&#xff1f;缓存击穿的解决办法 区别对比 在如今的开发中&…

从UNIX到Linux:操作系统进化史与开源革命

从UNIX到Linux&#xff1a;操作系统进化史与开源革命 一、操作系统&#xff1a;数字世界的基石 1.1 什么是操作系统&#xff1f; 操作系统&#xff08;OS&#xff09;是计算机系统的核心管理者&#xff0c;承担着三大核心使命&#xff1a; 硬件指挥官&#xff1a;直接管理C…

【Python 语法】算法合集

查找二分查找代码大 O 表示法 广度优先搜索代码 狄克斯特拉算法 递归递归调用栈 分而治之&#xff08;divide and conquer&#xff0c;D&C&#xff09;贪心教室调度问题背包问题集合覆盖问题 动态规划背包问题旅游行程最优化 遇到问题时&#xff0c; 如果不确定该如何 高效…

IDEAPyCharm安装ProxyAI(CodeGPT)插件连接DeepSeek-R1教程

背景&#xff1a;最近DeepSeek比较火嘛&#xff0c;然后在githup上也看到了GitHub Copilot&#xff0c;就想着现在AI的准确率已经可以提高工作效率了。所以从网上找了一些编程插件&#xff0c;发现Proxy支持的模型比较多&#xff0c;通用性和适配性比较好。所以本文记录一下pro…

基于javaweb的SSM+Maven幼儿园管理系统设计和实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…

Java-Lambda表达式详解

引言&#xff1a;为什么需要 Lambda 表达式&#xff1f; 在 Java 8 之前&#xff0c;处理需要传递代码块的场景&#xff08;如事件监听、线程任务&#xff09;通常依赖匿名内部类。这种方式代码冗余&#xff0c;可读性差。例如&#xff1a; // 传统匿名内部类实现 Runnable n…

springboot之集成Elasticsearch

目录 二、Elasticsearch 是什么&#xff1f;三、Elasticsearch 安装四、Springboot 集成 Elasticsearch 的方式五、创建项目集成 Elasticsearch 2.创建 Spring Initializr 项目 es &#xff08;3&#xff09;.新建实体类 User&#xff08;4&#xff09;.新建 dao 接口类 UserR…

HBuilderx 插件开发变量名称翻译 ,中文转(小驼峰,大驼峰,下划线,常量,CSS类名)

HBuilderx 插件开发变量名称翻译 &#xff0c;中文转&#xff08;小驼峰&#xff0c;大驼峰&#xff0c;下划线&#xff0c;常量&#xff0c;CSS类名&#xff09; 插件开发文档 工具HBuilderx &#xff0c;创建项目 创建成功后目录 插件需求 开发时 用来将中文转为&#xff0…

C# 数据转换

1. 文本框读取byte&#xff0c;ushort格式数据 byte addr; if (byte.TryParse(textBoxAddr.Text, out addr) true) {}2. 字节数组 (byte[]) 转换为 ASCII 字符串 byte[] bytes { 72, 101, 108, 108, 111 }; // "Hello" 的 ASCII 码 string s0 Encoding.ASCII.Ge…

unity学习60: 滑动条 和 滚动条 滚动区域

目录 1 滚动条 scrollbar 1.1 创建滚动条 1.2 scrollbar的子物体 1.3 scrollbar的属性 2 滚动视图 scroll View 2.1 创建1个scroll View 2.1.1 实际类比&#xff0c;网页就是一个 scroll view吧 2.2 子物体构成 2.3 核心component : Scroll Rect 3 可视区域 view p…