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-0Pod的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
}
这里有两个关键点:
-
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,这会导致查询失败。 -
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
最常见于两种场景:
-
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配置问题。 -
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的一次分享中偷师来的,实测有效。

3041

被折叠的 条评论
为什么被折叠?



