主题:云原生安全。本文讨论防御性网络隔离、验证和排障,不涉及攻击利用步骤。
核心观点
Kubernetes NetworkPolicy 不是“创建一个 YAML 就获得零信任”。它只是 Kubernetes API 层的策略对象,真正拦截发生在 CNI 数据面。策略能否生效,取决于 CNI 是否实现、Pod 是否被选中、Ingress/Egress 是否都进入隔离态、DNS 是否被显式放行、跨命名空间选择器是否写对,以及你有没有做数据面验证。
很多生产集群的微隔离失败,不是因为没有写策略,而是因为把 kubectl get netpol 当成了验证结果。正确做法是把 NetworkPolicy 当成一套控制闭环:声明策略、注入数据面、实测连通性、记录阻断原因、再把命名空间创建和标签治理纳入 CI/CD。
1. 第一条边界:CNI 不支持,策略对象就是摆设
Kubernetes 官方文档明确说明,如果使用的网络插件不支持 NetworkPolicy,创建资源本身不会产生隔离效果。换句话说,API Server 接受了 YAML,不代表数据面会执行。
落地前要先回答三个问题:
| 问题 | 证据 | 不能接受的替代品 |
|---|---|---|
| 当前 CNI 是什么 | 节点 CNI 配置、DaemonSet、云厂商集群网络插件信息 | “我们集群是 Kubernetes,所以应该支持” |
| 是否支持 NetworkPolicy | CNI 官方文档、版本说明、插件配置 | kubectl api-resources 能看到 NetworkPolicy |
| 策略是否注入数据面 | Calico/Cilium/云厂商数据面状态、实测阻断结果 | kubectl apply 成功 |
最小检查可以这样做:
kubectl -n kube-system get pods -o wide
kubectl get ds -n kube-system
kubectl get networkpolicy -A
kubectl api-resources | grep -i networkpolicy但这只是控制面检查。真正的验证必须进入连通性测试,后面会展开。
2. 第二条边界:默认拒绝必须按命名空间落地
NetworkPolicy 是 namespace-scoped。不存在一个原生 Kubernetes NetworkPolicy 能“一键全局默认拒绝所有命名空间”。如果只在 prod 放了 default deny,staging、tools、dev 仍然可能默认全通。
推荐把 default deny 作为 namespace bootstrap 的必选项,而不是事后手工补:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress注意这里没有 ingress 和 egress 规则,语义就是选中的 Pod 对入站/出站都进入隔离态。生产中不要只写 Ingress default deny 就声称“微隔离完成”,因为出站路径往往才是横向移动和数据外联的关键。
3. 第三条边界:Egress 默认拒绝会打断 DNS
很多团队第一次启用 Egress default deny,业务立刻报错,原因不是服务端口没放行,而是 DNS 被切断。Pod 访问 service.namespace.svc.cluster.local 之前需要先解析 CoreDNS;如果没有放行到 kube-dns/CoreDNS 的 UDP/TCP 53,应用表现通常是 timeout、name resolution failed 或连接池异常。
一个常见的最小 DNS 放行策略如下:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: production
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这个示例的边界也要写清楚:不同发行版、云厂商或 CoreDNS 标签可能不同,不能直接复制。应先用下面命令确认真实标签:
kubectl -n kube-system get pods --show-labels | grep -E 'coredns|kube-dns'
kubectl get ns kube-system --show-labels还要注意 Service ClusterIP 与 DNAT/SNAT 的实现差异。业务实际访问的通常是 kube-dns / CoreDNS Service IP,而不是 DNS Pod IP;不同 CNI 对 Service 转发前后如何匹配 NetworkPolicy 可能存在差异。因此 DNS egress 不能只靠 YAML 推断,必须做正向解析测试和负向外联测试,确认当前 CNI 的命中行为。
4. 第四条边界:namespaceSelector 和 podSelector 的组合很容易写错
跨命名空间放行时,最稳妥的方式是基于 namespace 标签和 Pod 标签,而不是写 Pod IP。Pod IP 是短生命周期对象,滚动更新后就会变化。
部署在 api 命名空间、允许 web/frontend 访问 api/backend 的 Ingress 示例:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-web-to-backend
namespace: api
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
tier: frontend-zone
podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080这里的缩进和层级很关键。namespaceSelector 与 podSelector 放在同一个 from item 中,表示“选中这些 namespace 里的这些 pod”。如果写成两个并列 item,语义可能变成“这些 namespace 的所有 pod”或“本 namespace 中这些 pod”,导致边界扩大。
5. 第五条边界:策略是叠加并集,不是后写覆盖前写
NetworkPolicy 的规则是 additive 的。多个策略选中同一个 Pod 时,允许流量取并集;它不是防火墙那种按顺序匹配、后写覆盖前写的模型。
这会带来一个常见误区:
| 误区 | 真实语义 | 风险 |
|---|---|---|
| 新写一个更严格策略会覆盖旧策略 | 多个策略允许项会叠加 | 旧策略残留导致仍可访问 |
| 删除某个允许项就一定阻断 | 其他策略可能仍允许 | 排障时误判原因 |
| default deny 可以压过 allow | default deny 让 Pod 进入隔离态,后续 allow 仍会放行 | 需要清点所有选中该 Pod 的策略 |
排查时不要只看某一条 YAML,要列出所有选中目标 Pod 的策略:
kubectl -n production get pod backend-xxx --show-labels
kubectl -n production get networkpolicy -o yaml成熟团队会把策略关系纳入 review:每个 Pod 的最终允许流量应该能从 GitOps 产物中推导出来,而不是靠出问题时人工猜。
6. 第六条边界:验证要测“应该通”和“应该不通”
只验证业务能通是不够的。微隔离上线必须同时验证正向和反向:白名单路径应该通,非白名单路径应该失败。
可以把验证写成 CI 作业或发布前检查:
# 应该通
kubectl -n web exec deploy/frontend -- curl -sS --max-time 3 http://backend.api.svc.cluster.local:8080/health
kubectl -n api exec deploy/backend -- nc -vz -w 3 postgres.data.svc.cluster.local 5432
# 应该不通
kubectl -n web exec deploy/frontend -- nc -vz -w 3 postgres.data.svc.cluster.local 5432 && exit 1 || true
kubectl -n tools exec deploy/debug -- curl -sS --max-time 3 http://backend.api.svc.cluster.local:8080/health && exit 1 || true这段脚本的目标不是攻击,而是证明边界真实存在。安全控制如果没有负向测试,基本等于没有验收。
7. 排障路径:不要第一反应就是删策略
NetworkPolicy 出问题时,业务团队经常要求“先关掉策略”。平台团队应该提供一条更短的排障路径:
| 症状 | 优先检查 | 常见修复 |
|---|---|---|
| 服务名无法解析 | DNS egress、CoreDNS 标签 | 放行 UDP/TCP 53 到真实 DNS Pod |
| 只有跨 namespace 失败 | namespace 标签、selector 层级 | 修正 namespaceSelector + podSelector 组合 |
| 目标端口 timeout | 目标 Pod Ingress 与源 Pod Egress | 两端策略都检查 |
| 策略创建成功但完全不拦截 | CNI 能力、数据面同步 | 确认插件支持,检查 CNI 日志 |
| 删除策略后仍可访问 | 其他策略仍允许 | 列出所有选中 Pod 的策略 |
| 云厂商集群行为异常 | CNI chain、云安全组、插件版本 | 对照云厂商文档和变更记录 |
阿里云 ACK 的 CNI Chain 文档也提醒,自定义 CNI Chain 属于高危操作,多个 CNI 插件之间的协作需要充分测试,策略规则可能与网络策略不兼容。这类云厂商网络实现差异,不能靠通用 YAML 想当然覆盖。
结论
NetworkPolicy 不是零信任按钮,它是一个需要标签规范、CNI 兼容性确认、白名单策略设计和连通性测试的防火墙抽象。default-deny-all 只是起点,真正的工程挑战在于如何让策略变更可审计、可回滚、可排障,而不是让开发团队因为『网络不通』而直接删掉策略。
结论
NetworkPolicy 的价值在于把 Kubernetes 集群的东西向流量从“默认互通”改成“按身份和端口显式授权”。但它不是零信任按钮,也不是单个 YAML 能解决的问题。
真正可靠的微隔离,必须同时满足六件事:CNI 支持、命名空间默认拒绝、DNS egress 可控、选择器语义正确、策略叠加关系可解释、正向和负向测试都通过。做到这些,NetworkPolicy 才是安全边界;做不到,它只是控制面里看起来很安全的一条对象记录。



