
在一套生产级 Kubernetes 集群里,我踩过一个隐蔽却杀伤力巨大的坑:为命名空间加上 NetworkPolicy 的 默认拒绝,却忘了为 egress 明确放行 DNS。结果是整片业务 Pod 同时失明,外部 API、内部 Service 的域名解析统统失败,症状传播极快,告警像雪崩一样涌来。把这次事故拆开看,背后是 NetworkPolicy 关于 egress 的隔离语义、默认值推断,以及 kube-dns/CoreDNS 的访问模式共同作用的连锁反应。
为了让排障记录更具可复现性,下面先交代技术环境,再给出故障现象、真实报错与调用栈、完整排查步骤,以及可直接 kubectl apply 的修复策略和防坑清单。
v1.26.x(网络策略语义同样适用于 v1.20+)v3.26(开启 Kubernetes NetworkPolicy 支持,不使用 Calico GlobalNetworkPolicy 进行跨命名空间全局拦截)1.10.x,kube-dns ClusterIP 位于 10.96.0.10169.254.20.10paymentspayments 命名空间引入 默认拒绝 的 NetworkPolicy,仅写了 ingress 的允许规则,遗漏了 egress 放行 DNS关于 NetworkPolicy 的基本模型、默认拒绝 建议和 egress 一旦生效即转入白名单模式的语义,可参考 Kubernetes 官方文档与厂商最佳实践。官方文档明确:NetworkPolicy 控制 L3/L4 流量,策略是 允许列表 语义;一旦某 Pod 被 egress 类型的策略选择到,该 Pod 的出站流量默认被隔离,除非被某条策略显式放行。
变更落地几分钟后,业务 Pod 陆续出现外部依赖访问失败与内部服务发现失败。典型的报错来自应用日志与 CoreDNS 日志。
API、第三方支付网关等外部域名时报:Get https://api.github.com: dial tcp: lookup api.github.com on 10.96.0.10:53: read udp 10.244.3.57:57589->10.96.0.10:53: i/o timeout这类 dial tcp ... i/o timeout 或 no such host 是 kube-dns/Cloud DNS 故障或不可达时的典型表征,GKE 的官方排障页也将其列为常见症状。
http: proxy error: dial tcp: lookup kubernetes.default.svc on 10.96.0.10:53: read udp 10.244.0.4:57589->10.96.0.10:53: i/o timeout(Server Fault)
CoreDNS Pod 日志出现大量 i/o timeout:
[ERROR] plugin/errors: 2 node3. A: read udp 10.244.0.25:43976->8.8.8.8:53: i/o timeout
[ERROR] plugin/errors: 2 example.com. A: read udp 10.244.0.25:55132->8.8.8.8:53: i/o timeout这类错误意味着业务 Pod 到 kube-dns 的查询报文要么发不出去,要么回包被丢,通常与网络策略或底层网络到 DNS 上游的连通性相关。
一条来自社区 issue 的 Python 调用栈精准命中我们的症状:应用通过 Service DNS 访问 Redis,最终因为 DNS 解析失败异常退出。我们现场采集到的栈与其高度一致:
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/redis/connection.py", line 544, in connect
raise ConnectionError(self._error_message(e))
ConnectionError: Error -3 connecting to o2o-redis-service.o2o-sales.svc.cluster.local:6379. Temporary failure in name resolution.说明:上面这类错误在不同语言栈都会出现对应形态,例如 Go 会打印
lookup <host> on 10.96.0.10:53: i/o timeout,Node.js 常见ENOTFOUND,Java 则多见UnknownHostException。共同点是 DNS 解析失败。
下列截图来自社区的同类现场,可与文中错误互相印证(i/o timeout 集中涌现、DNS 查询超时):
![Coredns [ERROR] plugin/errors: 2 read udp : i/o timeout(偶尔发生) - Rancher ...](https://developer.qcloudimg.com/http-save/1472475/193b277436d6765bad5389e6b649a809.jpg)
默认允许 → 默认拒绝 的转变:Kubernetes 在未命中任何 NetworkPolicy 时,Pod 的入站与出站都是允许的。一旦某个 Pod 被 egress 类型策略选中,即刻进入 出站白名单 模式——只允许策略显式放行的目标、端口与协议。 ingress 放行、未定义 egress 放行,等价于将 egress 完全关死。egress 到集群内的 kube-dns Service(UDP/TCP 53),若未放行,所有需要域名解析的出站流量都会失败。Calico、Cilium 的最佳实践都明确强调:有 egress 限制时必须显式允许到 DNS。 169.254.20.10/32,这不是普通 Pod IP,用 namespaceSelector/podSelector 选不出来,策略中要改用 ipBlock 放行这个链路。很多项目忽略了这一差异,导致即使写了 kube-dns 的选择器,解析依旧失败。 现场我采用了 自上而下 + 对照实验 的策略,把问题快速定位到 egress DNS。
busybox 在出问题命名空间做对照测试:kubectl -n payments run dns-test --image=busybox:1.36 --restart=Never -it --rm -- \
sh -c 'nslookup kubernetes.default && nslookup api.github.com || true'若返回 ;; connection timed out; no servers could be reached 或 i/o timeout,说明命名空间里的 Pod 已经解析失败,与业务现象一致。官方调试文档也建议用这种方式核验 CoreDNS 工作状态。
Pod 与 Service:kubectl -n kube-system get svc,ep -l k8s-app=kube-dns -o wide
kubectl -n kube-system logs -l k8s-app=kube-dns --tail=50日志若出现大量 i/o timeout,进一步印证 DNS 报文未达或回包被拦。
kubectl -n payments get networkpolicy
kubectl -n payments describe networkpolicy <name>只要存在任何一个 egress 类型策略选中业务 Pod,该 Pod 的出站即进入白名单模式。
dns-test Pod 临时打上不被策略选中的标签,或临时移除策略,nslookup 立即恢复。这是证明 NetworkPolicy 造成隔离的最直接实验。dns-test 的 resolv.conf 指向 169.254.20.10,进而验证策略中是否放行了该地址段流量。 YAML 与验证方法放行到 kube-system 命名空间、带标签 k8s-app: kube-dns 的 Pod,UDP/TCP 53:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: payments
spec:
podSelector: {} # 作用于命名空间内所有 Pod
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53这条策略只负责放行 DNS;其他业务 egress 规则可以按需另行补充。NetworkPolicy 的规则是相加的,写多条 allow 没问题。
由于 NodeLocal DNSCache 暴露在本机 169.254.20.10/32,需要改用 ipBlock 放行这个地址(选择器匹配不到):
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress-nodelocal
namespace: payments
spec:
podSelector: {}
policyTypes: [Egress]
egress:
- to:
- ipBlock:
cidr: 169.254.20.10/32 # NodeLocal DNS 缓存地址
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53实践里也可以两者都放行,兼容不同环境的 DNS 路径。关于 NodeLocal DNSCache 场景需要 ipBlock 的原因,可参考社区答复。
kubectl apply -f allow-dns-egress.yaml
kubectl -n payments get networkpolicynslookup、dig:kubectl -n payments run dns-ok --image=busybox:1.36 --restart=Never -it --rm -- \
sh -c 'nslookup kubernetes.default && nslookup api.github.com && wget -qO- https://api.github.com/zen'当你看到 nslookup 正常返回 10.96.0.1xx,以及获取到 zen 的一句话输出,egress DNS 已恢复。官方与厂商文档都建议在建立 默认拒绝 后优先补齐 DNS 放行。
Deployment(触发可观测错误)如果想在测试命名空间快速复现 egress 被拦时的典型错误,可以部署一个极简容器,定时做外部域名的 HTTP GET 与内部 Service 的解析并打印错误。下面是 BusyBox 版本的 CronJob,避免语言栈依赖,也无英文双引号:
apiVersion: batch/v1
kind: CronJob
metadata:
name: dns-probe
namespace: payments
spec:
schedule: '*/1 * * * *'
jobTemplate:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: dns-probe
image: busybox:1.36
command:
- sh
- -c
- |
echo '--- start probe ---'
date
echo 'nslookup kubernetes.default.svc'
nslookup kubernetes.default.svc || true
echo 'wget https://api.github.com/zen'
wget -qO- https://api.github.com/zen || true
echo '--- end probe ---'在没有 allow-dns-egress 的情况下,日志会稳定打印 connection timed out、i/o timeout。修复策略落地后,日志恢复正常。
默认拒绝 要配套 DNS 放行 模板。团队可以把 allow-dns-egress.yaml 作为命名空间基线策略的一部分,任何新业务命名空间默认携带,避免遗忘。egress 的隔离语义:只要某个 Pod 被任何 egress 策略选中,它的出站立刻进入白名单模式。把这句话贴在变更单模板上,能少很多事故。 UDP 53,也要放行 TCP 53。在应对响应分片、重试与部分上游行为时,TCP 通道同样会被使用,单放 UDP 可能会出现偶发解析失败。CNI 与公有云文档均有类似建议。 Pod 的 resolv.conf 指向 169.254.20.10,策略需要 ipBlock 放行;仅靠 kube-dns 的选择器不生效。 验证用例 固化成自动化检查。在 gitops 管道或 admission 阶段,对带 egress 的 NetworkPolicy 自动扫描是否包含 DNS 放行,缺失则阻断合入。Pod 的 error 日志关键字(如 i/o timeout、SERVFAIL)及业务侧 lookup ... on 10.96.0.10:53 等模式纳入告警,能在事故扩大前给出强提示。 FQDN 域名级 egress 放行(通过 DNS 解析到 IP 后动态放通),这对访问外部 SaaS 非常有用;但这只适用于 egress allow,并且需要信任的 DNS 源。 dial tcp: i/o timeout、no such host 的常见场景。 egress 指南与最佳实践,强调 默认拒绝 后按需放行。 默认拒绝、策略排障、DNS 域名级 egress。 这次事故对团队的冲击不小,却也让我们的 NetworkPolicy 策略从 能用 走向 可演进。当你在 零信任 的道路上收紧流量时,别忘了为 DNS 留一条明路;当你希望通过 默认拒绝 构筑边界时,也要把 egress 的白名单做成一张可信、可维护的配置表。用成熟的模板、自动化校验和可观测信号,把这类高频且隐蔽的问题挡在上线之前。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。