Kubernetes CoreDNS服务发现原理与生产排障实战

1. 这不是“又一个DNS教程”,而是Kubernetes里服务发现的底层心跳

你刚在集群里跑起第一个Pod, kubectl get pods 看着绿油油的READY状态,心里正美——结果一进容器里执行 ping my-nginx-service ,直接报 ping: bad address 'my-nginx-service' 。你懵了:Service明明创建成功了,ClusterIP也分配了,怎么连名字都解析不了?别急,这不是你的YAML写错了,也不是网络插件挂了,而是你还没真正摸到Kubernetes服务通信的命脉: DNS服务 。它不像Linux系统里改个 /etc/resolv.conf 就完事,它是整个集群服务发现的神经中枢,是所有Service、Headless Service、StatefulSet Pod名能被互相识别的唯一基础。我带过二十多个K8s落地项目,90%的新手卡点不在Deployment编排,不在Ingress配置,而是在这个看似最基础、实则最易被忽略的DNS层。它不显山不露水,但一旦出问题,整个服务网格就集体失语——Pod之间ping不通、curl失败、健康检查飘红、应用启动时连不上数据库Service……全都是它在背后悄悄掉链子。这篇文章不讲抽象概念,不堆RFC文档,只说我在生产环境里反复验证过的事实:CoreDNS是怎么把 redis.default.svc.cluster.local 这个字符串,变成一个真实可用的ClusterIP地址的;为什么你改了 /etc/resolv.conf 却毫无作用;为什么StatefulSet的Pod名能被稳定解析而Deployment的不能;以及当你看到 nslookup: can't resolve 'kubernetes.default.svc.cluster.local' 时,第一反应不该是重装集群,而是该去查哪三个关键日志。这是一份从 dig 命令开始、到CoreDNS源码级配置结束的实战手册,适合所有已经能跑通Hello World但还不敢碰生产环境的K8s使用者。

2. DNS服务在Kubernetes中的定位与演进逻辑

2.1 它不是可选插件,而是集群的“呼吸系统”

很多人误以为Kubernetes DNS是个像Metrics Server一样的可选组件,装了更好,不装也能用——这是最危险的认知偏差。DNS服务在K8s中扮演的角色,远超传统意义上的域名解析。它是 服务发现(Service Discovery)的默认且强制实现机制 。K8s设计哲学里有一条铁律: 所有服务间通信必须通过Service抽象层进行,而Service的访问入口,必须通过DNS名称完成 。这意味着,无论你是用 curl http://my-api:8080 还是 jdbc:mysql://mysql:3306/mydb ,背后都依赖DNS将服务名映射为ClusterIP。没有它, kubectl exec -it 进容器后连 kubernetes.default.svc.cluster.local (API Server的内部Service名)都解析不了, kubectl 命令本身就会失效。我见过最典型的案例:某金融客户在灰度集群里禁用了CoreDNS,想“简化架构”,结果第二天所有CI/CD流水线全部中断——Jenkins Agent Pod无法连接K8s API Server获取构建任务,因为 https://kubernetes.default.svc.cluster.local:443 根本解析失败。这不是功能缺失,而是整个控制平面的通信链路被物理切断。所以,理解DNS,首先要破除“它只是个辅助工具”的错觉。它和etcd、kube-apiserver一样,是K8s控制平面的基石组件,是集群维持“呼吸”所必需的氧气。

2.2 从kube-dns到CoreDNS:一次彻底的架构重构

