首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >当 NetworkPolicy 默认拒绝 邂逅 egress 遗漏:一次把全集群 DNS 掐断的惨痛事故复盘

当 NetworkPolicy 默认拒绝 邂逅 egress 遗漏:一次把全集群 DNS 掐断的惨痛事故复盘

原创
作者头像
编程小妖女
发布2025-09-21 23:11:29
发布2025-09-21 23:11:29
790
举报
文章被收录于专栏:后端开发后端开发

在一套生产级 Kubernetes 集群里,我踩过一个隐蔽却杀伤力巨大的坑:为命名空间加上 NetworkPolicy 的 默认拒绝,却忘了为 egress 明确放行 DNS。结果是整片业务 Pod 同时失明,外部 API、内部 Service 的域名解析统统失败,症状传播极快,告警像雪崩一样涌来。把这次事故拆开看,背后是 NetworkPolicy 关于 egress 的隔离语义、默认值推断,以及 kube-dns/CoreDNS 的访问模式共同作用的连锁反应。

为了让排障记录更具可复现性,下面先交代技术环境,再给出故障现象、真实报错与调用栈、完整排查步骤,以及可直接 kubectl apply 的修复策略和防坑清单。


技术环境

  • Kubernetes:v1.26.x(网络策略语义同样适用于 v1.20+
  • CNI:Calico v3.26(开启 Kubernetes NetworkPolicy 支持,不使用 Calico GlobalNetworkPolicy 进行跨命名空间全局拦截)
  • DNS:CoreDNS 1.10.xkube-dns ClusterIP 位于 10.96.0.10
  • 节点侧 DNS:未启用 NodeLocal DNSCache;若开启,常见地址为 169.254.20.10
  • 业务命名空间:payments
  • 触发变更:在 payments 命名空间引入 默认拒绝 的 NetworkPolicy,仅写了 ingress 的允许规则,遗漏了 egress 放行 DNS

关于 NetworkPolicy 的基本模型、默认拒绝 建议和 egress 一旦生效即转入白名单模式的语义,可参考 Kubernetes 官方文档与厂商最佳实践。官方文档明确:NetworkPolicy 控制 L3/L4 流量,策略是 允许列表 语义;一旦某 Podegress 类型的策略选择到,该 Pod 的出站流量默认被隔离,除非被某条策略显式放行。


事故现场:真实报错与调用栈

变更落地几分钟后,业务 Pod 陆续出现外部依赖访问失败与内部服务发现失败。典型的报错来自应用日志与 CoreDNS 日志。

应用侧错误消息(节选)

  • Go 应用访问 GitHub API、第三方支付网关等外部域名时报:
代码语言:sh
复制
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 timeoutno such host 是 kube-dns/Cloud DNS 故障或不可达时的典型表征,GKE 的官方排障页也将其列为常见症状。

  • 内部服务解析失败(ServerFault 社区同类案例):
代码语言:sh
复制
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 日志(节选)

CoreDNS Pod 日志出现大量 i/o timeout

代码语言:sh
复制
[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

这类错误意味着业务 Podkube-dns 的查询报文要么发不出去,要么回包被丢,通常与网络策略或底层网络到 DNS 上游的连通性相关。

真实调用栈(Python 栈示例)

一条来自社区 issue 的 Python 调用栈精准命中我们的症状:应用通过 Service DNS 访问 Redis,最终因为 DNS 解析失败异常退出。我们现场采集到的栈与其高度一致:

代码语言:sh
复制
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 ...
Coredns [ERROR] plugin/errors: 2 read udp : i/o timeout(偶尔发生) - Rancher ...


为什么会全军覆没:机制层面的连锁反应

  • 默认允许默认拒绝 的转变:Kubernetes 在未命中任何 NetworkPolicy 时,Pod 的入站与出站都是允许的。一旦某个 Podegress 类型策略选中,即刻进入 出站白名单 模式——只允许策略显式放行的目标、端口与协议。
  • 我们的策略只定义了 ingress 放行、未定义 egress 放行,等价于将 egress 完全关死。
  • DNS 本质上是 egress 到集群内的 kube-dns Service(UDP/TCP 53),若未放行,所有需要域名解析的出站流量都会失败。Calico、Cilium 的最佳实践都明确强调:有 egress 限制时必须显式允许到 DNS。
  • 进一步的坑:如果启用了 NodeLocal DNSCache,它的地址是 169.254.20.10/32,这不是普通 Pod IP,用 namespaceSelector/podSelector 选不出来,策略中要改用 ipBlock 放行这个链路。很多项目忽略了这一差异,导致即使写了 kube-dns 的选择器,解析依旧失败。

排查步骤:如何把问题边界快速收紧

现场我采用了 自上而下 + 对照实验 的策略,把问题快速定位到 egress DNS

  1. 用临时 busybox 在出问题命名空间做对照测试:
代码语言:bash
复制
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 reachedi/o timeout,说明命名空间里的 Pod 已经解析失败,与业务现象一致。官方调试文档也建议用这种方式核验 CoreDNS 工作状态。

  1. 查看 CoreDNS PodService
代码语言:bash
复制
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 报文未达或回包被拦。

  1. 检查命名空间的 NetworkPolicy 生效面:
代码语言:bash
复制
kubectl -n payments get networkpolicy
kubectl -n payments describe networkpolicy <name>

只要存在任何一个 egress 类型策略选中业务 Pod,该 Pod 的出站即进入白名单模式。

  1. 旁路验证:将 dns-test Pod 临时打上不被策略选中的标签,或临时移除策略,nslookup 立即恢复。这是证明 NetworkPolicy 造成隔离的最直接实验。
  2. 如果集群启用了 NodeLocal DNSCache,需要确认 dns-testresolv.conf 指向 169.254.20.10,进而验证策略中是否放行了该地址段流量。

复原方案:可直接使用的 YAML 与验证方法

1. 对未启用 NodeLocal DNSCache 的常见集群

放行到 kube-system 命名空间、带标签 k8s-app: kube-dnsPod,UDP/TCP 53

代码语言:yaml
复制
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 没问题。

2. 启用了 NodeLocal DNSCache 的集群

由于 NodeLocal DNSCache 暴露在本机 169.254.20.10/32,需要改用 ipBlock 放行这个地址(选择器匹配不到):

代码语言:yaml
复制
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 的原因,可参考社区答复。

3. 验证步骤

  • 应用策略:
代码语言:bash
复制
kubectl apply -f allow-dns-egress.yaml
kubectl -n payments get networkpolicy
  • 复测 nslookupdig
代码语言:bash
复制
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,避免语言栈依赖,也无英文双引号:

代码语言:yaml
复制
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 outi/o timeout。修复策略落地后,日志恢复正常。


避坑与经验总结

  • 默认拒绝 要配套 DNS 放行 模板。团队可以把 allow-dns-egress.yaml 作为命名空间基线策略的一部分,任何新业务命名空间默认携带,避免遗忘。
  • 理解 egress 的隔离语义:只要某个 Pod 被任何 egress 策略选中,它的出站立刻进入白名单模式。把这句话贴在变更单模板上,能少很多事故。
  • DNS 既要放行 UDP 53,也要放行 TCP 53。在应对响应分片、重试与部分上游行为时,TCP 通道同样会被使用,单放 UDP 可能会出现偶发解析失败。CNI 与公有云文档均有类似建议。
  • 留意 NodeLocal DNSCache。若启用,业务 Podresolv.conf 指向 169.254.20.10,策略需要 ipBlock 放行;仅靠 kube-dns 的选择器不生效。
  • 验证用例 固化成自动化检查。在 gitops 管道或 admission 阶段,对带 egress 的 NetworkPolicy 自动扫描是否包含 DNS 放行,缺失则阻断合入。
  • 观察面要覆盖 DNS。将 CoreDNS Poderror 日志关键字(如 i/o timeoutSERVFAIL)及业务侧 lookup ... on 10.96.0.10:53 等模式纳入告警,能在事故扩大前给出强提示。
  • 不同 CNI 的细节与增强能力要了解。比如 Calico 还支持 FQDN 域名级 egress 放行(通过 DNS 解析到 IP 后动态放通),这对访问外部 SaaS 非常有用;但这只适用于 egress allow,并且需要信任的 DNS 源。

参考与延伸阅读

  • Kubernetes 官方文档:NetworkPolicy 基础与语义。
  • Kubernetes 官方任务指南:DNS 调试步骤。
  • GKE 官方排障:dial tcp: i/o timeoutno such host 的常见场景。
  • Red Hat 与 CNCF 的 egress 指南与最佳实践,强调 默认拒绝 后按需放行。
  • Calico 文档:默认拒绝、策略排障、DNS 域名级 egress
  • Cilium 教程:网络策略实践。

这次事故对团队的冲击不小,却也让我们的 NetworkPolicy 策略从 能用 走向 可演进。当你在 零信任 的道路上收紧流量时,别忘了为 DNS 留一条明路;当你希望通过 默认拒绝 构筑边界时,也要把 egress 的白名单做成一张可信、可维护的配置表。用成熟的模板、自动化校验和可观测信号,把这类高频且隐蔽的问题挡在上线之前。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 技术环境
  • 事故现场:真实报错与调用栈
    • 应用侧错误消息(节选)
    • CoreDNS 日志(节选)
    • 真实调用栈(Python 栈示例)
    • 错误截图
  • 为什么会全军覆没:机制层面的连锁反应
  • 排查步骤:如何把问题边界快速收紧
  • 复原方案:可直接使用的 YAML 与验证方法
    • 1. 对未启用 NodeLocal DNSCache 的常见集群
    • 2. 启用了 NodeLocal DNSCache 的集群
    • 3. 验证步骤
  • 可运行的极简验证 Deployment(触发可观测错误)
  • 避坑与经验总结
  • 参考与延伸阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档