Kubernetes 网络策略
某公司微服务架构运行正常,直到一次内部渗透测试暴露了一个严重问题:攻击者通过 Web 应用的 SQL 注入漏洞拿下了数据库 Pod。然后,他发现了一个更惊人的事实——他可以直接从数据库 Pod 访问到内部所有其他服务,包括消息队列、缓存、甚至 Kubernetes API。
「扁平网络」让攻击者可以自由横向移动。在 Kubernetes 中,默认情况下,同一集群内的 Pod 可以自由通信,不受任何限制。这就是为什么需要 NetworkPolicy。
K8s 默认网络的全通性
Kubernetes 的默认网络模型是全通的(Flat Network)。每个 Pod 获得一个 IP 地址,Pod 之间可以直接通过 IP 通信,不需要 NAT。
这种设计简化了网络配置,但带来了安全风险:
无隔离:任意 Pod 可以访问任意其他 Pod,不受应用边界的约束。
默认信任:网络层面的身份认证缺失,假设「能连接就是可信的」。
攻击面扩大:一旦某个 Pod 被攻破,攻击者可以轻松横向移动到其他 Pod。
flowchart LR
A[Web Pod] -->|"无限制"| B[API Pod]
A -->|"无限制"| C[DB Pod]
B -->|"无限制"| C
C -->|"无限制"| A
style A fill:#e3f2fd
style C fill:#ffebee
NetworkPolicy 的作用
NetworkPolicy 是 Kubernetes 提供的网络隔离资源,用于控制 Pod 之间的网络流量。
命名空间隔离
通过标签选择器隔离命名空间,控制跨命名空间的流量。
工作负载隔离
在命名空间内部,通过 Pod 选择器控制哪些 Pod 可以相互通信。
入口与出口控制
分别控制入站流量(Ingress)和出站流量(Egress)。
NetworkPolicy 的 YAML 结构
NetworkPolicy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-network-policy
namespace: production
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: web
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
核心字段说明
Ingress 与 Egress 规则
Ingress 规则
控制谁可以访问被保护的 Pod。
Ingress
ingress:
# 允许来自 Web Pod 的流量
- from:
- podSelector:
matchLabels:
app: web
ports:
- protocol: TCP
port: 8080
# 允许来自特定命名空间的流量
- from:
- namespaceSelector:
matchLabels:
name: monitoring
ports:
- protocol: TCP
port: 9090
# 允许来自外部 IP 的流量
- from:
- ipBlock:
cidr: 10.0.0.0/8
except:
- 10.0.0.0/24
Egress 规则
控制被保护的 Pod 可以访问什么。
Egress
egress:
# 允许访问数据库
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
# 允许访问 DNS
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
# 允许访问外部 API
- to:
- ipBlock:
cidr: 0.0.0.0/0
ports:
- protocol: TCP
port: 443
标签选择器与命名空间选择器
Pod 选择器
Pod
ingress:
- from:
- podSelector:
matchLabels:
app: web
role: frontend
命名空间选择器
命名空间选择器
ingress:
- from:
- namespaceSelector:
matchLabels:
name: production
tenant: company-a
组合选择器
组合选择器
ingress:
# 同时满足命名空间和 Pod 标签
- from:
- namespaceSelector:
matchLabels:
name: production
podSelector:
matchLabels:
app: web
NetworkPolicy 的评估顺序
当多个 NetworkPolicy 作用于同一个 Pod 时,Kubernetes 按以下顺序评估:
- 如果 Pod 被任何 NetworkPolicy 选中,则只允许匹配规则的流量
- 如果 Pod 没有被任何 NetworkPolicy 选中,则允许所有流量
flowchart TB
A{有 NetworkPolicy<br/>选中该 Pod?} -->|是| B[仅允许匹配的流量]
A -->|否| C[允许所有流量]
B --> D[拒绝其他流量]
C --> E[网络全通]
style B fill:#c8e6c9
style C fill:#fff3e0
style E fill:#ffcdd2
默认行为的重要性
由于默认行为是允许所有流量,很多团队在部署了部分 NetworkPolicy 后误以为网络已被隔离。实际上,只有被 NetworkPolicy 选中的 Pod 才会被限制。
CNI 对 NetworkPolicy 的支持
不是所有 CNI 插件都支持 NetworkPolicy。
Calico 配置
Calico
apiVersion: projectcalico.org/v3
kind: NetworkPolicy
metadata:
name: api-isolation
namespace: production
spec:
selector: app == 'api'
types:
- Ingress
- Egress
ingress:
- action: Allow
protocol: TCP
source:
selector: app == 'web'
destination:
ports:
- 8080
- action: Deny
egress:
- action: Allow
protocol: TCP
destination:
selector: app == 'database'
ports:
- 5432
Cilium 配置
Cilium 支持 L7 层的 HTTP 感知策略:
Cilium
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: http-policy
namespace: production
spec:
endpointSelector:
matchLabels:
app: api
ingress:
- fromEndpoints:
- matchLabels:
app: web
toPorts:
- port: "8080"
rules:
http:
- method: GET
path: "/api/*"
常见 NetworkPolicy 模式
模式一:默认拒绝
最先部署的策略应该是默认拒绝,限制所有流量进入。
默认拒绝入口流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
模式二:允许 Web 到 API
Web
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-web-to-api
namespace: production
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: web
ports:
- protocol: TCP
port: 8080
模式三:允许 API 到数据库
API
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-to-db
namespace: production
spec:
podSelector:
matchLabels:
app: database
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: api
ports:
- protocol: TCP
port: 5432
egress:
- to:
- podSelector: {}
namespaceSelector: {}
ports:
- protocol: UDP
port: 53 # DNS
模式四:完整微隔离
flowchart TB
subgraph "production namespace"
W[Web Pod<br/>app=web] -->|"TCP:8080"| A[API Pod<br/>app=api]
A -->|"TCP:5432"| D[DB Pod<br/>app=database]
A -->|"TCP:6379"| R[Redis Pod<br/>app=redis]
end
subgraph "monitoring namespace"
M[Prometheus] -->|"TCP:9090"| A
end
W -->|"TCP:443<br/>DNS:53"| OUT[外部网络]
A -->|"TCP:443<br/>DNS:53"| OUT
W -.->|"仅 DNS"| OUT
A -.->|"仅 DNS"| OUT
NetworkPolicy 的测试与验证
NetworkPolicy 容易配置错误,需要测试验证。
测试工具:kubectl debug
测试网络连通性
# 部署测试 Pod
kubectl run test-$RANDOM --rm -it --image=busybox -- /bin/sh
# 测试到 API 的连通性
wget -qO- http://api.production:8080/health
# 如果能访问成功,说明策略未正确配置
测试工具:curl from within
验证网络隔离
# 创建临时测试 Pod
kubectl run nettest --image=curlimages/curl --rm -it --restart=Never
# 进入 Pod 并测试
/ $ curl -v http://database.production:5432
# 预期:连接被拒绝(NetworkPolicy 生效)
# 异常:连接成功(NetworkPolicy 未生效)
自动化测试:Sonobuoy
使用
# 运行 Network Policy 测试
sonobuoy run --mode=conformance --plugin=e2e --kubernetes-version=1.28.0
# 检查结果
sonobuoy status
sonobuoy retrieve
总结与延伸思考
NetworkPolicy 是微隔离的核心工具,但它的配置需要仔细规划。实践中,建议采用以下策略:
从外到内:先保护核心资源(如数据库),再放宽到外围服务。
显式声明:明确允许每一条通信路径,而不是默认允许再逐条拒绝。
测试驱动:在生产部署前,通过测试验证策略的有效性。
持续审计:定期检查实际流量与 NetworkPolicy 的匹配情况,发现过度的允许规则。
思考题
问题 1:为什么说「部署了一个 NetworkPolicy 后,所有流量都被限制了」是一个常见误解?
参考答案
这是因为对 Kubernetes NetworkPolicy 默认行为的误解。如果 Pod 没有被任何 NetworkPolicy 选中,它仍然可以自由通信。NetworkPolicy 的评估是基于「选中」关系的,只有被选中的 Pod 才会被限制。正确的做法是先部署「默认拒绝」策略,然后再显式声明允许的通信路径。
问题 2:为什么 GKE 的默认 CNI(Google VPC CNI)不直接支持 Kubernetes NetworkPolicy?如何解决这个问题?
参考答案
Google VPC CNI 使用 AWS VPC 式的安全组模型,不直接实现 Kubernetes NetworkPolicy API。解决方式有三种:1)使用 Calico 作为 CNI 插件替代默认插件;2)使用 Anthos Service Mesh(基于 Istio)实现网络隔离;3)在 GKE 中启用 Network Policy 功能(本质上是部署 Calico)。对于需要 Kubernetes 原生 NetworkPolicy 的场景,建议使用 Calico CNI。