K8s早期版本(1.11之前)默认使用 kube-dns ,它是一个由三个容器组成的Pod: kubedns (主解析器)、 dnsmasq-nanny (缓存与健康检查)、 sidecar (指标暴露)。这种多容器协作模式带来了显著的运维复杂性。我亲身经历的教训是:某次升级后 dnsmasq 容器因内存限制频繁OOM,导致DNS缓存失效,整个集群出现周期性解析延迟尖峰,排查了三天才发现是 dnsmasq-nanny --max-cache-size 参数没随节点规格同步调整。更致命的是, kube-dns 的配置热更新能力极弱,修改 ConfigMap 后必须手动滚动重启Pod,业务流量会瞬间抖动。2018年K8s 1.11正式将CoreDNS作为默认DNS服务器,这绝非简单的“换汤不换药”。CoreDNS是一个 单进程、插件化、基于Go语言编写的现代DNS服务器 。它的核心优势在于: 所有功能都通过插件(Plugin)实现,配置即代码,热加载零中断 。比如, kubernetes 插件负责处理 .svc.cluster.local 域的动态解析; forward 插件负责将非集群域名转发给上游DNS; cache 插件提供LRU缓存; log health 插件分别提供日志和健康探针。这种设计让运维变得极其清晰:要调优缓存,只改 cache 插件的 success 9984 30 (缓存成功响应9984条,TTL 30秒);要增加日志级别,只加一行 log . { class all } 。我管理的一个千节点集群,DNS配置变更从过去的“提心吊胆等凌晨窗口”,变成了“ kubectl edit cm coredns 保存后5秒内全量生效”。这种稳定性提升,是 kube-dns 时代完全无法想象的。

2.3 CoreDNS的默认配置结构与命名空间语义

CoreDNS的配置文件( Corefile )是理解其行为的钥匙。默认安装的 coredns ConfigMap内容看似简单,实则暗藏玄机:

.:53 {
    errors
    health {
       lameduck 5s
    }
    ready
    kubernetes cluster.local in-addr.arpa ip6.arpa {
       pods insecure
       fallthrough in-addr.arpa ip6.arpa
       ttl 30
    }
    prometheus :9153
    forward . /etc/resolv.conf
    cache 30
    loop
    reload
    loadbalance
}

这段配置定义了监听在53端口的DNS服务。其中最关键的 kubernetes 插件块,直接决定了集群内域名的解析规则。我们逐行拆解其语义:

  • cluster.local in-addr.arpa ip6.arpa :声明CoreDNS负责解析这三个域。 cluster.local 是K8s Service的默认集群域, in-addr.arpa ip6.arpa 则用于反向DNS查询(如 10.96.0.1 反查对应Service名)。
  • pods insecure :此参数极具迷惑性。它表示对Pod IP的A记录查询采用“不安全”模式,即不验证Pod是否真实存在(仅检查IP格式),这极大提升了查询性能。但注意,这并非漏洞,因为Pod IP本身就在集群网络内,安全性由CNI网络策略保障。
  • fallthrough in-addr.arpa ip6.arpa :当查询 in-addr.arpa ip6.arpa 域失败时,将请求透传给下游 forward 插件,避免反向查询阻塞。
  • ttl 30 :为所有Service记录设置30秒的TTL,这是平衡一致性和性能的关键参数。太短(如5秒)会导致客户端频繁重查,增加CoreDNS负载;太长(如300秒)则Service后端Pod变更后,客户端可能长时间访问到已销毁的Pod。

这里必须强调一个新手常踩的坑: Service的FQDN(完全限定域名)是有严格层级的 。以 my-nginx 这个Service为例,其完整域名是 my-nginx.default.svc.cluster.local 。其中 default 是命名空间(Namespace), svc 是Service子域, cluster.local 是集群根域。CoreDNS正是通过 kubernetes 插件,根据这个层级结构,从etcd中实时读取 /registry/services/specs/default/my-nginx 路径下的Service对象,提取其 spec.clusterIP 字段生成A记录。如果你在Pod里执行 nslookup my-nginx 失败,但 nslookup my-nginx.default.svc.cluster.local 成功,那99%是因为Pod的 /etc/resolv.conf search 域配置不正确,而非CoreDNS本身故障。

3. 核心解析流程与实操验证方法

3.1 从 nslookup 到etcd:一次完整的DNS查询链路

理解DNS如何工作,最好的方式是亲手追踪一次查询。假设你在 default 命名空间下有一个名为 redis 的Service,ClusterIP为 10.96.123.45 。现在,我们从一个Pod内部发起 nslookup redis ,全程拆解每一步发生了什么:

