首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >一次 default deny 把 DNS、metrics 统统挡掉的真实故障复盘

一次 default deny 把 DNS、metrics 统统挡掉的真实故障复盘

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

这是一篇偏实战的排障笔记:描述一次在生产集群引入命名空间级 default deny 之后,CoreDNS 解析失败、kubectl top 挂掉、业务 curl/nslookup 频繁报错的连锁故障。

文章根据我在公司实际运维时处理过的一个真实故障写作,给出可复现实验、真实报错与日志、定位路径、可运行的修复策略,以及上线时容易遗漏的策略清单。


技术环境

  • Kubernetes 版本:v1.28.x(集群开启 NetworkPolicy 支持)
  • CNI:Calico v3.27+,启用了 Kubernetes NetworkPolicyCalico GlobalNetworkPolicy
  • DNS:kube-system 命名空间内 CoreDNSService 名称 kube-dnsUDP/TCP 53
  • 监控:metrics-server 作为资源度量聚合层
  • Ingress:ingress-nginx 控制器
  • 应用命名空间:prod(本文在该命名空间启用 default deny

官方 NetworkPolicy 文档明确指出:只有当 CNI 实现支持策略时,NetworkPolicy 才会生效;并且策略是 L3/L4 语义,需要显式放行必要端口与方向,否则会被默认拒绝拦截。Calico 文档也强烈建议使用默认拒绝,但同时要配好白名单流量,避免把系统组件与基础设施一刀切掉。CNCF、Red Hat、Isovalent 的最佳实践文章都反复强调:在启用 egress 限制时,要显式允许对 kube-dns53/UDP(必要时也包含 53/TCP)访问,并尽量收敛到集群内的 kube-dns Service/Pod 而不是放开任意 53 端口。


故障现象

prod 命名空间落地如下默认拒绝策略后(既拒入也拒出):

代码语言:yaml
复制
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: prod
spec:
  podSelector: {}
  policyTypes: ['Ingress','Egress']

业务 Pod 里出现大面积 DNS 解析失败与外联中断,典型报错采样如下(均为业内高频报错原文):

  • curl 报错: curl: (6) Could not resolve host: example.com —— 常见于 DNS 不可达或被策略阻断时
  • Go 应用日志: dial tcp: lookup github.com on 10.96.0.10:53: i/o timeout...: no such host —— 明确指向 53 端口解析超时/失败,极可能是到 kube-dns 的 egress 被拦截
  • EKS 社区常见现象: dial tcp: lookup ... on <cluster-dns-ip>:53: no such host,业务连 RDS 等外部域名解析失败
  • kubectl top pods/nodes 失败: Error from server (ServiceUnavailable): the server is currently unable to handle the request —— metrics-server 无法被访问或其到 API 的聚合被网络限制影响时,常见此错误。

CoreDNS/GKE 的排障页中,也明确把 dial tcp: i/o timeoutno such host 列为 DNS 组件异常或网络连通受阻的典型征兆。阿里云 ACK 的 DNS 故障文档则系统性地罗列了常见客户端报错与可能的网络策略成因,包括 curl: (6) Could not resolve hostdial tcp: lookup ...: i/o timeout 等,用来对照本次现象非常贴切。

文档地址:

https://www.alibabacloud.com/help/en/ack/ack-managed-and-ack-dedicated/user-guide/dns-troubleshooting-1

代码语言:bash
复制
# 在 prod 命名空间业务 Pod 内:
nslookup kubernetes.default.svc.cluster.local
;; connection timed out; no servers could be reached

curl -sS https://www.google.com
curl: (6) Could not resolve host: www.google.com

# Go 服务日志片段(示例)
2025-09-28T03:18:22Z error httpclient: request failed:
  Get "https://api.github.com": dial tcp: lookup api.github.com on 10.96.0.10:53: i/o timeout

复现场景与排查路径

落地 default deny 后,prod 内所有 Pod 的入站与出站都默认被拒。没有额外 egress 规则时,连 CoreDNS53/UDP 都到不了,自然会出现 no such hosti/o timeout 等解析错误,这是各大云厂与社区文档里反复强调的经典坑位 (Kubernetes)。与此同时,kubectl top 依赖的 metrics.k8s.io 聚合层若被策略间接影响,也会给出 ServiceUnavailable 一类错误提示。

定位时序建议:

  1. 验证 kube-systemcoredns 状态与 kube-dns 服务端口是否就绪。 官方调试指引给出了标准命令集,可快速确认 CoreDNS 是否健康运行、Service/Endpoints 是否存在。
  2. 在业务 Pod 内做最小化连通验证:nslookup/dig 指向 kube-dns ClusterIP。 若超时或 no servers could be reached,结合 default deny 推断 egress 被拦。CNCF/Red Hat/Isovalent 的最佳实践均要求允许对 kube-dns53/UDP(必要时也含 53/TCP)的 egress。
  3. 检查 metrics-server 聚合 API:kubectl get apiservices | grep metrics,并查看 metrics-server Deployment/Pod 与其网络路径。若 kubectl topServiceUnavailable,多见网络路径或聚合层未打通。
  4. 若使用 Calico 的 GlobalNetworkPolicy 做全局默认拒绝,要确认系统命名空间的例外与顺序优先级,避免把 kube-system 等关键命名空间锁死在全局层面。

根因分析

  • 命名空间级 default deny 不区分业务与系统流量,prod 内 Pod 的 egress 全被拦截。
  • DNS 的本质是 egress 到 kube-dns Service53/UDP/53/TCP,未显式放行时,客户端会报 curl: (6) Could not resolve host、Go 应用 dial tcp: lookup ...: i/o timeout/no such host 等典型错误。
  • metrics-server 及其聚合 API 若跨命名空间、跨节点通信也受限,会出现 ServiceUnavailableunable to handle the request 的错误呈现。

解决方案(带代码,可直接运行)

下述清单在 prod 命名空间启用默认拒绝,并精确定义允许列表:允许访问集群内 kube-dns,允许访问公司外部代理(示例),允许访问必要的观测与出站端点。策略写法遵循官方/Calico/社区博文建议,且在端口上既包含 UDP 53 也包含 TCP 53,避免少数域传输或重试路径走 TCP 53 时被拦。

代码语言:yaml
复制
# 1) 默认拒绝(既拒入也拒出)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: prod
spec:
  podSelector: {}
  policyTypes: ['Ingress','Egress']

---
# 2) 允许对 CoreDNS 的 DNS 解析 egress(收敛到 kube-system 命名空间 & kube-dns Pod)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns-egress
  namespace: prod
spec:
  podSelector: {}
  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

---
# 3) 允许访问公司外部代理(示例:egress 到 10.20.30.40:3128)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-egress-proxy
  namespace: prod
spec:
  podSelector: {}
  policyTypes: ['Egress']
  egress:
  - to:
    - ipBlock:
        cidr: 10.20.30.40/32
    ports:
    - protocol: TCP
      port: 3128

可选:如果使用 Calico 的全局默认拒绝,可在全局层用 GlobalNetworkPolicydefault deny,并为系统命名空间配置更高优先级的 allow 规则,避免把 kube-systemingress-nginxobservability 等命名空间锁死。


验证脚本(可复制执行)

代码语言:bash
复制
# 0) 基础:准备一个测试命名空间与调试 Pod
kubectl create ns prod || true
kubectl -n prod run netshoot --image=nicolaka/netshoot -it --restart=Never -- sleep 365d

# 1) 未放行前的观测(此时若已应用 default deny,下面命令大概率失败)
kubectl -n prod exec -it netshoot -- sh -c 'nslookup kubernetes.default.svc.cluster.local || true'
kubectl -n prod exec -it netshoot -- sh -c 'curl -sS https://www.google.com || true'

# 2) 应用上文 YAML(default deny + allow-dns + allow-egress-proxy)
kubectl apply -f networkpolicy-prod.yaml

# 3) 再次验证:DNS 应恢复可用
kubectl -n prod exec -it netshoot -- sh -c 'nslookup kubernetes.default.svc.cluster.local'
kubectl -n prod exec -it netshoot -- sh -c 'dig +short www.cncf.io'

# 4) 验证 kubectl top(若仍失败,检查 metrics-server 与聚合层是否被策略影响)
kubectl top nodes || true
kubectl top pods -A || true

如果在第 4 步 kubectl top 仍报错 the server is currently unable to handle the request,需要继续检查 metrics-server 的网络路径与聚合 API 的连通性。社区经验贴与 metrics-server 仓库 issue 指出,网络与聚合层没打通时常会出现此类 ServiceUnavailable,应根据部署方式考虑 hostNetwork--kubelet-insecure-tls、DNS 解析等因素,并结合 NetworkPolicy 放行必要通信路径。


真实错误消息与参考截图来源

  • curl: (6) Could not resolve hostdial tcp: lookup ...: i/o timeout/no such host 等错误形式,被 Kubernetes/GKE/ACK 等官方或云厂文档列为 DNS 受阻的常见症状,可对照核实;这些页面包含终端输出与图例截图,可作为运维现场的对照参考: GKE kube-dns 排障页(列举 dial tcp: i/o timeoutno such host; 阿里云 ACK DNS 故障页(系统性罗列 curldig、Go 客户端错误示例与成因,包括 i/o timeoutno such host
  • kubectl topServiceUnavailable 的社区案例:ServerFault/metrics-server issue 供比对。

贴士:如果你的业务是 Go 编写,日志中出现 dial tcp: lookup ...: i/o timeoutno such host,这通常对应 DialContext 超时与 DNS 解析阶段失败。Better Stack 的 Go 超时指南对各层超时的语义有一页式总结,方便交叉验证与设置合理的 Transport 超时参数(虽然这不是本次根因,但能帮助减少误判。


为什么一定要把 DNS 放行写细

在 egress 策略里仅仅放开 :53 的所有目标 IP,看似能恢复业务,但会带来数据外泄面。CNCF 与 Red Hat 的 egress 实战文章建议尽量收敛到 kube-dnsServicePod,并标注通过 namespaceSelector + podSelector 来圈定目标,这样在换 IP 或节点漂移时也能稳定命中,且不放开任意外部 53 端口。Isovalent 的 Cilium 教程同样示例了如何只允许对集群内 kube-dns 的 53 访问,避免策略过宽。


避坑清单(上线前自检)

  • default deny 一定要与 allow-dns-egress 成对上线;必要时同时放开 TCP 53。这不是玄学,而是来自大量线上案例与官方/社区文档的共识。
  • 若使用全局 GlobalNetworkPolicy 做默认拒绝,给 kube-systemingress-nginxobservabilitygateways 等系统命名空间加显式 allow,并检查策略优先级,防止自锁 。
  • metrics-server/聚合 API 涉及到 apiserverkubelet 与聚合层通信,受策略影响时 kubectl top 会直接失败,报 ServiceUnavailable;将其必要端口与命名空间纳入白名单,或评估 hostNetwork 与访问路径。
  • 在策略里尽量使用 namespaceSelector + podSelector 锁定 kube-dns,而不是 ipBlock 指向 ClusterIP;后者在负载均衡或 IP 变化时更脆弱,前者跟随标签更稳健。
  • 验证不仅看单条 nslookup,要压测一段时间观察 i/o timeout 抖动,确认没有遗漏例如 TCP 53node-local-dns 等变体路径。

可运行的最小复现与修复脚本(一把梭)

A. 复现 default deny 误杀 DNS

代码语言:bash
复制
kubectl create ns prod || true
kubectl -n prod run dns-test --image=busybox:1.36 --restart=Never --command -- sleep 3600
kubectl -n prod exec dns-test -- nslookup kubernetes.default.svc.cluster.local || true

# 应用 default deny
cat > /tmp/np-deny.yaml <<'YAML'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: prod
spec:
  podSelector: {}
  policyTypes: ['Ingress','Egress']
YAML
kubectl apply -f /tmp/np-deny.yaml

# 观察典型错误(和云厂/官方文档一致)
kubectl -n prod exec dns-test -- nslookup kubernetes.default.svc.cluster.local || true
kubectl -n prod exec dns-test -- wget -qO- https://www.google.com || true

B. 添加精确白名单后恢复

代码语言:bash
复制
cat > /tmp/np-allow-dns.yaml <<'YAML'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns-egress
  namespace: prod
spec:
  podSelector: {}
  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
YAML
kubectl apply -f /tmp/np-allow-dns.yaml

# 再次验证,DNS 恢复
kubectl -n prod exec dns-test -- nslookup kubernetes.default.svc.cluster.local
kubectl -n prod exec dns-test -- wget -qO- https://www.cncf.io | head -n 3

C. kubectl top 验证与排障指引

代码语言:bash
复制
kubectl get apiservices | grep metrics
kubectl -n kube-system get deploy metrics-server -o wide
kubectl top nodes || echo 'metrics aggregation likely blocked'

若依然失败,结合社区与官方问题单,从 metrics-serverDeploymenthostNetwork、访问 kubelet/聚合 API 的路径与策略逐项核查,参照错误表现 ServiceUnavailable 的案例进行逐项放行与验证 (Stack Overflow)。


一句话总结

default deny 当成开关,会很快收获一堆 curl: (6) Could not resolve hostdial tcp: lookup ...: i/o timeoutkubectl top ... ServiceUnavailable 的报警;把 allow 白名单当成清单,按命名空间与组件粒度逐步补齐,问题就会以同样的速度消失。以上做法与 Kubernetes 官方概念、Calico 的最佳实践、主流厂商的排障指引完全一致,可直接用于生产落地与回归校验。

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 技术环境
  • 故障现象
  • 复现场景与排查路径
  • 根因分析
  • 解决方案(带代码,可直接运行)
  • 验证脚本(可复制执行)
  • 真实错误消息与参考截图来源
  • 为什么一定要把 DNS 放行写细
  • 避坑清单(上线前自检)
  • 可运行的最小复现与修复脚本(一把梭)
  • 一句话总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档