一、先聊聊我遇到的那次“鬼打墙”式的故障
前阵子我们团队把一个微服务应用迁到 Azure Kubernetes Service(简称 AKS)上,一切看似顺畅。可一到业务高峰期,就有人喊“页面打不开”“接口超时”,更奇怪的是,错误提示里经常出现“域名解析暂时失败”这几个字。起初大家以为 DNS 服务出了问题,可检查集群里的 CoreDNS 日志,发现它忙得飞起,但也没报什么致命错误。后来我们重启了几个 Pod,症状居然消失了,但过几分钟又冒出来,像闹鬼一样反复折腾。
后来我们静下心来,从网络层面一步步排查,才终于揪出罪魁祸首:集群里某两个命名空间下的 Pod 使用了严重重叠的 IP 地址段,导致 CoreDNS 在转发请求时,有一部分流量被“劫持”到了错误的 Pod 上,响应包就丢了,进而引发 DNS 查询间歇性超时。这篇文章就是想用大白话,把这个排查过程和解决办法完整讲清楚,顺便把 CoreDNS 插件和网络策略这两个“武器”也好好介绍一下。你不需要是网络专家,只要跟着我的思路走,准能看懂。
二、先说清楚一个关键概念:Pod 网络到底是怎么一回事
在 AKS 里,每个 Pod 都有自己的 IP 地址,这个地址是集群内部虚拟的,外部访问不到。AKS 默认使用 Azure CNI(Container Network Interface)插件,它会把 Pod 的 IP 地址池和虚拟网络(VNet)的子网绑定在一起。也就是说,Pod 的 IP 和虚拟机(VM)的 IP 是同一个网段里的合法地址。这样做的好处是网络性能好、延迟低,但也有一个“坑”:如果我们在创建集群时,把 Pod 子网的范围划得太大,或者不小心让多个节点上的 Pod 子网范围重叠了,那可就麻烦了。
更常见的冲突场景是:我们在不同命名空间里部署了多个应用,每个应用都用了自己定义的“NodeSelector”或“拓扑分布约束”,但底层的节点池子网配置搞错了,导致两个不同的节点池从同一个子网里分配 Pod IP。这样一来,两个完全不相关的 Pod 就可能拿到一模一样的 IP 地址。网络包发过去之后,交换机或路由器就不知道该把数据送给哪一个,经常随机丢包,表现就是“一会儿通,一会儿不通”。
三、故障定位第一步:先用朴素的 Kubernetes 命令看一眼全局
碰到这种玄学问题,我的经验是别急着看代码,先把集群里的 Pod 列表和 IP 分布打印出来,对照着查。你可以在本地终端用 kubectl 操作 AKS 集群,前提是已经配置好了连接信息。
# 获取集群所有命名空间下的 Pod,以及它们所在的节点和 IP 地址
kubectl get pods --all-namespaces -o wide | sort -k7
上面这条命令会输出很多列,其中第 7 列是 Pod 的 IP。我们要重点观察有没有重复的 IP。不过 Pod 数量太多时,肉眼很难看全。可以写一个小脚本,把重复 IP 过滤出来。
# 提取所有 Pod 的 IP,然后统计每一个 IP 出现的次数
kubectl get pods --all-namespaces -o jsonpath='{range .items[*]}{.status.podIP}{"\n"}{end}' | sort | uniq -d
如果这条命令打印出了重复的 IP,那基本可以锁定“IP 冲突”这个方向了。我当时就是在这个步骤,发现了一个叫 10.240.0.75 的地址,同时出现在 finance-app 命名空间和 logging-service 命名空间下,一个是一个是后端数据库,另一个是日志采集代理,八竿子打不着,但就是撞了车。
四、借助 CoreDNS 插件,把“幕后黑手”揪出来
找到冲突 IP 只是第一步,我们还得证明 DNS 故障确实和这个冲突有关。这就需要用到 CoreDNS 的一把利器——whoami 插件。这个插件能从收到的 DNS 查询中提取客户端的 IP 和端口,并作为 DNS 记录返回。听起来很抽象?我举个例子你就懂了。
假设我们有两个冲突的 Pod,IP 都是 10.240.0.75,但它们上面跑的服务不同。当一个请求需要解析 api.mycompany.com 时,CoreDNS 会从上游 DNS 服务器得到结果,然后返回给客户端。但如果这个 DNS 请求本身就被网络设备错误地路由到了冲突的 Pod 上,那么 Pod 里运行的进程(比如某个恶意代理或无意中开启的 DNS 服务)就可能会吃掉这个请求,导致响应异常。
为了复现这个情况,我们可以临时在 CoreDNS 的配置里加入 whoami 插件,这样每次有 DNS 查询进来,CoreDNS 都会在响应里附带它看到的客户端 IP 和端口。如果这个 IP 恰好和冲突 IP 相同,那证据就齐了。下面是具体操作。
4.1 先备份一下现网 CoreDNS 配置,安全第一
在改动前,一定先把配置备份到本地,万一改坏了能马上恢复。
# 获取 kube-system 命名空间下的 CoreDNS ConfigMap,并保存为文件
kubectl -n kube-system get configmap coredns -o yaml > coredns-backup.yaml
# 用 cat 看一下文件内容,确认备份成功
cat coredns-backup.yaml
4.2 修改 ConfigMap,加入 whoami 插件
CoreDNS 的配置在 AKS 里也是通过 ConfigMap 管理的。我们通过 kubectl edit 来修改它。
# 编辑 CoreDNS 的 ConfigMap
kubectl -n kube-system edit configmap coredns
在编辑界面里,你会看到类似这样的内容:
apiVersion: v1
data:
Corefile: |
.:53 {
errors
health
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
我们要在 loop 后面加一行 whoami。注意代码块里的注释是我加的,实际编辑时你不要加。
# 配置文件片段:在 loop 之后添加 whoami 插件
loop
whoami # 当收到 DNS 查询时,返回客户端的 IP 和端口信息
reload
loadbalance
保存并退出后,CoreDNS 会自动重新加载配置,不需要重启 Pod。你可以看一下 CoreDNS 的日志确认没有语法错误。
# 查看 CoreDNS 的日志,观察是否有配置报错
kubectl -n kube-system logs --selector=k8s-app=kube-dns --tail=20
4.3 发起测试 DNS 请求,观察响应
为了触发问题,我们需要从一个测试 Pod 内发起 DNS 查询。先创建一个简单的测试 Pod,取名 dns-guy,里面放一个带 nslookup 命令的镜像。
# 保存为 dns-test-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: dns-guy
spec:
containers:
- name: dns-tester
image: busybox
command: ["/bin/sh", "-c", "sleep 3600"]
创建这个 Pod,然后在它的内部执行 DNS 查询。
# 创建测试 Pod
kubectl apply -f dns-test-pod.yaml
# 进入 Pod 内部,执行 nslookup
kubectl exec -it dns-guy -- nslookup api.mycompany.com
正常情况下,输出结果会有 Server 和 Address。如果这时候去查看 CoreDNS 的日志,可能会看到类似这样的信息:
# 查看 CoreDNS 日志,搜索 whoami 记录
kubectl -n kube-system logs --selector=k8s-app=kube-dns --tail=100 | grep "10.240.0.75"
如果日志里出现了和冲突 IP 相关的客户端地址,那就说明 DNS 请求确实被路由到了那个冲突的 Pod 上。到这里,我们就用铁证证明了“IP 冲突导致 DNS 解析间歇性失败”这个结论。
五、说透 CoreDNS 插件的原理,以及它为什么这么好用
你可能会有疑惑:whoami 插件不过就是返回一个客户端地址而已,多大的事?别急,其实 CoreDNS 的插件体系非常强大,它本质上是一个 DNS 服务器框架,所有功能都由插件组合而成。除了 whoami 之外,还有几个在解决类似问题时经常用到的插件,我挨个介绍一下。
5.1 whoami 插件:流量路径的“监控摄像头”
它的功能很简单,但价值很高。当 CoreDNS 收到查询请求时,whoami 插件会构造一个特殊的响应,把查询源 IP、源端口、传输协议都塞进去。你可以在测试时开启它,然后观察日志,就能知道哪些客户端在查询、查询有没有经过预期的链路。这在排查网络路由错乱时特别有用。
5.2 kubernetes 插件:连接 K8s 和 DNS 的桥梁
这个插件负责处理集群内服务名、Pod 名的解析,比如 my-service.my-namespace.svc.cluster.local。它直接读取 Kubernetes 的 API,所以解析速度很快,也保证了一致性。我们在排查时,要确保这个插件配置正确,否则会出现“服务名解析失败”的假象。
5.3 forward 插件:把外部域名交给上游 DNS
当查询不是集群内部域名时,forward 插件会把请求转发给配置的上游 DNS(比如 Azure 自带的 DNS)。如果网络冲突发生在 Pod 之间,forward 转发出去的请求可能也会被扭曲。所以排查时要检查 forward 的配置路径。
5.4 插件配置的注意事项
- 插件顺序很重要,不同的解析流程会按顺序匹配规则。一般把
whoami放在loop后面,forward放在最后。 - 配置
whoami后,不要忘了用它完成排查后就移除,否则它会在所有 DNS 响应中额外返回一段信息,增加不必要的开销。 - 修改 CoreDNS 配置后,最好用
kubectl rollout restart重启一下 CoreDNS,让它彻底加载新配置,虽然reload插件支持热加载,但重启更干净。
六、治标也要治本:用网络策略精细规避 IP 冲突的负面影响
找到原因后,我们不可能立刻重建整个集群,因为成本太高。那怎么办?有两个思路:一是限制 Pod 之间的通信,让冲突的 Pod 不参与 DNS 流量;二是重新规划 IP 段,彻底消除冲突。前者适合快速止血,后者适合长期解决。这里详细说一下前者,也就是用 Kubernetes 的 NetworkPolicy(网络策略)来缩小爆炸半径。
6.1 网络策略的作用和限制
NetworkPolicy 是一种 Kubernetes 资源,它通过选择器匹配一组 Pod,然后允许或者拒绝进出这些 Pod 的特定流量。比如我们可以指定:只允许某个命名空间下的 Pod 访问 CoreDNS 的 53 端口,其他冲突 Pod 的流量一律拒绝。这样一来,即使两个 Pod 有相同的 IP,我们也能确保 CoreDNS 只会和真正该通信的 Pod 说话。
不过要注意,AKS 默认的网络插件是 Azure CNI,它本身支持 NetworkPolicy,但需要在创建集群时启用网络策略功能。如果没有启用,你需要先启用 azure-policy 组件。忘了也没关系,可以用下面的命令检查。
# 检查集群是否有网络策略相关的组件在运行
kubectl get pods -n kube-system | grep azure-policy
如果没有任何输出,说明没有启用,那就要在门户网站或通过 az aks 命令更新集群来启用。这一步操作比较费时,但属于一劳永逸。
6.2 示例:写一个网络策略,只允许特定应用访问 CoreDNS
下面我们实现这样一个场景:我们希望命名空间 finance-app 里的 Pod 能够正常访问 CoreDNS,而其他拥有冲突 IP 的命名空间(比如 logging-service)里的 Pod,不允许访问 CoreDNS,但允许它们访问自己的服务。当然,这个策略只是为了临时隔离问题,不能误杀正常业务,所以选择器要写准确。
假设我们有一个应用带有标签 app: payment-api,它需要解析集群域名。而 logging-service 下的 Pod 带有标签 app: filebeat。那我们写策略如下:
# 保存为 network-policy-dns-block.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: block-conflicting-dns-from-logging
namespace: logging-service # 只作用于这个命名空间
spec:
podSelector:
matchLabels:
app: filebeat # 只针对冲突的日志采集 Pod
policyTypes:
- Egress
egress:
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: finance-app # 允许访问财务应用
ports:
- protocol: TCP
port: 8080
等一下,这条策略有点问题:我们是想阻止 filebeat 访问 CoreDNS,但上面策略反而是允许了。实际上,NetworkPolicy 默认是拒绝所有未显式允许的流量。如果你不写任何 egress 规则,那么允许全部;但一旦写了 egress,只允许命中的规则,其他都拒绝。所以我们要阻止访问 CoreDNS,就直接不把 CoreDNS 的 53 端口放在放行列表里就行了。上面代码我故意写了一个复杂版本,这里我们重写一个干净清晰的。
正确的策略如下:
# 保存为 network-policy-dns-block.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: block-dns-only
namespace: logging-service
spec:
podSelector:
matchLabels:
app: filebeat
policyTypes:
- Egress
egress:
# 允许向任意目标发起 HTTPS 请求,但不允许 DNS 查询
- to:
- namespaceSelector: {} # 任意命名空间
ports:
- protocol: TCP
port: 443
# 允许向本命名空间内其他 Pod 发起通信(内部服务调用)
- to:
- podSelector: {}
ports:
- protocol: TCP
port: 5044
在这个策略下,filebeat Pod 只能访问 443 端口和本命名空间 5044 端口,绝对不能访问 UDP/TCP 53 端口。这样一来,即使它和别的 Pod 冲突,也无法再向 CoreDNS 发起查询,自然就不会干扰正常的 DNS 了。
但等等,这会不会让 filebeat 自身无法正常工作?如果 filebeat 确实需要解析外部域名怎么办?这就需要我们评估业务逻辑。在故障期间,我们可以暂时让它使用 IP 直接通信,或者通过 egress 规则单独放行它的特定上游。比如:
# 在同一个策略里添加专门的外部 DNS 白名单
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/8 # 这里换成你真正需要解析的 DNS 服务器网段
ports:
- protocol: UDP
port: 53
这个做法更精细,允许 filebeat 只能向特定上游 DNS 发送请求,而不是默认的 CoreDNS。这样既不影响它的核心功能,又切断了和集群 DNS 的关联。
七、长期解决方案:重划网络地址池,从根上消除冲突
NetworkPolicy 是应急手段,终究不优雅。我们要思考为什么 IP 会冲突,然后去修复地址规划。在 AKS 中,每个节点池都有自己关联的子网。当你创建多个节点池时,要确保每个节点池的子网地址不重叠,并且 Pod 子网的范围足够大,能容纳所有节点的 Pod。我们可以在 Azure 门户中检查虚拟网络(VNet)的子网列表,然后对比每个节点池的配置。
# 列出集群节点池及其子网 CIDR
az aks nodepool list --resource-group myResourceGroup --cluster-name myAKSCluster --output table
输出结果里有一个 vnetSubnetID 字段,记录着完整子网路径。如果发现两个节点池的 vnetSubnetID 指向了同一个子网,那就危险了。解决方案是创建新的节点池,指定不同的子网,然后把工作负载迁移过去。迁移过程可以选择运行 kubectl drain 排空旧节点,让 Pod 在新节点上重建。
# 将旧节点池中的节点排空,让 Pod 优雅迁移
kubectl drain aks-nodepool1-abcdef0 --ignore-daemonsets --delete-emptydir-data
完成之后删除旧节点池,并在集群配置里检查新节点池使用的子网。这样 IP 冲突就彻底消失了。再配合我们前面讲到的 whoami 插件做验证,可以看到 DNS 解析恢复稳定,CoreDNS 日志里不再出现混淆的客户端地址。
八、应用场景:这个问题最容易出现在哪些项目中
这种 IP 冲突引发的 DNS 故障,最容易发生在以下场景:
- 多团队共用一个 AKS 集群,大家各自创建命名空间和节点池,但网络管理员没有统一规划子网。
- 使用
Virtual Kubelet或者某些 Serverless 容器实例(如 ACI)时,Pod 的地址分配机制不同,容易和普通节点池冲突。 - 大规模动态扩缩容时,节点池的子网扩容操作不小心把地址范围填错了,导致原本不重叠的段变重叠。
所以,凡是涉及多命名空间、多子网、多节点池的 AKS 生产环境,都有必要提前做好地址规划,并且定期用脚本检查冲突。
九、技术优缺点、注意事项和排查建议
在我们这项技术方案中,我总结了一下:
9.1 CoreDNS 插件的优缺点
优点:插件丰富、配置方便、完全兼容 K8s,whoami 插件很适合诊断网络路由问题,不需要额外安装复杂的网络抓包工具。
缺点:启用 whoami 插件后,所有 DNS 响应都会带上额外信息,略微增加响应大小和日志量;而且它只能看到 CoreDNS 视角的信息,如果问题发生在 CoreDNS 之前的上游链路,还需要借助数据平面抓包。
9.2 网络策略的优缺点
优点:不需要重启集群,能快速隔离异常的流量,规则直观,支持命名空间级别和 Pod 级别。
缺点:如果策略写太严,会误伤正常业务;而且 NetworkPolicy 只能管 Kubernetes 集群内部的流量,对于节点到 Pod 的流量,还需要靠底层防火墙规则;另外,如果没启用 Azure Policy,需要额外配置。
9.3 注意事项
- 改 CoreDNS 配置前,一定先备份 ConfigMap。
- 启用
whoami后,用nslookup测试有代表性的域名,不要只测一次,要多次、用不同客户端测,以便重现间歇性问题。 - 网络策略的设计要遵循最小权限原则,只拒绝必须要拒绝的,不要一锅端。
- 最后别忘了移除
whoami插件,恢复干净配置,以免影响后续性能。
十、文章总结
说到底,AKS 集群里的 DNS 间歇性失败,很多时候不是 CoreDNS 的问题,而是底层网络地址规划出了问题。我们要学会利用 CoreDNS 插件来观察流量路径,也要善于利用网络策略来快速止血,最终还是要回归到彻底修整网络规划上。遇到类似问题,不要慌,按照“看 IP 分布 → 加观察插件 → 写策略隔离 → 重新规划子网”这个思路,一步一步来,就能把问题看清楚、解决掉。希望我的这篇实战经验能帮你在以后面对这类问题时少走弯路。
评论
围绕“在Azure Kubernetes Service集群环境中,Pod网络冲突引发DNS解析间歇性失败,如何借助CoreDNS插件与网络策略精细配置逐步完成故障定位与有效规避”参与讨论