第一步:Pod的DNS配置确认
进入目标Pod: kubectl exec -it <pod-name> -- sh ,查看 /etc/resolv.conf

nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

这里 nameserver 10.96.0.10 就是CoreDNS Service的ClusterIP(通常由 kube-dns Service指向)。 search 域列表定义了域名补全顺序:当执行 nslookup redis 时,系统会依次尝试 redis.default.svc.cluster.local redis.svc.cluster.local redis.cluster.local ,直到成功。 ndots:5 表示如果域名中包含5个或更多点( . ),则不进行search补全,直接查询原域名,避免对 www.google.com 这类公网域名做无谓的内部搜索。

第二步:CoreDNS接收并路由查询
CoreDNS监听在 10.96.0.10:53 。当收到 redis.default.svc.cluster.local 的A记录查询时, kubernetes 插件被触发。它首先检查该域名是否匹配 cluster.local 域(是),然后解析出 redis (服务名)、 default (命名空间)、 svc (子域)。接着,它向K8s API Server发起一个 GET /api/v1/namespaces/default/services/redis 请求(实际通过本地缓存或etcd直连,取决于配置)。

第三步:API Server与etcd的数据联动
API Server接收到请求后,从etcd中读取键 /registry/services/specs/default/redis 。这个键的value是一个JSON对象,其中 spec.clusterIP 字段的值就是 10.96.123.45 。CoreDNS将此IP封装成标准DNS A记录响应,返回给客户端Pod。

第四步:客户端缓存与应用行为
Pod内的glibc或musl libc库会将此A记录缓存一段时间(受 /etc/resolv.conf options timeout:2 attempts:3 等参数影响)。但请注意: 应用层的HTTP客户端(如Java的OkHttp、Python的requests)通常有自己的DNS缓存,且默认TTL远高于系统级缓存 。这就是为什么有时你 nslookup 能看到新IP,但应用依然连着旧Pod——应用没刷新自己的DNS缓存。解决方案要么重启应用,要么在代码中禁用其DNS缓存(如Java添加JVM参数 -Dnetworkaddress.cache.ttl=5 )。

我建议你立刻在自己集群里执行这个验证链路。找一个运行中的Pod,执行:

# 1. 确认DNS配置
cat /etc/resolv.conf

# 2. 手动指定CoreDNS IP进行查询,绕过search域
nslookup redis.default.svc.cluster.local 10.96.0.10

# 3. 查看CoreDNS日志,确认查询被处理
kubectl logs -n kube-system deployment/coredns | grep "redis"

# 4. 直接curl API Server验证Service对象存在
curl -k --cert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
     --header "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
     https://kubernetes.default.svc.cluster.local:443/api/v1/namespaces/default/services/redis

这四步做完,你就亲手打通了从应用代码到etcd存储的完整数据链路,比看一百页文档都管用。

3.2 Headless Service与StatefulSet的特殊解析逻辑

普通Service(ClusterIP类型)的DNS解析,返回的是一个单一的ClusterIP地址。但Headless Service( clusterIP: None )完全不同,它返回的是 后端所有Pod的A记录集合 。这是StatefulSet实现稳定网络标识的核心机制。我们以一个名为 mysql 的Headless Service为例:

apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  clusterIP: None
  selector:
    app: mysql
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: "mysql" # 必须与Headless Service名一致
  replicas: 3
  template:
    spec:
      containers:
      - name: mysql
        image: mysql:5.7

当这个StatefulSet创建后,会产生三个Pod: mysql-0 mysql-1 mysql-2 。此时,DNS解析行为发生质变:

  • nslookup mysql.default.svc.cluster.local :返回三个A记录,分别是 mysql-0.mysql.default.svc.cluster.local mysql-1.mysql.default.svc.cluster.local mysql-2.mysql.default.svc.cluster.local 对应的Pod IP。
  • nslookup mysql-0.mysql.default.svc.cluster.local :直接返回 mysql-0 Pod的IP,且这个名称 永久绑定 到该Pod,即使Pod被删除重建,只要序号不变,名称和DNS记录就保持不变。

