最需要记住的一件事:没有策略等于全部放通
Kubernetes 的网络模型里,pod 拿到的是集群级的扁平网络,任意两个 pod 之间天然可以直接发包。NetworkPolicy 不是「默认拒绝、逐条放行」的防火墙,它恰好相反:只有当某个方向的流量被至少一条策略选中时,该方向才会进入白名单判定;没有被选中的流量一律放行。
换句话说,一个命名空间里没有任何 NetworkPolicy 对象时,ingress 和 egress 都是完全开放的,集群对所有 pod 表现得像一个大二层网络。很多团队以为「先不写策略,系统默认应该是安全的」,实际结果完全相反——这是安全评审里最容易被忽略、也最容易出事的一条。
策略的选择逻辑是「并集」而不是「覆盖」。如果同一条流量同时命中三条策略,只要其中任意一条的规则是 allow,这条流量就放行;只有当所有命中策略都要求拒绝时才被拒。写策略时必须把「同一方向上别人写过什么」也纳入考虑,不能假设自己是唯一作者。
策略的作用对象是 pod,用 `podSelector` 选,而不是用 Service 选。你写的标签必须匹配 pod 模板上的标签,匹配 Service 的标签毫无意义。另外 `podSelector: {}` 是一个合法的空选择器,含义是「当前命名空间的所有 pod」,不是「不选任何东西」。
# 现状盘点:哪些命名空间/对象真的有策略
kubectl get networkpolicy -A
# 看某条策略到底选中了哪些 pod(最容易出错的一步)
kubectl get networkpolicy allow-dns -n prod -o yaml
# 逐条确认:selector 与 pod 模板 label 是否一致
kubectl get pods -n prod --show-labelsingress 与 egress:两个独立的开关
NetworkPolicy 有两个顶层字段,各自独立生效。写一条只含 `ingress` 的策略,egress 方向仍然是完全放通的——这是最常见的「我明明限制了出站」的误解来源。很多人只写了入站规则就以为 pod 不能外连,结果 pod 照样能把数据往外发。
ingress 的语义是「谁可以连我」。规则匹配来源 pod 的标签(`from`)加上目标端口(`ports`)。`from` 用 `ipBlock` 时要注意它是按 IP CIDR 匹配的,不考虑 pod 标签,因此在有 NAT 或主机网络参与时语义会变得反直觉。
egress 的语义是「我可以连谁」。规则匹配目标 pod 的标签(`to`)加目标端口。这里有一个非常容易踩的坑:一旦你写了任何一条 egress 策略,所有出站流量(包括 DNS)都必须被某条规则显式允许,否则 pod 会连 DNS 都解析不了,表现为全面超时。
所以生产环境里的标准做法是:先写一条 `policyTypes: [Ingress, Egress]` 的 baseline 策略,把允许的流量显式列出;再叠加更细粒度的策略。落地顺序建议先 Ingress 后 Egress——Ingress 配错表现为业务不可用,容易发现;Egress 配错往往表现为「偶发超时」,更难定位。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: prod
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
# 只允许 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前提条件:策略由 CNI 执行,不是由 kube-proxy 执行
NetworkPolicy 是一个纯 API 对象。它的存在本身不会改变任何数据路径,真正的执行者是集群使用的 CNI 插件。如果你的集群用的是不支持 NetworkPolicy 的插件,`kubectl get networkpolicy` 依然能列出对象、`kubectl apply` 也返回成功,但集群行为完全不变——这是「配了不生效」排第一位的原因。
常见支持策略的插件包括 Calico、Cilium、Antrea 以及部分厂商实现。安装前需要确认两件事:插件确实声明支持 NetworkPolicy(官方文档有支持矩阵),以及插件的策略执行模式是默认还是需要额外启用(例如 Calico 早期版本需要在 Pod CIDR 上做额外配置,Cilium 则支持额外的 L7 策略)。
验证方式不看 YAML 写得对不对,而看数据路径。通用做法是起一个 busybox 容器,用 `wget`/`nc` 去连另一个 pod 的 Service 或 Pod IP:允许时通,被拒时连接挂起(timeout)而不是立即 refused。挂起而不是拒绝是策略丢弃的典型特征,因为包被 iptables/eBPF 规则丢掉了,没有回 RST。
集群还必须为 pod 分配了 Pod CIDR。网络策略需要基于 IP 的匹配与标记,如果没有 Pod CIDR(部分 CNI 在特定模式下不分配),策略同样无法落地。这是「日志里策略存在但行为不变」的第二个高频原因。
# 1) 确认 CNI 插件与策略模式
kubectl -n kube-system get pods -o wide | grep -Ei "calico|cilium|antrea|weave|flannel"
# 2) 实测:从被允许的源连被拒绝的目标
kubectl run nettest --rm -it --image=busybox:1.36 --restart=Never -- sh
# 在 nettest 里:
# nslookup kube-dns.kube-system.svc.cluster.local # DNS 策略生效检查
# wget -T 3 http://<target-pod-ip>:8080/ # 通 => 允许,timeout => 被策略丢弃
# 3) Calcio 场景查看策略端点(可选插件不同命令不同)
calicoctl get policy --scope=global 2>/dev/null | head -20配了不生效的六类原因
第一,podSelector 标签对不上。策略写在 Deployment 的 template.metadata.labels 上,但实际 pod 的标签由控制器加上其它字段(例如 pod-template-hash),或你只给 Service 打了标签而没给 pod 打。用 `kubectl get pods --show-labels` 逐字对比,而不是凭记忆。
第二,端口写成了服务端口而不是容器端口。NetworkPolicy 匹配的是目标 pod 收到的端口,通常是 containerPort;写成 Service 的 port 常常是另一个值,导致规则永不命中。用 `kubectl get pod -o jsonpath` 确认 containerPort。
第三,方向搞反。客户端 pod 上写 ingress 规则去限制服务端 pod 的访问,是无效的——ingress 只约束「谁连我」。限制客户端行为必须写在客户端 pod 的 egress 上。
第四,`namespaceSelector` 默认只在同一命名空间生效。想跨命名空间引用,必须显式写 `namespaceSelector`,且要用 `kubernetes.io/metadata.name` 标签或带 `matchLabels` 的选择器;只写 `podSelector` 会被理解为同命名空间内的 pod。
第五,NodePort 与 hostNetwork 绕过策略。hostNetwork 的 pod 使用节点 IP,通常不受 NetworkPolicy 约束;从集群外经 NodePort 访问 pod 的流量也常因实现差异而不被策略拦住。做隔离验收时必须确认测试流量来源,否则会得出「策略无效」的错误结论。
第六,只配了 Ingress 却以为出站也被限制。最保险的做法是任何命名空间一旦开始写策略,就同时提供 default-deny ingress 与 egress,再按需放行,把「默认」这件事从人的记忆里挪到集群配置里。
# 逐项对照:策略 selector vs 真实 pod label
kubectl get netpol -n prod -o jsonpath='{range .items[*]}{.metadata.name}{" -> "}{.spec.podSelector}{"\n"}{end}'
kubectl get pods -n prod -o custom-columns=NAME:.metadata.name,LABELS:.metadata.labels
# 确认 containerPort 到底是多少(策略要写这个值)
kubectl get pod <pod> -n prod -o jsonpath='{.spec.containers[*].ports[*].containerPort}''
# 确认命名空间自带标签
kubectl get ns prod --show-labels把策略做成可维护的工程:分层与命名
策略的长期成本不在写,而在维护。建议固定三层:`default-deny` 一条管全局兜底,`allow-<role>` 按角色(前端、后端、worker)批量放行,`allow-<role>-to-<dep>` 做点对点例外。每层都用一致的前缀命名,`kubectl get netpol` 一眼能看出意图,也让 code review 有明确的评审对象。
应用层标签要稳定。策略依赖 pod 标签,而标签会随发布变化。建议用一组独立的、语义化的标签(如 `app`、`tier`、`team`)专门喂给策略,不要复用 `version`、`hash` 这类会变的标签;一旦有人改了标签名,安全边界会在一次普通发布里悄悄消失。
每次改动都要有验证脚本。至少覆盖三个方向:本 pod 允许的目标要通、被拒绝的目标必须 timeout、允许的源能连进来。把这三条写成一个 pod 里的 shell 循环放进 CI,比人工点一次可靠得多。策略变更属于安全变更,走和代码一样的评审与回滚流程。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-to-db
namespace: prod
spec:
podSelector:
matchLabels:
app: postgres # 目标:数据库 pod
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: api-server # 来源:同命名空间的 API pod
ports:
- protocol: TCP
port: 5432
# 出站不受本策略影响:这里的 podSelector 只影响 ingress 方向