8.4 K8s 生产运维:NetworkPolicy 网络策略
预计阅读时间:13 分钟
📖 目录
默认情况下,K8s 集群中的所有 Pod 可以互相通信(扁平网络)。NetworkPolicy 是一种防火墙规则,用于控制 Pod 之间以及 Pod 与外部服务的网络流量。
学习目标
- 理解 NetworkPolicy 的作用、匹配逻辑与策略类型(Ingress/Egress)
- 能够为生产环境编写默认拒绝 + 白名单放行的完整网络策略
- 掌握多维度选择器(namespaceSelector、podSelector、ipBlock)的组合用法
- 具备排查网络策略不生效的常见问题的能力
前置知识
- K8s Pod、Namespace、Label/Selector 基本概念
- 基础网络知识:TCP/UDP 端口、CIDR、子网划分
- 已安装支持 NetworkPolicy 的 CNI 插件(Calico、Cilium 等)
- 了解 iptables 基本原理(有助于理解底层实现)
前提条件
NetworkPolicy 需要 CNI 插件支持。以下 CNI 支持 NetworkPolicy:
- Calico(最常用,功能最丰富)
- Cilium(eBPF 驱动,性能最好)
- Weave Net
- Antrea
- Flannel(不支持,需额外组件)
# 检查当前 CNI
kubectl get pods -n kube-system | grep -E "calico|cilium|weave|flannel|antrea"
# 如果 CNI 不支持 NetworkPolicy,策略会被创建但不生效
# 查看集群是否启用了 NetworkPolicy 支持
kubectl get crd | grep networkpolicies
1. NetworkPolicy 基础
策略结构
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all
spec:
podSelector: {} # 空选择器匹配命名空间下所有 Pod
policyTypes:
- Ingress
- Egress
默认拒绝所有入站流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
# 没有 ingress 规则 = 拒绝所有入站
默认拒绝所有出站流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-egress
spec:
podSelector: {}
policyTypes:
- Egress
# 没有 egress 规则 = 拒绝所有出站
同时拒绝入站和出站
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
# 此策略创建后,production 命名空间中所有 Pod 的所有流量被阻断
# 必须配合显式白名单规则才能恢复通信
2. 常见场景
场景一:只允许前端访问后端 API
# 后端只允许前端 Pod 访问(端口 8080)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-allow-frontend
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
场景二:允许前端从公网访问
# 前端允许来自 Ingress Controller 的流量(端口 80/443)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: frontend-allow-ingress
spec:
podSelector:
matchLabels:
app: frontend
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- protocol: TCP
port: 80
- protocol: TCP
port: 443
场景三:数据库只允许后端访问
# MySQL 只允许后端 Pod 访问(端口 3306)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: mysql-allow-backend
spec:
podSelector:
matchLabels:
app: mysql
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: backend
- from:
- namespaceSelector:
matchLabels:
name: monitoring # 允许监控系统访问
场景四:允许 Pod 访问特定外部地址
# 只允许访问 DNS 和 API
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-and-api
spec:
podSelector:
matchLabels:
app: app
policyTypes:
- Egress
egress:
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- to:
- ipBlock:
cidr: 10.100.0.0/16
except:
- 10.100.0.0/24
ports:
- protocol: TCP
port: 443
场景五:按 Pod 标签精细化控制出站
# 只允许 app=payroll 的 Pod 访问财务系统
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: payroll-egress-finance
spec:
podSelector:
matchLabels:
app: payroll
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 10.200.1.0/24 # 财务系统网段
ports:
- protocol: TCP
port: 8443
- to: # 必须放行 DNS
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
场景六:同命名空间内所有 Pod 互访
# 允许同一命名空间内所有 Pod 互相通信
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-same-namespace
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- podSelector: {} # 空 podSelector = 同命名空间内所有 Pod
3. 多维度选择器
# namespaceSelector + podSelector 组合
ingress:
- from:
- namespaceSelector:
matchLabels:
environment: production
podSelector:
matchLabels:
app: frontend
# 上述规则意味着:来自 production 命名空间中 label app=frontend 的 Pod
# 多个 from 条目是 OR 关系
ingress:
- from: # 条件 1:允许监控命名空间所有 Pod
- namespaceSelector:
matchLabels:
name: monitoring
- from: # 条件 2:允许本命名空间中 app=frontend 的 Pod
- podSelector:
matchLabels:
app: frontend
# Pod 满足任意一个条件即可访问
# 同一 from 条目内的 namespaceSelector + podSelector 是 AND 关系
ingress:
- from:
- namespaceSelector:
matchLabels:
env: prod
podSelector:
matchLabels:
role: api-client
# 必须同时满足:来自 env=prod 命名空间 AND label role=api-client 的 Pod
4. 三层防护模型
# 生产环境推荐的分层策略
# 1. 全局默认拒绝(在 namespace 级别创建)
# - default-deny-ingress
# - default-deny-egress
# 2. 基础设施规则(kube-system)
# - 允许 DNS 出站
# - 允许 kube-dns 入站
# 3. 应用层策略(逐命名空间细化)
# 每个命名空间定义自己的白名单规则
# 完整的三层防护 YAML 示例:
---
# Layer 1: 默认拒绝入站
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
---
# Layer 2: 允许 DNS 出站
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
# Layer 3: 应用层白名单
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-allow-frontend
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
5. 调试 NetworkPolicy
# 安装网络策略调试工具
# Calico 自带的策略跟踪
calicoctl get networkpolicy -n default
calicoctl get profile -o wide
# 使用 netshoot 测试连通性
kubectl run -it --rm test-pod --image=nicolaka/netshoot -- /bin/bash
# 在容器内
curl -v http://backend-svc:8080
nc -zv backend-svc 8080
# 检查策略规则数
kubectl get networkpolicy -A
kubectl describe networkpolicy backend-allow-frontend
# Cilium 专用调试
kubectl -n kube-system exec -it cilium-xxxxx -- cilium endpoint list
kubectl -n kube-system exec -it cilium-xxxxx -- cilium policy trace \
--src-k8s-pod default:frontend-pod \
--dst-k8s-pod default:backend-pod \
--dport 8080
# 查看策略生效顺序
kubectl get networkpolicy -n default -o custom-columns=\
'NAME:.metadata.name,POD-SELECTOR:.spec.podSelector.matchLabels'
# 对比策略前后差异(先删除策略再测试)
kubectl delete networkpolicy backend-allow-frontend
# 再测试连通性是否恢复
6. 多策略叠加语义详解
生产环境一个命名空间往往有 10+ 个 NetworkPolicy 同时存在,理解它们的合并逻辑是排障的前提。规则非常简单,但很多人记反:
| 组合方式 | 语义 | 类比 |
|---|---|---|
| 多个策略之间 | 并集(OR):任一策略放行即放行 | 防火墙白名单叠加 |
| 同一策略内多个 from/to 条目 | 并集(OR):满足任一条目即可 | 多个放行规则 |
| 同一 from 条目内多个选择器 | 交集(AND):必须同时满足 | 条件叠加 |
| ports 与 from/to 的关系 | 同一个 ingress 条目内 同时生效(AND):既匹配来源又匹配端口 | 源 + 端口双条件 |
# 叠加示例:以下三个策略同时作用于 app=backend 的 Pod
# 策略 A:允许 frontend 访问 8080
# 策略 B:允许 monitoring 命名空间访问所有端口
# 策略 C:允许本命名空间任意 Pod 访问 3306
# 合并结果:来源为 frontend 或 monitoring 或本命名空间 Pod 的流量全部放行,
# 端口 8080/3306 的限制只在各自策略内生效
# 换句话说:只要命中任意一条完整规则,流量即被放行——"任何策略放行 = 放行"
默认拒绝策略的最佳写法
# 生产推荐:default-deny 与白名单分开管理,互不干扰
# 1. default-deny(每个命名空间一份,podSelector: {} 覆盖全部)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
# 2. 白名单(只写允许项,无需担心与 deny 冲突)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
7. Calico 实现原理
理解底层实现有助于解释"为什么策略不生效"这类问题。以最常用的 Calico 为例(iptables 数据面):
- Felix:Calico 的核心 agent,监听 K8s API 中的 NetworkPolicy/EndpointSlice 变更,把它们翻译成宿主机 iptables 规则
- Profile 与标签:Calico 把每个 Pod 的标签编码成 iptables 的 comment/子链(如
cali-pc-<hash>),规则匹配基于这些标识 - 链结构:每个 Pod 的 veth 出口挂一条
cali-tw-<pod>链,流量先经过 20+ 条 cali 前缀链的过滤(这是iptables-save里大量 cali- 规则的原因) - Egress 方向:Pod 出站流量同样经独立链检查,未命中的默认 DROP
# 查看 Calico 渲染出的 iptables 规则
iptables-save | grep -E "cali-pi|cali-po" | head -20
# 查看 Felix 日志中的策略处理
kubectl -n calico-system logs -l k8s-app=calico-node | grep -i felix | tail -20
# 数据面验证:直接在节点上模拟 Pod 流量路径
# Pod 的 veth 接口
ip link show | grep cali
# 跟踪链规则(需要 root)
iptables -t nat -L -n -v | grep -A 3 cali
当数据面与 API 对象不同步(例如 Calico 升级期间、Felix 重启)时,就会出现"策略存在但规则未渲染"的假象——排错第一步永远是检查 Felix 是否健康、策略是否被正确翻译。
8. 排错案例实战
案例一:数据库连接超时(端口协议不匹配)
现象:应用能连上 MySQL,但 30 秒后超时断开。
排查:MySQL 的 3306 只放行了 TCP,但 MySQL 客户端默认开启了连接复用探测(TCP keepalive),中间 Calico 的 conntrack 老化把空闲连接清掉了。
# 检查 conntrack 表
conntrack -L | grep 3306
# 解决:确认策略端口为 TCP,且 conntrack 超时配置合理
# 或者改用 TCP + 更短的客户端 keepalive 参数
案例二:Ingress 访问 404(策略拦的是健康检查)
现象:Ingress 转发正常,但后端 Pod 频繁摘流。
排查:只放行了来自 ingress-nginx 命名空间的 80 端口,但 kubelet 的探针流量(ReadinessProbe)来自节点 IP,被默认拒绝策略拦截——探针失败导致 Pod 被摘除。
# 修复:放行节点网段(探针与 metrics 采集都走这里)
ingress:
- from:
- ipBlock:
cidr: 10.0.0.0/8 # 节点网段,按实际调整
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- protocol: TCP
port: 8080
案例三:策略灰度导致全量故障
现象:上线新策略 5 分钟后,支付服务大面积超时。
排查:default-deny-egress 应用到所有 Pod 后,支付服务需要访问的外部回调地址(异地数据中心 IP)没有被列入白名单,出站全被丢弃。生产环境应分阶段灰度策略(先小流量命名空间),且 Egress 白名单必须提前基于现有连接清单整理(ss -tnp 或 conntrack 导出活跃连接)。
kubectl exec <pod> -- ss -tn),再按"监控命名空间 → 非核心业务 → 核心业务"的顺序推进。常见错误
问题一:NetworkPolicy 创建后不生效
现象:创建了拒绝策略,但 Pod 间仍然可以通信。
原因:当前 CNI 插件不支持 NetworkPolicy(如 Flannel)。
解决:确认 CNI 支持情况,使用前文命令检查。Flannel 不支持 NetworkPolicy,需要迁移到 Calico 或 Cilium。
问题二:DNS 解析失败
现象:设置了 default-deny-egress 后,Pod 无法解析域名。
原因:出站策略未放行 DNS 流量(UDP/TCP 53)。
解决:为所有命名空间添加 DNS 出站放行规则。
# 必须为每个命名空间添加此规则
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
问题三:策略规则互相冲突
现象:多个 NetworkPolicy 同时匹配同一 Pod,行为不符合预期。
原因:多个策略的 ingress/egress 规则是并集关系(OR),任何一个策略放行就会生效。
解决:理解策略合并逻辑——所有匹配的规则取并集。避免在同一 Pod 上创建互相矛盾的策略。
问题四:跨命名空间访问被拒绝
现象:设置了 namespaceSelector 但跨命名空间通信仍被阻断。
原因:目标命名空间的默认拒绝策略阻止了入站。
解决:确保目标命名空间也有对应的放行规则,或检查是否有 default-deny-ingress 覆盖。
问题五:kube-dns Pod 无法访问
现象:设置了 DNS 出站规则,但 kube-dns 本身被隔离。
原因:kube-system 命名空间的默认拒绝策略阻止了外部 Pod 访问 kube-dns。
解决:为 kube-system 命名空间添加允许所有命名空间 Pod 访问 kube-dns 的策略。
最佳实践
- 从默认拒绝开始——先创建 default-deny 策略,再逐步添加白名单规则
- 使用命名空间级别的默认策略先兜底——避免遗漏任何 Pod
- 标签命名要一致——建议用 org/app/tier 三层标签体系
- 出站规则通常比入站更复杂——先从入站开始,再扩展出站
- 不要忘记 DNS(UDP 53)出站规则——否则 Pod 无法解析域名
- 定期审核 NetworkPolicy:kubectl get networkpolicy -A -o yaml
- 使用 calicoctl 或 cilium policy trace 验证策略——不要仅凭直觉判断
- 为每个环境维护独立的策略文件——通过 kustomize overlay 管理 dev/staging/prod 差异
练习题
练习一:三层防护实践
在 default 命名空间创建三层防护:(1) 默认拒绝所有入站和出站流量;(2) 放行 DNS 出站;(3) 允许 app=frontend 的 Pod 访问 app=backend 的 Pod 8080 端口。然后用 netshoot 测试验证。
练习二:多命名空间隔离
创建两个命名空间 team-a 和 team-b,各自部署前端和后端 Pod。要求:同命名空间内可互访,跨命名空间默认禁止。team-a 的后端允许 team-b 的前端访问 8080 端口。
练习三:出站白名单
为 app=payroll 的 Pod 创建出站策略:只允许访问 10.200.1.0/24 网段的 8443 端口和 DNS(53 端口),禁止其他所有出站流量。验证 Pod 只能访问目标服务。
学习检查点
学完本章后,请检验自己是否掌握以下内容:
| 检查项 | 自测问题 | 验证方法 |
|---|---|---|
| 概念理解 | 能用自己的话解释 NetworkPolicy 的 Pod 选择器和规则类型 | 尝试向他人讲解 |
| 命令操作 | 能不查文档完成 NetworkPolicy 的编写和应用 | 在终端实际执行 |
| 原理掌握 | 能说出 NetworkPolicy 的 CNI 实现和流量过滤原理 | 画出流程图 |
| 故障排查 | 能独立排查 NetworkPolicy 导致 Pod 无法通信的问题 | 模拟故障并修复 |
| 最佳实践 | 能说明为什么需要为每个 Namespace 配置默认拒绝策略 | 对比不同方案 |
本章总结
默认全开的扁平网络是生产安全的最大缺口,NetworkPolicy 的核心思路是"默认拒绝 + 最小放行":先为每个命名空间创建 default-deny 兜底,再按 podSelector/namespaceSelector/ipBlock 组合逐层添加白名单(多个策略是并集语义),并牢记放行 DNS 出站。落地时采用三层防护模型——全局默认拒绝、基础设施规则、应用层白名单,并配合统一的标签体系与 calicoctl/cilium policy trace 验证。
延伸阅读
- 6.2:Kubernetes 入门 Kubernetes 容器编排入门——Service 与 Ingress
- 5.5:防火墙实战 防火墙实战——iptables/nftables
- 5.12:eBPF 基础 eBPF 基础——Cilium 网络