1. 答案先说:默认全放行,策略靠“建”不靠“默认”
先把结论摆在这里:Kubernetes 中 Pod 之间的默认网络策略是“允许所有”,也就是全部放行。只要你没有显式创建任何 NetworkPolicy 资源,集群内任意两个 Pod 之间都可以自由通信,不管它们在哪个节点、哪个命名空间。
这个问题我在面试里被人问过,也在生产环境踩过坑。很多人一听到“防火墙”“网络策略”这些词,下意识会以为 Kubernetes 天生自带一套类似 VMware 安全组、AWS Security Group 那样的默认拒绝机制。但这个理解是错的。Kubernetes 本身只是一个编排平台,它定义了 NetworkPolicy 这套 API 资源,但“策略是否真正执行”取决于你装了什么 CNI 网络插件,而“默认是否拒绝”则取决于你有没有创建策略对象。
打个比方:Kubernetes 就像小区物业,NetworkPolicy 就是物业贴出的管理规定。规定没贴出来之前,业主之间互相串门不受限制;规定贴出来了,才按照白名单放行。如果你想让 Pod 之间默认互不相通,必须主动创建策略,把“默认允许”变成“默认拒绝”。什么都不写,就等于裸奔。
所以这个问题的完整答案其实是两句话:
- 默认行为:Kubernetes 集群内 Pod 之间允许所有通信。
- 安全建议:生产环境务必通过 NetworkPolicy 或服务网格等手段,把默认策略收紧为白名单制。
接下来我会把“为什么默认是允许”“策略是怎么生效的”“怎么一步步收紧策略”“踩过的坑有哪些”全部拆开讲一遍。
2. 为什么默认是“允许”?这要从 Kubernetes 的网络模型说起
2.1 扁平网络:每个 Pod 都是“一台独立主机”
Kubernetes 的网络模型有一条硬性规定:所有 Pod 共享一个扁平的、无 NAT 的地址空间。也就是说,Pod 的 IP 在集群内部都是真实可达的,不需要做地址转换,也不需要经过什么网关代理。
这个设计的好处是:服务发现和流量编排变得极其简单。A Pod 要访问 B Pod,源 IP 直接就是 A 的 IP,B 返回的包也能直接回到 A。负载均衡、监控、链路追踪这些工具在这种模型下都能天然工作。但代价就是:网络层面没有任何天然的隔离屏障。Pod 和 Pod 之间“裸奔”是默认状态。
你可以把 Kubernetes 集群想象成一个大型局域网,每台服务器都直接接在同一台交换机上,所有端口互通。默认情况下,没有 ACL,没有 VLAN 隔离,任何两台机器都能互相 ping 通、互相访问端口。Kubernetes 的默认网络就是这个样子。
2.2 为什么不默认加一条“拒绝所有”规则?
这是一个很关键的“为什么”。很多安全导向的设计者第一次接触 Kubernetes 时都会问:为什么默认不是 deny all?难道不应该像防火墙那样,先拒绝一切,再逐步放行吗?
答案涉及到 Kubernetes 早期的设计哲学和生态兼容性。Kubernetes 在 1.3 版本引入 NetworkPolicy 时,社区面对的现实是:市面上有大量 CNI 插件,Flannel、Weave、Calico、Cilium、Kube-Router,各自实现方式完全不同。有的插件天生不支持策略;有的插件只支持部分策略。如果 Kubernetes 默认就带一条“拒绝所有”规则,那么所有不支持 NetworkPolicy 的插件都会直接瘫痪——集群里任何流量都走不通。
所以 Kubernetes 选择了最稳妥的方案:默认不给任何限制,策略由用户显式声明,由 CNI 插件执行。你创建了 NetworkPolicy,支持策略的插件就来执行;你不创建,那大家就保持互通。这是为了兼容生态、保证集群开箱即用的妥协,也是今天这个“默认允许”的根源。
2.3 一个常见误区:命名空间不等于隔离
略微接触过 Kubernetes 的人还会有一个误解:既然有命名空间(Namespace)这个概念,是不是把 Pod 放在不同命名空间里,它们之间天然就隔离了?
并不是。命名空间只是逻辑上的分组,用于资源管理和权限控制,不提供网络隔离。不同命名空间里的 Pod 默认依然可以互相访问。你创建一个 Pod 在 ns-a,另一个 Pod 在 ns-b,只要你知道对方的 IP 或 Service 名称,直接就能访问。想通过命名空间做网络隔离,还是得靠 NetworkPolicy 里的 namespaceSelector 去声明。
3. NetworkPolicy 到底怎么工作?核心机制一次讲透
3.1 策略对象长什么样
NetworkPolicy 是一个 Kubernetes API 资源,最简形式长这样:
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: deny-all namespace: default spec: podSelector: {} policyTypes: - Ingress这个 YAML 的含义是:在 default 命名空间内,对所有 Pod(podSelector 为空对象,表示匹配全部)应用一条 Ingress 策略。policyTypes 里只写了 Ingress,但注意:只要 policyTypes 里包含 Ingress,且没有填写任何 ingress 规则,这条策略就会拒绝所有入站流量。
这里有一个重要的语义要理解:NetworkPolicy 不是“防火墙规则列表”那种“先匹配先执行”的模式,而是白名单模型。每一条 ingress 规则相当于一个“允许条件”,只要流量匹配任意一个条件,就放行;如果策略存在但没有任何允许条件,那所有流量都被拒绝。
3.2 匹配字段:podSelector、namespaceSelector、ipBlock
NetworkPolicy 的规则匹配有三种维度,掌握了这三种就能拼出绝大部分场景:
podSelector:选择当前命名空间内的某个或某组 Pod。写法是标准的 label selector,比如app: nginx就只匹配打了这个标签的 Pod。
namespaceSelector:选择来源(Ingress)或目标(Egress)所在的命名空间。这个字段配合同命名空间内某个 Pod 选择器,可以实现“只允许来自某个命名空间的流量访问我”。
ipBlock:直接按 IP 段匹配。这个字段比较底层,一般用于匹配集群外部的 IP 段、节点 IP 段,或者某些无法通过标签选中的场景。注意:ipBlock 匹配的是源 IP 或目标 IP,在 Kubernetes 网络模型下,Pod 的 IP 是多变的,所以能用标签解决的就别用 IP。
3.3 Ingress 和 Egress:两个方向,分开控制
NetworkPolicy 支持两个方向:Ingress(入站)和 Egress(出站)。入站策略控制“谁能访问我”,出站策略控制“我能访问谁”。
以防火墙来类比,Ingress 相当于入站规则,限制外部发往本机的流量;Egress 相当于出站规则,限制本机发往外部的流量。很多人在配置时只关注了 Ingress,忘了 Egress,结果被各种“诡异”的访问失败折腾半天。
举个真实例子:我给一个后端服务配了“只允许前端访问”的 Ingress 策略,后端 Pod 的入站流量确实被限制住了。但随后我发现后端 Pod 一直报错,无法连接数据库。排查了半天才意识到:我只限制了入站,没有限制出站。但这里的核心问题是——当你为一个 Pod 创建了任何一条包含 Egress 的 NetworkPolicy 时,这个 Pod 的所有出站流量默认都会被拒绝,除非你在 egress 规则里显式列出允许的目标。
这个行为和 iptables 的默认策略非常像:一旦某条链上有规则了,未匹配的包就走默认策略;而 Kubernetes 里“有策略即默认拒绝”。
3.4 policyTypes 的隐藏含义
再说一个容易被忽略的点。policyTypes 字段里的 Ingress / Egress,不仅仅表示“我要配置这个方向的策略”,更关键的是它决定了默认兜底行为:
- 如果 policyTypes 里包含 Ingress,那么未匹配任何 ingress 规则的入站流量全部拒绝。
- 如果 policyTypes 里包含 Egress,那么未匹配任何 egress 规则的出站流量全部拒绝。
- 如果 policyTypes 里两个都没写,插件会默认按 spec 中是否有 ingress / egress 字段来推断。
这也就解释了为什么很多“策略不生效”或者“网络完全断掉”的故障,都是因为多写了或漏写了 policyTypes 导致的。你本来只想加一条白名单规则,但一旦 policyTypes 里出现了 Egress,你就等于同时把出站全部封锁了。
4. 实操:从“默认全放行”一步步收紧到白名单制
4.1 环境准备:确认你的 CNI 支持策略
动手之前先确认一个关键前提:你的 CNI 插件必须支持 NetworkPolicy。常见的支持情况如下:
| CNI 插件 | 是否支持 NetworkPolicy | 备注 |
|---|---|---|
| Calico | 支持 | 策略能力最强,支持全局策略、命名空间策略 |
| Cilium | 支持 | 基于 eBPF,支持 L3-L7 层策略 |
| Weave Net | 支持 | 支持基础策略 |
| Flannel | 不支持 | 默认仅提供 Overlay 网络,无策略能力 |
| Kube-Router | 支持 | 基于 iptables 实现 |
如果你的集群用的 Flannel,而且没装额外组件,那后面的所有配置都不会生效。你创建 NetworkPolicy 不会报错,但流量照样全放行——因为底层插件根本不认识这套 API,直接忽略掉。这是生产环境最容易被忽视的隐患之一。
验证方法也很简单:创建一条“拒绝所有入站”的策略后,从一个 Pod 去访问另一个 Pod,如果依然能通,就说明 CNI 不支持或者组件没装对;如果立刻不通,说明策略生效了。
4.2 第一步:建立全命名空间默认拒绝策略
在给业务配置精细白名单之前,我的习惯是先铺一层“安全网”——所有命名空间先默认拒绝入站。这样即使有人忘了给新服务配策略,至少它不会被外部随意访问。
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny-all namespace: your-namespace spec: podSelector: {} policyTypes: - Ingress把这个 YAML apply 进去,your-namespace下的所有 Pod 就都“关上门”了。此时任何入站流量都会被拒绝,包括来自同命名空间其他 Pod 的流量、来自其他命名空间的流量、来自 Service 的流量(这一点尤其要注意)。不过集群内部访问 kubelet 的健康检查探针流量有时不在 NetworkPolicy 管辖范围内,但这不是你该依赖的。
4.3 第二步:放行某组 Pod 给另一组 Pod 访问
现实中常见的拓扑是:前端 Nginx 要访问后端 API,后端 API 要访问数据库,前端不能直接访问数据库。我们用三个 Deployment 和一套策略把它模拟出来。
先部署测试服务:
kubectl create deployment nginx-front --image=nginx kubectl create deployment api-backend --image=nginx kubectl create deployment db-backend --image=nginx给每个 Deployment 打上标签,方便策略匹配:
kubectl label deployment nginx-front app=front tier=web kubectl label deployment api-backend app=api tier=backend kubectl label deployment db-backend app=db tier=data现在创建一条策略,允许app=front的 Pod 访问app=api的 Pod 的 80 端口:
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-front-to-api namespace: default spec: podSelector: matchLabels: app: api policyTypes: - Ingress ingress: - from: - podSelector: matchLabels: app: front ports: - protocol: TCP port: 80这条策略的语义是:匹配app=api的 Pod,只允许来自app=front的 Pod 访问其 80 端口。其他来源、其他端口一律拒绝。
验证连通性。进入 front Pod:
kubectl exec -it deploy/nginx-front -- curl http://api-backend-svc:80如果通了,说明策略放行生效。再试着从 db Pod 去访问 api Pod 的 80 端口:
kubectl exec -it deploy/db-backend -- curl http://api-backend-svc:80如果这个请求超时或被拒绝,说明默认拒绝确实生效了。
4.4 第三步:精细化——限制访问特定端口
很多人会把 NetworkPolicy 的 port 字段忽略掉,但生产环境强烈建议加上。原因很简单:如果你只限制来源、不限制端口,那等于告诉对方“你只要能连上我,什么服务都能直接用”。比如上一步的规则如果去掉ports字段,app=front的 Pod 就不仅能访问你的 80 端口,还能访问 Redis 的 6379、MySQL 的 3306,哪怕这些端口根本没有对外提供 Service,只要 Pod IP 直达就都能碰到。
把端口加入策略后,即使来源匹配,端口不匹配也会被拒绝。这就像防火墙的“五元组”规则:源 IP、目标 IP、源端口、目标端口、协议,全部匹配才算放行。
4.5 第四步:跨命名空间隔离
如果要实现“只允许前端命名空间访问后端命名空间”,就需要用到 namespaceSelector:
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-web-ns-to-api namespace: backend spec: podSelector: matchLabels: app: api policyTypes: - Ingress ingress: - from: - namespaceSelector: matchLabels: env: web podSelector: matchLabels: app: front注意这里的写法:from下面既有 namespaceSelector 又有 podSelector,它们之间是AND关系。也就是“来自 env=web 这个命名空间,并且 pod 标签为 app=front 的 Pod”。如果你在 from 下面写两个独立条目,那就是 OR 关系。
这个细节特别容易出错。很多人想表达“来自某个 namespace 的某个 Pod”,结果把两个字段写成了两条独立规则,导致放行范围远超预期。我实际遇到过有人因为这里写错,把整个命名空间下所有 Pod 都放行到了生产数据库。
4.6 第五步:出站策略(Egress)——控制 Pod 能访问谁
出站策略通常用在两个场景:一是防止 Pod 被入侵后向内网横向扩散,二是防止 Pod 主动外连(比如矿池、数据外传)。一个严格控出站的 Pod,等于既锁了门又锁了窗。
下面这条策略允许app=api的 Pod 只访问app=db的 Pod 的 5432 端口,以及 kube-system 命名空间下 kube-dns 的 53 端口(域名解析),其他出站全拒绝:
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: api-egress namespace: default spec: podSelector: matchLabels: app: api policyTypes: - Egress egress: - to: - podSelector: matchLabels: app: db ports: - protocol: TCP port: 5432 - to: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: kube-system podSelector: matchLabels: k8s-app: kube-dns ports: - protocol: UDP port: 53这里有一个很多人第一次配置时必踩的坑:如果没有放行 DNS,Pod 就解析不了域名,所有依赖 Service 名称的访问都会失败。因为 Service 域名解析依赖 CoreDNS,CoreDNS 跑在 kube-system 命名空间,你的出站策略一旦把 Pod 的出站全部掐断,DNS 查询的 UDP 53 包也发不出去。所以配置 Egress 时,必须记得给 CoreDNS 留一条放行规则。
4.7 完整的“三层隔离”策略组合实例
把前面几步拼起来,一套比较稳妥的隔离策略如下:
| 命名空间 | Pod 标签 | Ingress 策略 | Egress 策略 |
|---|---|---|---|
| default | app=front | 仅允许来自 ingress-nginx 或外部 LB | 允许访问 app=api 的 80 端口 + DNS |
| default | app=api | 仅允许来自 app=front | 允许访问 app=db 的 5432 + DNS |
| default | app=db | 仅允许来自 app=api | 不允许任何出站(如需备份再额外加白名单) |
再加上每个命名空间一条default-deny-all作为兜底,这个模型已经能把绝大多数生产环境的横向移动风险挡在门外。
5. 常见问题与排查技巧实录
5.1 明明创建了策略,流量却还是全通
这是最常见的问题。第一步先确认 CNI 是否支持 NetworkPolicy。如果用的是 Flannel,直接换 Calico 或 Cilium,然后再测试。第二步看 NetworkPolicy 是否创建成功:
kubectl get networkpolicy -n <namespace>如果显示出来了但流量还是通,可能是插件组件的 Pod 没有正常运行,比如 calico-kube-controllers 或 calico-node 处于 CrashLoopBackOff。查看组件日志:
kubectl logs -n kube-system <calico-pod-name>另一个容易被忽视的点:策略匹配的是 Pod,不是 Service。如果访问方通过 Service 访问目标,流量先到达 Service 的 ClusterIP,再转发到后端 Pod。此时 NetworkPolicy 看到的是转发后到达 Pod 的源 Pod IP,所以你在策略里写的来源标签,必须匹配实际发起访问的 Pod,而不是匹配 Service。这也是很多人测试时明明来源没问题,却总被拒绝的原因。
5.2 启用默认拒绝后,健康检查挂了
如果给 Deployment 配了 livenessProbe / readinessProbe,而这些探针的流量属于 Ingress,那默认拒绝策略会把探针也挡在外面。这时候有一个好消息:kubelet 的探针流量理论上应该绕过 NetworkPolicy,但很多 CNI 插件并没有做这个豁免,我实际遇到过 Calico 下探针被策略挡掉的案例。
解决思路有两种:
- 给探针流量的来源单独放行,但来源是节点 IP,需要查节点网段,用 ipBlock 放行。
- 调整策略粒度,别做全命名空间默认拒绝,而是只对需要保护的高危服务做精细白名单。
我个人更推荐后者。全命名空间默认拒绝虽然视觉上很安全,但带来的维护成本和排查成本都很高,尤其是当集群里应用很多、探针类型各异的时候。生产环境的策略应该按服务重要性和攻击面来分级,不要一刀切。
5.3 DNS 解析突然失败
症状:创建了 NetworkPolicy 后,Pod 内 curl http://some-service 能拿到 IP 但连不上,或者直接报“Temporary failure in name resolution”。
原因几乎总是 Egress 策略没有放行 DNS。解决方案就是像上面那样,在 egress 里加一条 UDP 53 的放行规则。这里要额外提醒一句:DNS 流量既有 UDP 也有 TCP(当响应超过 512 字节时走 TCP),建议 UDP 53 和 TCP 53 都放行,免得遇到大 DNS 响应时卡住。
5.4 策略能匹配,但端口写错导致总拒绝
端口字段只写数字,不带协议字段默认是 TCP。如果你的服务跑在 UDP 上,只写端口不写 protocol 就会导致策略永远不匹配。我排查过一个 syslog 采集服务,怎么都收不到数据,最后发现就是 NetworkPolicy 里写的是默认 TCP,而 syslog 走的是 UDP。
5.5 大规模集群下策略数量爆表,怎么管理
当集群里微服务数量多、策略条目动辄上百条时,人工维护 YAML 会非常痛苦。我的建议是:
- 使用 Helm Chart 或 Kustomize 统一管理策略,所有策略纳入版本控制。
- 遵循“命名空间默认拒绝 + 服务级白名单 + 全局例外”的三层模型,避免每条策略都精确到 Pod。
- 用 Cilium 的 CCNP(Clusterwide NetworkPolicy)处理跨命名空间的全局规则,减少重复配置。
- 定期用
kubectl get networkpolicy -A导出策略清单,人工审计哪些策略已无关联 Pod 使用,及时清理。
5.6 排查异常流量的实操工具
当某个 Pod 之间通信被策略挡掉时,靠肉眼看 YAML 有时候很难定位。我的标准排查流程:
# 1. 查看目标 Pod 的策略 kubectl describe networkpolicy -n <namespace> # 2. 进入源 Pod 测试连通性(先确认是策略问题还是应用问题) kubectl exec -it <source-pod> -- curl -v <target-ip>:<port> # 3. 查看 CNI 插件是否对该流量做了 drop(以 Calico 为例,直接进节点查 iptables) iptables -L -n -v | grep <target-pod-ip>如果看到链里有一条 DROP 规则且匹配计数在增加,说明策略在生效,是规则没写对。如果计数为零,那流量可能根本没到这台节点,需要检查网络插件本身的路由。
6. 关于“默认允许”这事,我的个人体会
把整个问题梳理完之后,我想说点题外话。很多人把“Kubernetes 默认允许所有流量”当作一个缺陷甚至一个安全事故,但我这些年做集群运维下来的感觉是:这更像一个“默认信任,需要主动加固”的模型,而不是“默认安全”的模型。Kubernetes 把网络的自主权完全交给了使用者,你可以选择裸奔,也可以选择层层加锁。这种设计给了不同规模、不同安全级别的团队极大的灵活性。
如果你现在管理的是一个测试环境,Pod 就那么几个,跑的都是自己的开发代码,那默认允许完全够用,强行加策略反而给开发流程添乱。但如果你管理的是生产环境,哪怕只有几个服务,我都建议至少把默认拒绝策略铺上。这不需要很多成本,却能避免很多“我以为别人访问不了”的错觉。
另外一个小建议:网络策略最好跟着应用一起部署,而不是事后补。每次上线新服务,都顺手把它的白名单策略写进同一个 Helm Chart 或 Kustomize 目录里,形成“新服务默认带策略”的规范。我见过太多团队都是出了问题才想起来安全,结果一边救火一边现写规则,最后规则写成了筛子。
最后再分享一个我常用来验证策略的土办法:在集群里跑两个临时 Pod,一个带app=client标签,一个不带,分别去访问目标服务。如果带标签的能通、不带标签的不能通,说明策略生效了;如果两个都能通,说明策略没匹配上;如果两个都不能通,那多半是默认拒绝把正常流量也挡了。这个测试方法简单粗暴,但比看一百遍 YAML 都管用。