去年我们把一套业务拆成十几个 Pod 丢进 Kubernetes,Ingress 配好、Service 配好、探针配好,一切看起来都很标准。三个月后的一个晚上,我收到 Redis 侧的告警:有个陌生的客户端在集群内网段扫端口,扫到我的 Redis 6379,用的是没有密码的默认配置,把里面两个业务库的键值翻了个遍。我第一反应是"我们没把 Redis 暴露到公网啊",查了一圈 Ingress 和 Service,发现 Redis 只监听在集群内部 NetworkPolicy 根本没配但这压根不是重点,重点是:在默认的 Kubernetes 网络模型里,同一个集群内的所有 Pod 之间,本来就是完全互通的。谁在这个集群里跑起来、跑的是谁的镜像,我一个都不知道,但它想访问谁就能访问谁。
如果你也在自建或者托管一套 Kubernetes 集群,这台机器的规格和节点规划其实是整套隔离方案的地基:节点数量、网络插件版本、网卡带宽这几项从一开始就要留出余量,真到了要收紧策略的时候再改就来不及了。可以先看看nube(全球云服务器,提供云主机与独立服务器)这类方案的节点和网络说明,把机器规格和集群规模对上,再回头设计隔离策略会踏实很多。
一、K8s 默认的网络模型:Pod 之间天生互通
先说清楚原理,不然后面所有规则都看不懂。Kubernetes 对容器网络的要求里有一条核心约定:一个集群里的所有 Pod,都被分配到同一个扁平的、虚拟的网段里,每个 Pod 有独立 IP,任意两个 Pod 之间可以直接用 IP 通信。这套模型的好处是简单,Service、Ingress、StatefulSet 这些上层抽象全部建立在这个前提之上Service 的负载均衡本质上就是在做 Pod IP 之间的转发,Ingress Controller 转发进来的流量最终也要落到某个 Pod IP 上。
默认情况下,这条约定是"只进不出"的网络:Kubernetes 只负责保证 Pod 之间能通,不会替你限制谁能访问谁。你可以用一个很简单的实验验证这一点:在任意一个 Pod 里 ping 另一个 Pod 的 IP,或者直接 curl 它的服务端口,只要网络插件是正常工作的,都会通。生产、测试、监控、日志这些 namespace 之间的 Pod,互通程度完全一样。
这就是"东西向流量"(east-west traffic)这个词的含义:流量不是从外面进来再出去,而是在集群内部从 A 服务流到 B 服务。绝大多数人对东西向流量的风险感知很低,因为外部入口那一层通常做了不少防护WAF、高防、HTTPS 证书、认证中间件。但内部这一段,在很多团队里就是一片裸地。
二、Service 和 NetworkPolicy 是两件完全不同的事
我见过不止一次有人把这两个搞混。Service 解决的是"怎么找到服务":它给你一个稳定的 ClusterIP 或者一个 DNS 名,背后挂一组 Pod,帮你做负载均衡和会话保持。NetworkPolicy 解决的是"谁可以访问谁":它在网络层面放行或者拦截指定来源到指定目标的连接。
你可以把它类比成公司前台和门禁。前台(Service)决定"来的人怎么找到对应部门",门禁(NetworkPolicy)决定"哪些人能进哪层楼"。前台装得再漂亮,没有门禁不代表任何人都不能上楼。有些人以为"我的 Redis 只配了 ClusterIP Service,所以外部访问不到"外部确实访问不到,但集群内任何一个 Pod 都能直接按 Pod IP 连它,根本不需要经过 Service。
还有一个必须知道的细节:NetworkPolicy 的执行不是 Kubernetes 自己做的,而是交给 CNI 网络插件。API Server 只是把策略对象存下来,真正的拦截发生在节点的网卡层面,靠 CNI 插件的 agent 往 iptables 或者 eBPF map 里写规则。所以如果你用的 CNI 插件不支持 NetworkPolicy,你在集群里 kubectl get networkpolicy 也能看到对象,YAML 也没有语法错误,但流量照样畅通无阻。这一点很坑,因为"对象创建成功"给了人虚假的安全感。
三、我的那次事故:一个没有密码的 Redis 被同集群的 Pod 读走了
事情发生在凌晨,告警是 Redis 侧的连接数突增。我登录跳板机进集群,先查 Redis 的 CLIENT LIST,发现客户端地址是一串 10.244.x.x 的 Pod IP,属于集群网段,但那个 Pod 不是我部署的任何东西。
后来查清了:那个集群的节点上跑着好几个团队的业务,其中一个同事在做压力测试,用的是一个从网上抄来的 redis:latest 镜像,compose 文件里既没设密码也没绑限制。而这个 Pod 就躺在同一个扁平网段里,扫到我的 Redis IP 和默认端口,直接就连上了。整个过程不需要任何凭证,因为 Redis 默认不带认证。
这次事故让我真正理解了一件事:容器编排解决的是"怎么把应用跑起来",不解决"应用之间该不该互相访问"。你所有 namespace 之间的信任关系,默认全是开的。
四、NetworkPolicy 的两种类型和三个关键字段
策略有两个类型:Ingress(控制入向流量谁能进来)和 Egress(控制出向流量谁能出去)。绝大多数场景大家只配了 Ingress,因为外部流量进不来是主要诉求。但 Egress 往往更值钱一个被攻破的 Pod 如果不能主动外连,它连数据回传都做不到,损失会小得多。
一个策略的骨架是三块:podSelector(作用对象)、matchLabels(匹配来源或目标)、ports(放行的协议和端口)。举个最简单的例子,只允许特定 namespace 里的 Pod 访问 Redis 的 6379:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: redis-ingress
spec:
podSelector:
matchLabels:
app: redis
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: backend
ports:
- protocol: TCP
port: 6379
- to:
- podSelector:
matchLabels:
app: web
ports:
- protocol: TCP
port: 6379
五、四个最容易踩的坑
坑一:以为"不写策略"就是"禁止访问"。在同一个 namespace 里,只要没有任何 NetworkPolicy 选中这个 Pod,它就是全放行的。但一旦你创建了任何一条 选中该 Pod 的 Ingress 策略,这个 Pod 就立刻变成"白名单模式"没被策略明确放行的流量全部拒绝。这是最反直觉的一点,也是我改策略改到业务断连的头号原因:本来 Pod 之间随便通,加了一条只放行 80 端口的策略,结果数据库连不上了。
坑二:只写 podSelector 不写 namespaceSelector,隔离等于没做。一个只写了 podSelector 的 from 段,作用范围是所有 namespace 里的匹配 Pod。你以为"只允许我自己这个 namespace 访问",实际上全集群符合条件的 Pod 都能进。这是我在复盘里看到最多的一处配置疏漏。
坑三:DNS 出口没放行,策略一收紧服务全挂。Pod 要解析 redis.default.svc.cluster.local 这种域名,走的是 CoreDNS 的 53 端口。如果你的 Egress 策略写了"只允许访问特定几个 Pod",而这些 Pod 的 label 恰好没包含 CoreDNS,那么域名解析全部失败,表现是应用大量超时、报 connection reset 或者 no such host,看起来像服务挂了,其实是 DNS 通道被自己掐断。
坑四:CNI 不支持策略,或者策略在节点上没有真正落地。前面提过,API 层面创建成功不等于节点在执行。可以到节点上看 iptables -t nat -L KUBE-NODEPORTS 或者用 calicoctl、cilium 的命令行工具 dump 策略状态,看看规则是不是真的下到网卡上了。
六、怎么验证策略真的生效
别靠猜。我一般用三步验证:
第一步,先在一个被允许的 Pod 里发起访问,确认业务没被误伤;第二步,在一个被禁止的 Pod 里 curl 目标服务,预期是超时(connection timeout)而不是 connection refused被 NetworkPolicy 拦下的流量通常直接丢弃,所以是超时;如果返回 refused,说明网络层压根没拦你,问题在别处。第三步,删除策略再复测一次,确认能通,说明这条策略确实是那个"关键开关",而不是别的原因导致的不通。
另外提醒一句,绝大多数 CNI 插件在策略生效和更新上有几秒到几十秒的延迟,所以策略改完别立刻断言"没生效",等一下再测。
七、我现在的隔离基线
总结一下我目前给每个业务集群定下的规矩:
1. 每个 namespace 至少有一条 default-deny-all 的基础策略,明确拒绝所有入向和出向;
2. 每个服务单独一条策略,只放行业务真正需要的那几个端口;
3. Egress 至少保证 DNS(53/UDP+TCP)能通,以及监控、日志上报的出口;
4. 数据库类服务启用认证,口令走 Secret 挂载,不裸奔在 compose 文件里;
5. 每次改策略前,先在测试集群用同样的 CNI 版本验证一遍;
6. 策略文件进 Git,走 PR 评审,不允许 kubectl 直接改完就上线。
如果你现在还是"什么都能通"的裸奔状态,建议先从最敏感的那个服务下手通常是数据库、缓存、或者带密钥的后台服务。先加一条只放行必要来源的 Ingress 策略,再慢慢往下补 Egress,这样即使策略写错了,爆炸半径也有限。

验证策略最直接的办法,就是从一个本该被拒绝的 Pod 里发起真实连接请求,看它是不是真的被挡在网络层。

NetworkPolicy 的作用就是把默认的全互通,改成按需放行。策略对象建好之后,真正的拦截动作是在节点网卡上完成的。

集群规模越大,节点上的策略规则越多,对网络插件和网卡的处理能力要求也越高。这也是为什么很多团队在自建集群时会把节点规格和 CNI 选型当成头等大事。

策略文件一定要进版本库、走评审。我们吃过亏:有人为了临时排查把一条策略删了,忘了加回来,那个窗口期里整个集群又变成了裸奔状态。
节点规格和网络的选型同样值得提前规划。集群要扩容、要做节点隔离、要把高敏感服务放到专门的机器上,这些都需要节点层面的资源余量和独立网络规划,tnahosting(全球云服务器,含 KVM SSD 与独立服务器)这类把云主机和独服放在同一套产品线里的方案比较方便,节点扩容时可以按隔离等级分批采购,不用等到出事再临时找机器。


喜欢
顶
难过
囧
围观
无聊