这种“稳定网络身份”是Deployment永远无法提供的。Deployment的Pod名是随机后缀(如 nginx-7d5b9c8f4-abcde ),每次重建都会变化,DNS记录也随之失效。而StatefulSet通过 serviceName 字段,强制将Headless Service的DNS解析逻辑与Pod序号深度绑定。我在一个高可用MySQL集群中就依赖此特性:应用连接字符串直接写 jdbc:mysql://mysql-0.mysql:3306,mysql-1.mysql:3306,mysql-2.mysql:3306/mydb ,利用MySQL JDBC驱动的自动故障转移能力,无需任何中间件。当 mysql-1 Pod宕机时,应用自动切换到 mysql-2 ,整个过程对业务透明。这背后,全是CoreDNS对Headless Service的精准解析在支撑。

3.3 IPv6 DNS支持的配置要点与陷阱

随着IPv6在云环境的普及,K8s集群开启IPv6双栈已成为趋势。但DNS服务的IPv6支持并非开箱即用,需要精细配置。核心挑战在于: CoreDNS必须能同时解析IPv4(A记录)和IPv6(AAAA记录),且上游DNS转发必须兼容

首先,确保你的K8s集群已启用IPv6双栈。检查Node的 /proc/sys/net/ipv6/conf/all/disable_ipv6 应为 0 ,且 kubectl get nodes -o wide 显示Node有IPv6地址。然后,修改CoreDNS的 Corefile

.:53 {
    errors
    health
    ready
    kubernetes cluster.local in-addr.arpa ip6.arpa {
       pods insecure
       fallthrough in-addr.arpa ip6.arpa
       ttl 30
    }
    # 关键:为IPv6单独配置forward插件
    forward . 2001:4860:4860::8888 2001:4860:4860::8844 {
       policy random
    }
    # 启用AAAA记录缓存
    cache 30
    loop
    reload
    loadbalance
}

这里有两个关键点:

  1. forward 插件的上游DNS服务器必须是支持IPv6的。Google的 2001:4860:4860::8888 是经典选择,但国内环境建议使用腾讯DNS的IPv6地址 2402:f000:1::1 或阿里DNS的 2400:3200::1 。切忌混用IPv4和IPv6上游,如 forward . 8.8.8.8 2001:4860:4860::8888 ,这会导致查询失败。
  2. cache 插件默认只缓存A记录,要缓存AAAA记录,需显式指定: cache 30 { success 9984 30 } ,其中 success 参数明确列出要缓存的记录类型。

一个真实踩过的坑:某客户在IPv6集群中,Pod内 ping6 www.google.com 超时,但 nslookup -type=AAAA www.google.com 却能返回正确IPv6地址。排查发现,是 forward 插件配置了 policy sequential (顺序查询),当第一个上游(IPv4 DNS)响应慢时,整个查询被阻塞。改为 policy random 后问题解决。这再次印证:DNS不是黑盒,每个参数都关乎生死。

4. 故障排查与生产环境避坑指南

4.1 “No such host”类错误的分层诊断法

nslookup curl No such host 时,新手往往陷入“是CoreDNS挂了?是Service没创建?是网络策略拦了?”的混乱。我总结了一套四层诊断法,按顺序执行,95%的问题能在5分钟内定位:

第一层:确认Pod自身DNS配置
这是最容易被忽视的起点。执行:

kubectl exec <pod-name> -- cat /etc/resolv.conf

检查 nameserver 是否指向正确的CoreDNS ClusterIP(通常是 10.96.0.10 )。如果显示 127.0.0.1 8.8.8.8 ,说明Pod的 dnsPolicy 被错误设置为 Default None 。修复方案:在Pod Spec中显式设置 dnsPolicy: ClusterFirst

