Repository Reading Site
第五课:NetworkPolicy、零信任网络与流量边界
上一课我们解决的是: 这一课要解决的是: 这两个问题听起来很像,但本质完全不同。 如果说上一课是“道路系统”,那这一课就是“交通管制系统”。 --- 很多初学者以为: 这是错的。 因为在 Kubernetes 里,能不能到达目标,至少受三类因素影响: 1. 路有没有 2. 服务映射对不对 3. 策略允不允许 前两者解决的是“技术可达性”,最后一者解决的是“访
第五课:NetworkPolicy、零信任网络与流量边界
为什么这节课是网络课的下半场
上一课我们解决的是:
流量怎么到达目标。
这一课要解决的是:
流量该不该到达目标。
这两个问题听起来很像,但本质完全不同。
上一课的关键词
- Pod IP
- Service IP
- DNS
- kube-proxy
- Calico IPIP
- WireGuard
- 路由
- NAT
这一课的关键词
- 零信任
- 默认拒绝
- 允许白名单
- Ingress
- Egress
- namespaceSelector
- podSelector
- ipBlock
如果说上一课是“道路系统”,那这一课就是“交通管制系统”。
先讲一个最容易被误解的点
很多初学者以为:
- 只要 Pod 到 Pod 网络是通的
- Service 也能解析
- 那么访问一定应该成功
这是错的。
因为在 Kubernetes 里,能不能到达目标,至少受三类因素影响:
- 路有没有
- 服务映射对不对
- 策略允不允许
前两者解决的是“技术可达性”,最后一者解决的是“访问授权边界”。
NetworkPolicy 就是第三类。
Kubernetes 默认网络模型:几乎是“全通”
这是学习 NetworkPolicy 必须先接受的现实。
默认情况下
如果你什么都不配:
- 同 namespace Pod 之间可通信
- 跨 namespace Pod 之间也可通信
- 绝大多数 Pod 可以自由出站
这在实验环境里方便,在生产环境里危险。
为什么 Kubernetes 默认不帮你自动隔离
因为 Kubernetes 的核心目标是:
- 提供通用编排平台
而不是默认替你决定:
- 哪些业务应该互相隔离
- 哪些系统可以访问数据库
- 哪些 namespace 之间该有边界
这些属于平台治理与安全策略层,而不是最小网络模型本身。
这意味着什么
如果你不主动设计边界,那么集群一旦规模变大,就会自然演化成:
大平层网络。
也就是:
- 谁都能找谁
- 谁都能试着扫谁
- 谁都可能误连或误打
这和零信任完全相反。
零信任网络到底是什么
零信任不是一句时髦口号,它的核心思想其实很朴素:
默认不信任,按需最小放行。
翻成 Kubernetes 网络语言就是:
- 默认不让 Pod 随意互访
- 只有明确业务需要的流量才放行
- 放行尽量精确到:
- 谁可以来
- 来哪个端口
- 谁可以出
- 出到哪里
这和传统内网思维的区别
传统内网思维:
- 都在内网,默认可信
零信任思维:
- 在内网也不默认可信
- 每条流量都应该有理由
这就是为什么 NetworkPolicy 在现代平台里非常重要。
先给你一个最重要的认知模型
Ingress Policy 管的是“谁能打进来”
它作用在目标 Pod 身上。
回答的问题是:
哪些来源可以访问这个 Pod?
Egress Policy 管的是“我能打出去给谁”
它作用在源 Pod 身上。
回答的问题是:
这个 Pod 可以访问哪些目标?
你必须牢记一句话
Ingress 看目标,Egress 看源头。
很多人学 NetworkPolicy 时最容易混淆这个点。
这次我们不是只讲语法,而是做了完整实验
我在集群里新增了两个实验命名空间:
np-demonp-external
并创建了:
np-demo/web:Nginx 服务端np-demo/same-ns-client:同 namespace 客户端np-external/cross-ns-client:跨 namespace 客户端np-demo/web-svc:服务入口
实验文件都在:
本轮最核心的实验顺序是:
- 什么策略都不加,验证默认全通
- 加
default-deny-ingress - 加
allow-same-namespace - 用
namespaceSelector放行带特定标签的 namespace - 在客户端侧加
default-deny-egress - 只放 DNS
- 再放业务 HTTP 端口
这条实验链会让你真正理解:
- 策略是怎么从“全通”逐步收紧,再按需精确打开的
实验一:什么都不加时,默认是全通
真实结果
在没有任何实验策略之前,我们验证了:
same-ns-client -> http://web-svc成功cross-ns-client -> http://web-svc.np-demo.svc.cluster.local也成功cross-ns-client可以正常 DNS 解析web-svc.np-demo.svc.cluster.local
这就是 Kubernetes 默认网络模型的现实:
跨 namespace 默认也不是隔离的。
这个默认值为什么危险
因为一旦集群里有:
devstagingprod- 第三方组件
- 多团队业务
默认全通意味着:
- 测试环境误打生产
- 应用直接扫数据库
- 监控组件无意探测敏感服务
- 某个被攻陷的 Pod 横向探测整个集群
这就是为什么成熟平台迟早都会走向 default deny。
NetworkPolicy 的工作方式:它不是“全局开关”,而是“选中目标 Pod 后开始生效”
这是第一大核心原理。
podSelector 选中的,是“被保护的目标 Pod”
例如:
spec:
podSelector: {}
在某个 namespace 里表示:
- 选中这个 namespace 下所有 Pod
如果写:
spec:
podSelector:
matchLabels:
app: web
就表示:
- 只选中
app=web的 Pod
这里最容易误解的点
很多人会以为:
podSelector是“哪些 Pod 可以访问别人”
这不对。
在标准 NetworkPolicy 里,顶层 podSelector 表示:
这条策略要作用到哪些 Pod 身上。
然后你再在 ingress.from 或 egress.to 里定义谁可以来、谁可以出。
第二大核心原理:策略是“累加允许”,不是“按顺序覆盖”
这是 NetworkPolicy 最值得死记硬背的规则之一。
标准 Kubernetes NetworkPolicy 没有显式 deny 规则
标准语义是:
- 只要某方向上没有策略选中某 Pod,该方向默认全放
- 一旦有策略选中某 Pod,该方向就进入“默认拒绝 + 允许列表”模式
- 多条策略同时存在时,允许规则做并集
也就是说:
标准 NetworkPolicy 是“allow-list union”,不是“按优先级覆盖”。
这和很多防火墙习惯不同
很多人习惯于:
- 上面一条拒绝
- 下面一条允许
- 看顺序决定结果
但标准 Kubernetes NetworkPolicy 不是这个模型。
它更像:
- 先看你是不是被纳入隔离对象
- 如果是,再把所有允许项加起来
实验二:default-deny-ingress
配置长什么样
我们应用了:
核心内容是:
spec:
podSelector: {}
policyTypes:
- Ingress
注意:
- 没有任何
ingress规则
这意味着什么
在 np-demo 这个 namespace 里:
- 所有 Pod 的入站流量都开始被隔离
- 但允许列表是空的
结果就是:
- 拒绝所有 ingress
真实结果
应用之后:
same-ns-client -> web-svc超时cross-ns-client -> web-svc.np-demo.svc.cluster.local也超时
为什么这里是 timeout 而不是 connection refused
很常见的真实现象是:
- 被策略丢掉的流量,更像“没人回应”
- 所以客户端常表现为超时
但你不能把“超时”简单等同于“一定是 NetworkPolicy”,因为:
- 路由问题
- DNS 问题
- 端口没监听
也都可能表现成超时。
所以还是要结合对象状态和策略状态一起判断。
实验三:allow-same-namespace
配置长什么样
我们应用了:
核心逻辑是:
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- podSelector: {}
这条规则为什么只允许“同 namespace”
因为这里的 from 只写了:
podSelector: {}
而没有写 namespaceSelector。
在 NetworkPolicy 语义里,这表示:
当前 namespace 内,所有 Pod 都算允许来源。
真实结果
应用之后:
same-ns-client -> web-svc成功cross-ns-client -> web-svc...仍然超时
这正好证明了什么
这正好证明了两件事:
- 策略是累加允许的
- 没写 namespaceSelector 时,不会自动跨 namespace 放行
实验四:namespaceSelector 放行带标签的命名空间
我们怎么做的
先给 np-external 打标签:
kubectl label namespace np-external access=allowed --overwrite
然后应用:
核心逻辑:
ingress:
- from:
- namespaceSelector:
matchLabels:
access: allowed
ports:
- port: 80
protocol: TCP
这里的语义是什么
对 np-demo 里的 app=web Pod 来说:
- 允许来自带
access=allowed标签的 namespace 的流量 - 但只允许 TCP 80 端口
真实结果
打标签并应用策略之后:
cross-ns-client -> web-svc.np-demo.svc.cluster.local成功
这说明了什么
这说明我们不是按 IP 白名单在放行,而是按:
- namespace 身份
在放行。
这在多团队、多环境平台里非常有价值。
一个非常重要的语义陷阱:namespaceSelector 和 podSelector 的 AND / OR
这点如果你不学清楚,后面很容易写出“看起来对、实际上错”的策略。
情况一:同一个 from 项里同时写
例如:
from:
- namespaceSelector:
matchLabels:
team: data
podSelector:
matchLabels:
app: api
语义是:
来自满足
team=data的 namespace,且 Pod 本身满足app=api
也就是 AND。
情况二:写成两个 from 项
例如:
from:
- namespaceSelector:
matchLabels:
team: data
- podSelector:
matchLabels:
app: api
语义变成:
来自满足 namespace 条件的任何 Pod,或者来自当前 namespace 中满足
app=api的 Pod
也就是 OR。
为什么这很重要
因为很多“策略明明写了但流量不按预期”的问题,都来自这里。
这是一条典型的专家知识点。
Ingress 方向你现在应该怎么理解
到这里,你可以把 ingress Policy 总结成一句话:
我在目标 Pod 这边列一个白名单,允许谁从哪来、打我哪个端口。
也就是说,Ingress Policy 的思考顺序永远是:
- 我要保护哪些 Pod
- 这些 Pod 允许谁打进来
- 允许打哪些端口
不要反过来想。
现在进入更容易忽略的 Egress
很多人以为:
- 服务端放行了
- 客户端就自然能访问
这只在客户端没有 egress 隔离时成立。
一旦你对客户端加了 egress policy,事情就变了。
Egress 的本质
Egress Policy 是在源 Pod 侧说:
我允许自己访问哪些目标。
这意味着什么
即使目标 Pod 那边完全欢迎你,源头如果被 egress policy 卡住,连接仍然不会成功。
这就是为什么完整连接语义要同时考虑:
- 目标端 ingress
- 源端 egress
实验五:default-deny-egress
配置长什么样
我们应用了:
核心逻辑:
spec:
podSelector:
matchLabels:
role: client
policyTypes:
- Egress
没有任何 egress 允许规则。
这意味着什么
在 np-external namespace 里:
- 所有
role=client的 Pod 出站流量全部被隔离 - 允许列表为空
结果就是:
- 什么都出不去
真实结果
应用后,cross-ns-client 出现两个非常典型的现象:
1. nslookup kubernetes.default.svc.cluster.local 失败
错误:
connection timed out; no servers could be reached
2. wget http://web-svc.np-demo.svc.cluster.local 失败
但这次报的不是 timeout,而是:
bad address 'web-svc.np-demo.svc.cluster.local'
为什么这次 HTTP 报的是 bad address
因为这次连接还没走到 HTTP。
它先卡死在 DNS 解析阶段了。
这就是为什么:
DNS 本身也是一种网络流量,它也会被 egress policy 拦住。
这点在生产里极其重要。
很多团队一上来做 default-deny-egress,结果发现:
- 应用突然“所有外部访问都坏了”
往往第一刀砍死的就是 DNS。
实验六:只恢复 DNS,不恢复业务流量
配置长什么样
我们应用了:
核心逻辑是:
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
这条规则在说什么
允许 role=client 的 Pod:
- 访问
kube-systemnamespace 中 - 标签为
k8s-app=kube-dns的 Pod - 端口只允许 TCP/UDP 53
真实结果
应用后:
nslookup web-svc.np-demo.svc.cluster.local恢复成功- 但
wget http://web-svc...仍然 timeout
这说明了什么
这完美说明:
- DNS 和 HTTP 是两类不同流量
- 你可以只放 DNS,不放业务流量
- Egress Policy 可以精确到“只允许解析,不允许访问”
这就是零信任设计的真正力量。
实验七:再放行业务 HTTP 出站
配置长什么样
我们应用了:
核心逻辑:
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: np-demo
podSelector:
matchLabels:
app: web
ports:
- protocol: TCP
port: 80
这条规则在说什么
允许 np-external 里的客户端:
- 去
np-demonamespace - 访问
app=web的 Pod - 只允许 TCP/80
真实结果
应用后:
wget http://web-svc.np-demo.svc.cluster.local恢复成功
到这里,整个逻辑闭环了
Ingress 侧
目标服务端:
- 默认拒绝
- 放行同 namespace
- 放行特定标签的 namespace
Egress 侧
客户端:
- 默认拒绝出站
- 先只放 DNS
- 再放业务 HTTP
这已经非常接近生产级的零信任模型了。
到底什么时候一个连接会成功
这是整节课最重要的判断模型之一。
一个连接成功,至少要满足什么
- 路由与 Service 映射正常
- DNS 正常,或者你直接用 IP
- 源 Pod 的 egress 允许
- 目标 Pod 的 ingress 允许
- 目标端口确实监听
可以把它记成一张小公式
连接成功 = 路径可达 × 源头能出 × 目标能进 × 端口在听
任何一项不满足,都不通。
这也是为什么网络排障不能只盯着一边。
标准 NetworkPolicy 到底能匹配什么
标准 networking.k8s.io/v1 NetworkPolicy,主要是 L3/L4 维度。
常见匹配维度
1. podSelector
匹配 Pod 标签。
2. namespaceSelector
匹配 namespace 标签。
3. ipBlock
匹配 CIDR,例如:
ipBlock:
cidr: 10.0.0.0/8
except:
- 10.96.0.0/12
适合:
- 放行某些外部网段
- 限制特定网段
4. ports
按 TCP/UDP 和端口范围控制。
标准 NetworkPolicy 不能直接做什么
它不能直接做:
- 按 HTTP path 放行
- 按域名放行
- 显式 deny 优先级
- 有序规则链
这些通常需要更强的策略体系,例如:
- Calico GlobalNetworkPolicy
- Cilium L7 policy
- Service Mesh / Envoy 层
标准 NetworkPolicy 和 Calico Policy 有什么区别
你的集群里同时存在:
- 标准
networking.k8s.io/v1NetworkPolicy - Calico CRD:
crd.projectcalico.org/v1 NetworkPolicyGlobalNetworkPolicyNetworkSetGlobalNetworkSet
我们已经实际查到了这些 API 资源。
标准 NetworkPolicy 的特点
- Kubernetes 原生
- 语义简单
- 兼容性好
- 只有 allow-list 模型
Calico Policy 的增强能力
常见包括:
- 全局策略
GlobalNetworkPolicy - 显式 deny
- 顺序控制
- 更强的匹配能力
- 某些高级场景支持基于 ServiceAccount、域名等扩展能力
工程建议
如果你在做教学或基础平台设计:
- 先把标准 NetworkPolicy 学透
如果你在做大规模生产零信任治理:
- 再引入 Calico 增强策略能力
这样认知层次会更稳。
为什么说 default-deny 是零信任的第一步,而不是最后一步
很多人以为:
- 加了 default deny 就安全了
其实不对。
default-deny 只是让你开始认真设计边界
真正成熟的零信任,还需要你继续设计:
- 谁访问谁
- 哪些端口放行
- DNS 怎么放
- 外网 API 怎么放
- 监控、日志、Sidecar、探针要不要放
真正难的是白名单设计
因为平台里你要考虑:
- 应用业务流量
- DNS
- metrics
- tracing
- sidecar
- backup
- webhook
- operator
这也是为什么做网络治理,永远不是“打一条策略就完事”。
生产里最常见的几种策略设计模式
1. 每个 namespace 先加 default-deny-ingress
作用:
- 防止跨 namespace 任意访问
2. 业务 namespace 再加 default-deny-egress
作用:
- 防止应用任意扫内网或访问外部
3. 单独放 DNS
这是最容易忘,但又必须放的。
4. 明确放行业务依赖
例如:
- frontend -> backend: 8080
- backend -> redis: 6379
- backend -> postgres: 5432
5. 对监控和运维组件做专门策略
例如:
- Prometheus 抓 metrics
- 日志采集
- Admission Webhook
- Operator 控制器
这些系统流量在零信任设计里不能被忽略。
排查 NetworkPolicy 问题时,最常用的命令
一、先看策略对象本身
kubectl get networkpolicy -A
作用:
- 看全局有哪些策略
- 看在哪些 namespace
- 看
podSelector是谁
kubectl -n <ns> get networkpolicy -o yaml
作用:
- 看具体规则内容
二、看 namespace 标签
kubectl get ns --show-labels
作用:
- 排查
namespaceSelector为什么没匹配上
这是非常高频的一步。
三、从客户端 Pod 里发请求
kubectl exec <pod> -- nslookup <service>
用途:
- 看 DNS 是否被挡
kubectl exec <pod> -- wget -qO- -T 3 http://service
或:
kubectl exec <pod> -- curl -v http://service
用途:
- 看 HTTP 是否被放行
kubectl exec <pod> -- ping <ip>
用途:
- 快速看三层 reachability
但要注意:
- ping 通不代表 TCP/HTTP 通
- 某些策略可能只放 ICMP 或根本不看它
四、看 Service 和 Endpoints
kubectl get svc,endpoints -n <ns>
用途:
- 防止把“Service 没后端”和“NetworkPolicy 拦了”混为一谈
这两个现象对用户来说都像“不通”,但根因完全不同。
五、看 CNI 插件日志
在你的集群里,重点是:
kubectl -n kube-system logs ds/calico-node
用途:
- 看 Calico 节点代理是否异常
如果策略明明存在但完全不像生效,就要考虑:
- CNI 是否支持 NetworkPolicy
- Calico 节点是否健康
遇到不通时,如何分层判断
这里给你一个非常实战的顺序。
1. DNS 是否正常
先看:
nslookup/etc/resolv.conf
2. Service 是否有后端
看:
kubectl get svc,endpoints
3. Pod 是否 Ready
看:
kubectl get podkubectl describe pod
4. Ingress / Egress 策略是否选中了相关 Pod
看:
kubectl get networkpolicy -o yaml- pod label
- namespace label
5. 方向判断对没对
问自己:
- 这是源 Pod 出不去?
- 还是目标 Pod 不让进?
6. 再考虑更底层的 CNI 和节点网络
只有上面都排完了,再去怀疑:
- Calico
- 路由
- 节点防火墙
这就是专家和初学者的区别:先分层,后下钻。
这节课最重要的 12 个结论
- Kubernetes 默认网络模型几乎是全通
- NetworkPolicy 不是全局防火墙,而是“选中 Pod 后对该方向进行隔离”
- 顶层
podSelector选中的是被保护对象,不是来源对象 - 标准 NetworkPolicy 是“累加允许”模型,不是按顺序覆盖
default-deny-ingress会让目标 Pod 的入站变成默认拒绝allow-same-namespace只在当前 namespace 内生效,不会自动跨 namespacenamespaceSelector让你可以按命名空间身份放行- Ingress 看目标,Egress 看源头
- DNS 本身也是网络流量,default-deny-egress 会先把它砍掉
- 恢复 DNS 不等于恢复业务流量
- 一个连接要成功,需要同时满足路径、源头 egress、目标 ingress 和端口监听
- 你的集群既支持标准 NetworkPolicy,也具备 Calico 增强策略能力
你现在必须能回答的 15 个问题
- 为什么 Kubernetes 默认几乎是全通?
podSelector在顶层 spec 里到底表示什么?- 什么时候一个 Pod 会进入“默认拒绝 + 白名单”状态?
- 为什么标准 NetworkPolicy 说它是“allow-list union”?
default-deny-ingress为什么会让同 namespace 也不通?allow-same-namespace为什么不影响跨 namespace 流量?namespaceSelector的现实用途是什么?- 在同一个
from项里同时写namespaceSelector和podSelector,语义为什么是 AND? - 分成两个
from项时,为什么变成 OR? - Egress Policy 为什么会让 DNS 挂掉?
- 为什么
wget可能显示bad address而不是 timeout? - 为什么说“目标端放行了”仍然不代表连接一定成功?
- 标准 NetworkPolicy 和 Calico GlobalNetworkPolicy 的能力差别在哪?
- 排查
NetworkPolicy问题时,为什么先看 namespace label? - 生产环境为什么要把 DNS、监控、Webhook 等系统流量纳入白名单设计?
下一课预告
你现在已经把:
- 控制面主链路
- 调度
- 网络与协议
- NetworkPolicy
这几条核心骨架都搭起来了。
下一步最值得继续推进的是:
配置和身份是怎么进入这套系统的。
也就是:
- ConfigMap / Secret
- ServiceAccount
- RBAC
- 凭据注入
- API 访问身份
因为平台的真正边界,往往不是只有网络,还有:
- 配置边界
- 身份边界
- 权限边界
第五课总结
这节课最重要的结论只有一句:
NetworkPolicy 不是让网络“更复杂”,而是把原本隐含的访问边界显式写出来,让集群从默认信任走向按需最小放行。
而你这次已经亲手看到了这条路线是如何一步步建立起来的:
- 默认全通
- default deny
- 同 namespace 白名单
- 命名空间级身份放行
- 源头 egress 收紧
- DNS 单独恢复
- 业务端口按需恢复
这就是零信任网络在 Kubernetes 里的真实落地方式。