K8s Lab 把当前仓库文档整理成一个可阅读的网页站点

Repository Reading Site

第五课:NetworkPolicy、零信任网络与流量边界

上一课我们解决的是: 这一课要解决的是: 这两个问题听起来很像,但本质完全不同。 如果说上一课是“道路系统”,那这一课就是“交通管制系统”。 --- 很多初学者以为: 这是错的。 因为在 Kubernetes 里,能不能到达目标,至少受三类因素影响: 1. 路有没有 2. 服务映射对不对 3. 策略允不允许 前两者解决的是“技术可达性”,最后一者解决的是“访

Markdown05-第五课-NetworkPolicy与零信任网络.md2026年4月9日 18:11

第五课:NetworkPolicy、零信任网络与流量边界

为什么这节课是网络课的下半场

上一课我们解决的是:

流量怎么到达目标。

这一课要解决的是:

流量该不该到达目标。

这两个问题听起来很像,但本质完全不同。

上一课的关键词

  • Pod IP
  • Service IP
  • DNS
  • kube-proxy
  • Calico IPIP
  • WireGuard
  • 路由
  • NAT

这一课的关键词

  • 零信任
  • 默认拒绝
  • 允许白名单
  • Ingress
  • Egress
  • namespaceSelector
  • podSelector
  • ipBlock

如果说上一课是“道路系统”,那这一课就是“交通管制系统”。


先讲一个最容易被误解的点

很多初学者以为:

  • 只要 Pod 到 Pod 网络是通的
  • Service 也能解析
  • 那么访问一定应该成功

这是错的。

因为在 Kubernetes 里,能不能到达目标,至少受三类因素影响:

  1. 路有没有
  2. 服务映射对不对
  3. 策略允不允许

前两者解决的是“技术可达性”,最后一者解决的是“访问授权边界”。

NetworkPolicy 就是第三类。


Kubernetes 默认网络模型:几乎是“全通”

这是学习 NetworkPolicy 必须先接受的现实。

默认情况下

如果你什么都不配:

  • 同 namespace Pod 之间可通信
  • 跨 namespace Pod 之间也可通信
  • 绝大多数 Pod 可以自由出站

这在实验环境里方便,在生产环境里危险。

为什么 Kubernetes 默认不帮你自动隔离

因为 Kubernetes 的核心目标是:

  • 提供通用编排平台

而不是默认替你决定:

  • 哪些业务应该互相隔离
  • 哪些系统可以访问数据库
  • 哪些 namespace 之间该有边界

这些属于平台治理与安全策略层,而不是最小网络模型本身。

这意味着什么

如果你不主动设计边界,那么集群一旦规模变大,就会自然演化成:

大平层网络。

也就是:

  • 谁都能找谁
  • 谁都能试着扫谁
  • 谁都可能误连或误打

这和零信任完全相反。


零信任网络到底是什么

零信任不是一句时髦口号,它的核心思想其实很朴素:

默认不信任,按需最小放行。

翻成 Kubernetes 网络语言就是:

  1. 默认不让 Pod 随意互访
  2. 只有明确业务需要的流量才放行
  3. 放行尽量精确到:
    • 谁可以来
    • 来哪个端口
    • 谁可以出
    • 出到哪里

这和传统内网思维的区别

传统内网思维:

  • 都在内网,默认可信

零信任思维:

  • 在内网也不默认可信
  • 每条流量都应该有理由

这就是为什么 NetworkPolicy 在现代平台里非常重要。


先给你一个最重要的认知模型

Ingress Policy 管的是“谁能打进来”

它作用在目标 Pod 身上。

回答的问题是:

哪些来源可以访问这个 Pod?

Egress Policy 管的是“我能打出去给谁”

它作用在源 Pod 身上。

回答的问题是:

这个 Pod 可以访问哪些目标?

你必须牢记一句话

Ingress 看目标,Egress 看源头。

很多人学 NetworkPolicy 时最容易混淆这个点。


这次我们不是只讲语法,而是做了完整实验

我在集群里新增了两个实验命名空间:

  • np-demo
  • np-external

并创建了:

  • np-demo/web:Nginx 服务端
  • np-demo/same-ns-client:同 namespace 客户端
  • np-external/cross-ns-client:跨 namespace 客户端
  • np-demo/web-svc:服务入口

实验文件都在:

本轮最核心的实验顺序是:

  1. 什么策略都不加,验证默认全通
  2. default-deny-ingress
  3. allow-same-namespace
  4. namespaceSelector 放行带特定标签的 namespace
  5. 在客户端侧加 default-deny-egress
  6. 只放 DNS
  7. 再放业务 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 默认也不是隔离的。

这个默认值为什么危险

因为一旦集群里有:

  • dev
  • staging
  • prod
  • 第三方组件
  • 多团队业务

默认全通意味着:

  • 测试环境误打生产
  • 应用直接扫数据库
  • 监控组件无意探测敏感服务
  • 某个被攻陷的 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.fromegress.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... 仍然超时

这正好证明了什么

这正好证明了两件事:

  1. 策略是累加允许的
  2. 没写 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 身份

在放行。

这在多团队、多环境平台里非常有价值。


一个非常重要的语义陷阱:namespaceSelectorpodSelector 的 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 的思考顺序永远是:

  1. 我要保护哪些 Pod
  2. 这些 Pod 允许谁打进来
  3. 允许打哪些端口

不要反过来想。