第二层:验证CoreDNS服务可达性
在Pod内直接 telnet nc 测试CoreDNS端口:

kubectl exec <pod-name> -- nc -zv 10.96.0.10 53

如果连接失败,说明CoreDNS Service的Endpoint未就绪。执行:

kubectl get endpoints -n kube-system coredns
# 正常输出应类似:NAME      ENDPOINTS                         AGE
# coredns   10.244.0.5:53,10.244.0.6:53   10d

如果ENDPOINTS为空,证明CoreDNS Pod未Running或Readiness Probe失败。此时应检查 kubectl get pods -n kube-system -l k8s-app=kube-dns 的状态。

第三层:检查CoreDNS日志中的具体错误
不要只看 kubectl logs -n kube-system deployment/coredns 的末尾几行。要用 grep 精准过滤:

# 查看是否有“refused”错误(通常因配置语法错误)
kubectl logs -n kube-system deployment/coredns | grep refused

# 查看是否有“timeout”错误(通常因API Server不可达或etcd压力大)
kubectl logs -n kube-system deployment/coredns | grep timeout

# 查看是否有特定域名的“NXDOMAIN”(域名不存在,说明Service或Namespace名拼错)
kubectl logs -n kube-system deployment/coredns | grep "redis.*NXDOMAIN"

第四层:绕过CoreDNS,直连API Server验证数据
如果前三层都正常,但解析仍失败,问题一定出在数据源。用 curl 直接访问API Server:

# 获取Service对象的完整信息
kubectl exec <pod-name> -- curl -k \
  --cert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
  --header "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
  https://kubernetes.default.svc.cluster.local:443/api/v1/namespaces/default/services/redis

如果返回 404 ,说明Service确实不存在;如果返回 200 spec.clusterIP 为空,说明Service是Headless类型,此时应查询 endpoints 而非 services

