K8s网络策略:NetworkPolicy的Pod间通信隔离与微服务安全边界
K8s网络策略:NetworkPolicy的Pod间通信隔离与微服务安全边界
适读人群:关注K8s集群网络安全的工程师 | 阅读时长:约20分钟 | 适用版本:K8s 1.20+、Calico/Cilium CNI
开篇故事
我们的K8s集群里混部了多个业务团队的服务:电商业务、内部工具、数据分析。默认情况下,K8s集群内所有Pod之间是可以自由通信的,没有任何网络隔离。这意味着,内部工具服务可以直接访问电商业务的数据库Pod,数据分析服务也能随意扫描生产库……
有次安全审计,审计团队发现一个内部工具服务(有个SQL注入漏洞)可以直接连接到生产MySQL Pod。如果被攻击者利用,可以在不经过任何授权的情况下直接访问生产数据库。
这个发现让我们立刻开始推进NetworkPolicy的配置。我花了两周时间,给所有生产服务配上了网络隔离策略,把"默认全通"改成了"默认全拒绝,按需放行"。
一、核心问题分析
默认的K8s网络模型的安全问题
K8s默认的网络模型是扁平的:只要Pod之间可以路由,任意两个Pod就可以互相通信,不管它们在哪个namespace,不管它们属于哪个业务。
这在小规模、单一业务的集群里问题不大。但在多租户、多业务混部的场景下,这相当于把所有服务都放在同一个内网里,任何一个服务被攻破,就可能成为横向攻击的跳板。
NetworkPolicy是K8s提供的网络访问控制机制,允许基于Pod标签、namespace、CIDR等条件定义精细的Ingress(入站)和Egress(出站)规则。注意:NetworkPolicy需要CNI插件支持,Flannel不支持,Calico、Cilium、Weave Net等支持。
二、原理深度解析
NetworkPolicy的工作方式
NetworkPolicy是白名单机制:一旦某个Pod被NetworkPolicy选中,所有未被明确允许的流量都会被拒绝。如果Pod没有被任何NetworkPolicy选中,则流量不受限制(默认全通)。
关键理解:NetworkPolicy是附加在Pod上的,不是附加在连接上的。一个连接需要源Pod的Egress策略和目标Pod的Ingress策略都允许,才能建立。
三、完整配置实现
第一步:为namespace设置默认拒绝策略
# default-deny-all.yaml
# 推荐:为每个生产namespace设置默认全拒绝策略
# 然后再按需添加允许规则
---
# 默认拒绝所有入站流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: production
spec:
podSelector: {} # 选中namespace内所有Pod
policyTypes:
- Ingress # 对入站流量生效
---
# 默认拒绝所有出站流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-egress
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress第二步:按需放行必要的流量
# allow-dns.yaml
# 关键:必须允许所有Pod访问DNS,否则服务发现完全失效
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: production
spec:
podSelector: {} # 所有Pod
policyTypes:
- Egress
egress:
# 允许访问kube-dns(CoreDNS)
- ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53第三步:为各服务配置精细的NetworkPolicy
# networkpolicy-order-service.yaml
---
# order-service:只接受来自ingress-nginx的流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: order-service-ingress-policy
namespace: production
spec:
podSelector:
matchLabels:
app: order-service
policyTypes:
- Ingress
ingress:
# 允许来自ingress-nginx namespace的nginx-ingress Pod
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
podSelector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
ports:
- protocol: TCP
port: 8080
# 允许来自同namespace的其他服务(内部调用)
- from:
- podSelector:
matchLabels:
tier: backend # 只有标记了tier=backend的Pod才能调用
ports:
- protocol: TCP
port: 8080
# 允许监控系统采集metrics
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
podSelector:
matchLabels:
app: prometheus
ports:
- protocol: TCP
port: 8080 # /actuator/prometheus端口
---
# order-service出站:只允许访问数据库和缓存
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: order-service-egress-policy
namespace: production
spec:
podSelector:
matchLabels:
app: order-service
policyTypes:
- Egress
egress:
# 允许访问MySQL
- to:
- podSelector:
matchLabels:
app: mysql
ports:
- protocol: TCP
port: 3306
# 允许访问Redis
- to:
- podSelector:
matchLabels:
app: redis
ports:
- protocol: TCP
port: 6379
# 允许访问消息队列
- to:
- podSelector:
matchLabels:
app: rabbitmq
ports:
- protocol: TCP
port: 5672
# 允许访问其他微服务(通过Service名称,需要DNS解析)
- to:
- podSelector:
matchLabels:
tier: backend
ports:
- protocol: TCP
port: 8080
# 允许访问外部API(如短信、邮件服务)
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8 # 排除内部网络(不允许随意访问内网)
- 172.16.0.0/12
- 192.168.0.0/16
ports:
- protocol: TCP
port: 443
---
# MySQL:只接受来自业务服务的连接
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: mysql-network-policy
namespace: production
spec:
podSelector:
matchLabels:
app: mysql
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
tier: backend # 只有backend服务可以连接MySQL
ports:
- protocol: TCP
port: 3306
# 允许备份Job访问
- from:
- podSelector:
matchLabels:
job-name: mysql-backup
ports:
- protocol: TCP
port: 3306
egress:
# MySQL只需要DNS解析,不需要其他出站流量
- ports:
- protocol: UDP
port: 53使用Cilium的高级网络策略(L7层)
Cilium支持L7(应用层)的网络策略,可以限制HTTP方法、路径等:
# cilium-l7-policy.yaml
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: order-service-l7-policy
namespace: production
spec:
endpointSelector:
matchLabels:
app: order-service
ingress:
- fromEndpoints:
- matchLabels:
app: api-gateway
toPorts:
- ports:
- port: "8080"
protocol: TCP
rules:
# L7层限制:只允许特定的HTTP方法和路径
http:
- method: "GET"
path: "/orders/.*"
- method: "POST"
path: "/orders"
- method: "PUT"
path: "/orders/[0-9]+/status"四、生产最佳实践
渐进式NetworkPolicy实施策略
在已有的集群里实施NetworkPolicy,不能一下子把所有流量都锁死,要渐进式推进:
第一阶段(观察):用Calico的日志模式(不真正拦截,只记录会被拦截的流量)观察30天,建立流量基线图。
第二阶段(数据层隔离):只给数据库、消息队列这类数据层服务加NetworkPolicy,拒绝所有非预期的入站连接。这是最高优先级,影响范围明确。
第三阶段(业务层隔离):为业务服务逐步添加NetworkPolicy,从最敏感的核心服务开始。
第四阶段(默认拒绝):全部配置完成后,加上默认拒绝策略,形成闭环。
测试NetworkPolicy是否生效
# 部署一个测试Pod来验证网络策略
# 从允许的Pod发请求(应该成功)
kubectl run test-allowed \
--image=curlimages/curl \
--labels="tier=backend" \
--rm -it \
--restart=Never \
-- curl -s http://order-service:8080/actuator/health
# 从不允许的Pod发请求(应该超时/拒绝)
kubectl run test-blocked \
--image=curlimages/curl \
--labels="tier=frontend" \
--rm -it \
--restart=Never \
-- curl --connect-timeout 5 http://mysql:3306
# 预期:test-blocked的请求会超时,说明NetworkPolicy生效五、踩坑实录
坑一:Flannel CNI不支持NetworkPolicy,规则写了等于没写
这是最坑的一点,Flannel是K8s最常见的CNI插件之一,但它根本不支持NetworkPolicy。你把NetworkPolicy写好了,apply了,看上去成功了,但实际上完全没有效果,流量该通的还是通,该拒绝的也没拒绝。
确认CNI是否支持NetworkPolicy的方法:
# 如果是Calico
kubectl get pods -n kube-system -l k8s-app=calico-node
# 如果是Cilium
kubectl get pods -n kube-system -l k8s-app=cilium在不支持NetworkPolicy的CNI上,NetworkPolicy资源可以创建,但完全不起作用,不会有任何报错。这是一个无声的安全漏洞。
坑二:忘记放行DNS导致服务发现失败
第一次在生产加了默认拒绝Egress策略后,大量服务突然开始报UnknownHostException,连不上数据库。排查了很久,发现是DNS解析失败。
加了默认拒绝出站策略后,Pod连UDP 53端口(DNS)都无法访问了,导致所有域名解析失败。连数据库的URL是hostname(如mysql-service),解析失败就连不上了。
务必在加默认拒绝策略的同时,确保DNS放行策略也一并apply:
# 正确的操作顺序
kubectl apply -f allow-dns-egress.yaml # 先放行DNS
kubectl apply -f default-deny-egress.yaml # 再加默认拒绝坑三:AND与OR逻辑混淆
NetworkPolicy的from/to字段里,同一个- from条目里的多个条件是AND关系,不同- from条目之间是OR关系。这个逻辑很容易搞混:
# 错误:这表示"来自既满足namespaceSelector又满足podSelector的Pod"
# 即:来自production namespace里且有app=order-service标签的Pod
from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: production
podSelector:
matchLabels:
app: order-service
# 正确:表示"来自production namespace的任意Pod 或者 来自有app=order-service标签的Pod"
from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: production
- podSelector:
matchLabels:
app: order-service一般来说,跨namespace的精细控制应该用同一条目下的AND逻辑(namespaceSelector AND podSelector),避免意外放通太多流量。
六、总结
NetworkPolicy是K8s集群纵深防御的重要一环,实现了服务级别的网络访问控制。对于混部多个业务的集群,这是安全基线要求,不是可选项。
实施NetworkPolicy的核心原则:从数据层开始,自外向内,渐进实施;先观察后收紧,避免一刀切导致业务中断;默认全拒绝是目标状态,但要确保所有必要流量(尤其是DNS)在加拒绝规则前都已经被明确放行。
NetworkPolicy配合RBAC,构成了K8s安全体系的两条腿:RBAC控制对K8s API的访问,NetworkPolicy控制Pod间的网络通信,两者缺一不可。