现在进入更容易忽略的 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-system namespace 中
  • 标签为 k8s-app=kube-dns 的 Pod
  • 端口只允许 TCP/UDP 53

真实结果

应用后:

  • nslookup web-svc.np-demo.svc.cluster.local 恢复成功
  • wget http://web-svc... 仍然 timeout

这说明了什么

这完美说明:

  1. DNS 和 HTTP 是两类不同流量
  2. 你可以只放 DNS,不放业务流量
  3. 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-demo namespace
  • 访问 app=web 的 Pod
  • 只允许 TCP/80

真实结果

应用后:

  • wget http://web-svc.np-demo.svc.cluster.local 恢复成功

到这里,整个逻辑闭环了

Ingress 侧

目标服务端:

  • 默认拒绝
  • 放行同 namespace
  • 放行特定标签的 namespace

Egress 侧

客户端:

  • 默认拒绝出站
  • 先只放 DNS
  • 再放业务 HTTP

这已经非常接近生产级的零信任模型了。


到底什么时候一个连接会成功

这是整节课最重要的判断模型之一。

一个连接成功,至少要满足什么

  1. 路由与 Service 映射正常
  2. DNS 正常,或者你直接用 IP
  3. 源 Pod 的 egress 允许
  4. 目标 Pod 的 ingress 允许
  5. 目标端口确实监听

可以把它记成一张小公式

连接成功 = 路径可达 × 源头能出 × 目标能进 × 端口在听

任何一项不满足,都不通。

这也是为什么网络排障不能只盯着一边。


标准 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/v1 NetworkPolicy
  • Calico CRD:
    • crd.projectcalico.org/v1 NetworkPolicy
    • GlobalNetworkPolicy
    • NetworkSet
    • GlobalNetworkSet

我们已经实际查到了这些 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 pod
  • kubectl describe pod

4. Ingress / Egress 策略是否选中了相关 Pod

看:

  • kubectl get networkpolicy -o yaml
  • pod label
  • namespace label

5. 方向判断对没对

问自己:

  • 这是源 Pod 出不去?
  • 还是目标 Pod 不让进?

6. 再考虑更底层的 CNI 和节点网络

只有上面都排完了,再去怀疑:

  • Calico
  • 路由
  • 节点防火墙

这就是专家和初学者的区别:先分层,后下钻。


这节课最重要的 12 个结论

  1. Kubernetes 默认网络模型几乎是全通
  2. NetworkPolicy 不是全局防火墙,而是“选中 Pod 后对该方向进行隔离”
  3. 顶层 podSelector 选中的是被保护对象,不是来源对象
  4. 标准 NetworkPolicy 是“累加允许”模型,不是按顺序覆盖
  5. default-deny-ingress 会让目标 Pod 的入站变成默认拒绝
  6. allow-same-namespace 只在当前 namespace 内生效,不会自动跨 namespace
  7. namespaceSelector 让你可以按命名空间身份放行
  8. Ingress 看目标,Egress 看源头
  9. DNS 本身也是网络流量,default-deny-egress 会先把它砍掉
  10. 恢复 DNS 不等于恢复业务流量
  11. 一个连接要成功,需要同时满足路径、源头 egress、目标 ingress 和端口监听
  12. 你的集群既支持标准 NetworkPolicy,也具备 Calico 增强策略能力

你现在必须能回答的 15 个问题

  1. 为什么 Kubernetes 默认几乎是全通?
  2. podSelector 在顶层 spec 里到底表示什么?
  3. 什么时候一个 Pod 会进入“默认拒绝 + 白名单”状态?
  4. 为什么标准 NetworkPolicy 说它是“allow-list union”?
  5. default-deny-ingress 为什么会让同 namespace 也不通?
  6. allow-same-namespace 为什么不影响跨 namespace 流量?
  7. namespaceSelector 的现实用途是什么?
  8. 在同一个 from 项里同时写 namespaceSelectorpodSelector,语义为什么是 AND?
  9. 分成两个 from 项时,为什么变成 OR?
  10. Egress Policy 为什么会让 DNS 挂掉?
  11. 为什么 wget 可能显示 bad address 而不是 timeout?
  12. 为什么说“目标端放行了”仍然不代表连接一定成功?
  13. 标准 NetworkPolicy 和 Calico GlobalNetworkPolicy 的能力差别在哪?
  14. 排查 NetworkPolicy 问题时,为什么先看 namespace label?
  15. 生产环境为什么要把 DNS、监控、Webhook 等系统流量纳入白名单设计?

下一课预告

你现在已经把:

  • 控制面主链路
  • 调度
  • 网络与协议
  • NetworkPolicy

这几条核心骨架都搭起来了。

下一步最值得继续推进的是:

配置和身份是怎么进入这套系统的。

也就是:

  • ConfigMap / Secret
  • ServiceAccount
  • RBAC
  • 凭据注入
  • API 访问身份

因为平台的真正边界,往往不是只有网络,还有:

  • 配置边界
  • 身份边界
  • 权限边界

第五课总结

这节课最重要的结论只有一句:

NetworkPolicy 不是让网络“更复杂”,而是把原本隐含的访问边界显式写出来,让集群从默认信任走向按需最小放行。

而你这次已经亲手看到了这条路线是如何一步步建立起来的:

  • 默认全通
  • default deny
  • 同 namespace 白名单
  • 命名空间级身份放行
  • 源头 egress 收紧
  • DNS 单独恢复
  • 业务端口按需恢复

这就是零信任网络在 Kubernetes 里的真实落地方式。