这套方法论的价值在于:它把一个模糊的“DNS不通”问题,分解为四个可独立验证的原子步骤。我在一次深夜故障中,就是靠它在2分钟内定位到是 Corefile 里一个多余的 { 符号导致CoreDNS启动失败,而不是盲目地重启整个集群。

4.2 “503 Service Unavailable”错误的根源与修复

503 Service Unavailable 是K8s中最具迷惑性的错误之一。它常出现在 kubectl 命令、Dashboard访问或API调用时。很多人第一反应是“API Server挂了”,但真相往往藏在DNS层。 503 错误的本质是: 客户端成功连接到某个服务(如CoreDNS或API Server的代理),但该服务无法将请求转发到真正的后端

在DNS上下文中, 503 最常见于两种场景:

  1. CoreDNS的 forward 插件上游不可达 :当CoreDNS需要解析 www.baidu.com 这类外部域名时,它会将请求转发给 forward 插件配置的上游DNS(如 8.8.8.8 )。如果上游DNS服务器宕机或网络不通,CoreDNS会返回 503 。验证方法:在Pod内执行 nslookup www.baidu.com ,如果失败,再执行 nslookup www.baidu.com 8.8.8.8 ,如果后者成功,证明是CoreDNS的 forward 配置问题。
  2. API Server的Aggregation Layer(聚合层)故障 :K8s的 metrics-server custom-metrics-apiserver 等扩展API,都通过Aggregation Layer注册到主API Server。当这些扩展服务的Service或Endpoints异常时,主API Server在处理 /apis/metrics.k8s.io/v1beta1 等路径请求时,会返回 503 。此时, nslookup 本身可能正常,但 kubectl top nodes 会失败。

修复 503 的黄金法则: 先确定是哪个服务返回的503,再针对性处理 。使用 kubectl get events --sort-by=.lastTimestamp 查看最近事件,重点关注 Warning 级别的 FailedToCreateEndpoint FailedToUpdateEndpoint 。对于CoreDNS上游问题,最稳妥的方案是配置多个上游DNS,并启用 policy random

forward . 114.114.114.114 223.5.5.5 8.8.8.8 {
   policy random
   health_check 5s
}

health_check 参数会让CoreDNS定期探测上游DNS的健康状态,自动剔除故障节点。

4.3 生产环境必须做的五项加固配置

在经历过三次因DNS问题导致的P0级事故后,我为所有生产集群制定了以下五项强制加固措施,它们成本极低,但收益巨大:

1. 启用CoreDNS的 log 插件并配置分级日志
默认CoreDNS日志级别过低,无法定位问题。在 Corefile 中添加:

log . {
   class all
}

并将日志输出重定向到标准输出(确保 kubectl logs 能捕获),同时配置Logrotate防止磁盘打满。我曾靠 log 插件的一行 [INFO] 10.244.1.3:42123 - 51251 "A IN redis.default.svc.cluster.local. udp 54 false 512" NOERROR qr,aa,rd 124 0.000123456s ,精准定位到是某个Pod的DNS查询频率过高,触发了上游DNS的限流。

2. 设置合理的 cache 插件参数
默认 cache 30 只缓存30秒,对高并发集群远远不够。根据你的QPS调整:

  • QPS < 100: cache 30 { success 1000 30 }
  • QPS 100-1000: cache 30 { success 5000 60 }
  • QPS > 1000: cache 30 { success 10000 120 } success 后的数字是缓存条目数上限,第二个数字是TTL。注意:TTL过长会导致Service变更后客户端无法及时感知,需在性能和一致性间权衡。

3. 配置 loop 插件并监控其告警
loop 插件用于检测DNS查询循环(如CoreDNS将请求转发给自己)。它默认开启,但必须配置告警。在Prometheus中添加Rule:

- alert: CoreDNSLoopDetected
  expr: coredns_dns_request_count_total{job="coredns", code="SERVFAIL"} > 0
  for: 1m
  labels:
    severity: critical
  annotations:
    summary: "CoreDNS loop detected"

一旦触发,立即检查 forward 配置是否错误地将请求指回了自身。

4. 为CoreDNS Deployment设置严格的资源限制
CoreDNS是CPU密集型服务。未设限制时,单个Pod可能吃光节点CPU,导致整个节点上的Pod DNS解析延迟飙升。我的标准配置:

resources:
  limits:
    memory: "170Mi"
    cpu: "100m"
  requests:
    memory: "70Mi"
    cpu: "100m"

limits 设为 170Mi 是经过压测的阈值,超过此值CoreDNS会OOM,但不会影响其他Pod。

5. 实施CoreDNS配置的GitOps化管理
绝不允许 kubectl edit cm coredns 。所有 Corefile 变更必须走Git仓库,通过Argo CD或Flux自动同步。这样做的好处是:每一次变更都有审计日志,回滚只需 git revert ,且能避免多人编辑冲突。我们曾因两个工程师同时修改 Corefile ,导致一个删了 health 插件,一个改了 ttl ,最终生成了一个语法错误的配置,集群DNS服务瘫痪了17分钟。GitOps是唯一能杜绝此类人为灾难的方案。

5. 高级配置与企业级实践案例

5.1 自定义DNS策略:为不同命名空间配置专属上游DNS

大型企业集群常有混合云或多租户场景:开发环境需要访问公网DNS,而金融核心业务区必须强制走内网DNS服务器,且禁止解析任何公网域名。这时, forward 插件的全局配置就力不从心了。CoreDNS的 kubernetes 插件支持基于命名空间的条件路由,我们可以这样实现:

# 为default命名空间配置公网DNS
default.svc.cluster.local:53 {
    errors
    health
    kubernetes default.svc.cluster.local {
       pods insecure
       fallthrough
    }
    forward . 114.114.114.114 223.5.5.5
    cache 30
}

# 为finance命名空间配置内网DNS,且禁止公网解析
finance.svc.cluster.local:53 {
    errors
    health
    kubernetes finance.svc.cluster.local {
       pods insecure
       fallthrough
    }
    # 只允许解析finance.svc.cluster.local域
    rewrite stop type A name regex (.*)\.finance\.svc\.cluster\.local {1}.finance.svc.cluster.local
    # 将所有其他查询重写为NXDOMAIN
    rewrite name regex (.*) \. {1}.finance.svc.cluster.local
    forward . 10.100.1.100  # 内网DNS服务器
    cache 30
}

这个配置实现了: finance 命名空间下的Pod,只能解析 *.finance.svc.cluster.local 域名,任何对 www.baidu.com 的查询都会被重写并返回 NXDOMAIN 。而 default 命名空间下的Pod则不受限制。这种细粒度控制,是 kube-dns 时代完全无法实现的。我们在一个银行客户的私有云中部署了此方案,成功满足了等保三级对“业务隔离与DNS访问控制”的硬性要求。

5.2 使用 template 插件实现服务发现的动态注入

有些遗留应用无法修改代码,但又需要在启动时获取其他Service的IP。这时,可以利用CoreDNS的 template 插件,在DNS响应中动态注入环境变量。例如,为 my-app Service生成一个特殊的TXT记录,包含其ClusterIP:

.:53 {
    errors
    health
    kubernetes cluster.local in-addr.arpa ip6.arpa {
       pods insecure
       fallthrough in-addr.arpa ip6.arpa
    }
    template IN TXT my-app-ip.my-app.default.svc.cluster.local {
       answer "{{ .Name }} 30 IN TXT \"{{ .ClientIP }}\""
       fallthrough
    }
    forward . /etc/resolv.conf
    cache 30
}

然后,在 my-app 的Pod启动脚本中:

#!/bin/sh
# 从DNS获取my-app自身的IP
MY_IP=$(nslookup -type=TXT my-app-ip.my-app.default.svc.cluster.local | grep "text =" | awk -F'"' '{print $2}')
echo "My IP is $MY_IP"
exec "$@"

这种方式绕过了应用代码改造,用基础设施层的能力解决了业务问题。虽然不算最佳实践,但在迁移过渡期,它救了我们好几个关键项目的上线时间。

5.3 性能压测与容量规划:单CoreDNS实例的极限在哪里

很多人问:“我的集群有5000个Pod,需要几个CoreDNS副本?”答案不是拍脑袋,而是要压测。我使用的标准压测方案如下:

工具 dnsperf (比 ab wrk 更专业) 命令

# 生成测试域名列表(包含Service名、Pod名、Headless名)
seq 1 1000 | awk '{print "redis-"$1".default.svc.cluster.local"}' > domains.txt
# 压测100并发,持续60秒
dnsperf -s 10.96.0.10 -d domains.txt -Q 100 -l 60

关键指标

  • QPS(Queries Per Second) :单实例稳定值通常在8000-12000 QPS(取决于CPU型号)。超过此值,延迟(Latency)会指数级上升。
  • P99延迟 :必须< 100ms。如果P99 > 200ms,说明CoreDNS已过载。
  • CPU使用率 :单核CPU使用率持续> 80%,即为瓶颈。

扩容公式

所需CoreDNS副本数 = ceil(集群总QPS / 单实例QPS)

但必须留20%余量。例如,压测得单实例QPS为10000,则5000 QPS的集群,至少需要 ceil(5000/10000 * 1.2) = 1 个副本;而20000 QPS的集群,则需要 ceil(20000/10000 * 1.2) = 3 个副本。我管理的最大集群有12000个Pod,峰值QPS达35000,最终部署了5个CoreDNS副本,并通过 loadbalance 插件实现了请求的轮询分发。记住:DNS是集群的咽喉,宁可冗余,不可紧绷。

我个人在实际操作中发现,最有效的优化不是堆硬件,而是 精简 search 。默认的 search default.svc.cluster.local svc.cluster.local cluster.local 会让每次查询最多产生3次DNS请求。将 search 域缩减为 search default.svc.cluster.local ,能直接降低30%的CoreDNS负载。这个小技巧,是我从K8s SIG-Networking的一次分享中偷师来的,实测有效。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值