想象一个新建成的小区——A 小区。物业管理处图省事,决定不装门禁、不设围墙,所有住户的大门常年敞开。起初大家相安无事,直到某天小偷溜进一户人家,顺走财物后,又轻轻松松串到隔壁几户——这时业主们才发现,敞开的门不叫便利,叫做隐患。这正是 Kubernetes 集群的默认写照。集群里的每个 Pod 就像一户人家,默认位于同一个扁平网络中,任意两个 Pod 之间都能直接通信,没有任何隔离。 要让小区安全,物业需要一套门禁系统:给每家贴上标签(如“业主”“租客”“快递员”),再写清楚“哪些人能进哪户人家”“哪户人家能拜访谁”。这套规则,就是 Kubernetes 里的 Network Policy(网络策略)。它用标签选择器锁定目标 Pod,用入站(ingress)/出站(egress)规则定义白名单,再交给 Calico、Cilium 这类 CNI 插件翻译成底层网络的“门卫指令”,最终实现零信任的微隔离——只有被明确允许的流量,才能通行。

岗位关联

掌握 NetworkPolicy 是云原生安全与零信任架构的必修课。无论你是运维工程师还是安全架构师,学会它就能精准管控集群东西向流量,抵御横向渗透,让“默认拒绝、最小授权”从口号变成可落地的配置。

目录
  1. 可视化演示
  2. 默认全互通:敞开大门的隐患
  3. 核心对象解析:门禁制度怎么写
  4. 标签选择器术:门禁怎么认人
  5. 入站出站规则:两张门禁登记表
  6. CNI 兼容差异:门禁设备选哪家
  7. 零信任微隔离:全员验证的时代
  8. 策略模板库:物业的标准化图纸
  9. 排错性能调优:门禁失灵怎么办
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示Kubernetes Network Policy的Pod间流量控制机制,可视化网络拓扑与策略规则,演示允许/拒绝流量如何通过标签选择器实现零信任网络隔离。

本节配有交互动效(进入视口后自动加载,约 3KB)。

'+modeCaptions.topo+'

默认全互通:敞开大门的隐患

A 小区刚交付时,物业没有装任何门禁,住户之间可以随意串门。今天你去邻居家借酱油,明天邻居来你家看球赛,大家觉得挺方便。可等到小偷光顾后,警方发现他盗窃的几户人家之间根本没有阻碍——小偷正是顺着“全通”的便利,一路偷了五家。Kubernetes 集群默认就是这个状态:所有 Pod 通过 CNI 网络插件接入一个扁平、全连通的虚拟网络,彼此之间不需要任何授权即可通信。

这种设计在 K8s 诞生之初是为了降低网络复杂度:每个 Pod 有独立 IP,应用之间仿佛在一台物理机上互访。但代价是,攻击者只要攻破任意一个 Pod,就能像小偷一样横向移动,轻易访问数据库、缓存、内部服务等所有目标,攻击面被无限放大。安全团队由此意识到:默认全通是便利,更是无法接受的敞口风险。

用集合论的语言说,集群的默认可达关系是一个完全图——任意两点之间都有一条边:

既然“完全图”不安全,物业(运维团队)决定引入门禁系统——这就是 NetworkPolicy 登场的背景。它不是为了打开通路,而是为了在默认全通的世界里,一点点剪出安全的形状。

∀Pi,Pj∈Pods: Pi→Pj∈Ecluster\forall P_i, P_j \in \text{Pods}:\ P_i \to P_j \in E_{\text{cluster}}
这个公式说明:在没有任何策略的 K8s 集群里,任意两个 Pod 之间都存在一条允许通信的边,隔离完全不存在。NetworkPolicy 要做的事,就是给这张完全图“剪枝”。
  1. 全称量词与任意组合P_i 和 P_j 表示集群里任意两个 Pod,i 和 j 只是区分“源”与“目标”的下标,不限制具体数量。
    ∀Pi,Pj\forall P_i, P_j
  2. 可达箭头P_i → P_j 表示“从 Pod i 到 Pod j 的流量被默认允许”,这就是默认全通的直接体现。
    Pi→PjP_i \to P_j
  3. 完全可达边集E_cluster 是集群所有可达关系的集合,任意点对都属于它,数学上等价于完全图。
    Ecluster={(u,v)∣u,v∈Pods}E_{\text{cluster}} = \{ (u,v) \mid u,v \in \text{Pods} \}

核心对象解析:门禁制度怎么写

物业要给小区装门禁,先得印一张“门禁制度表”。表格上写清楚:这条规则管哪几户(适用范围)、管进门还是出门(管控方向)、具体放行谁(细则)。NetworkPolicy 就是 Kubernetes 里的这张“制度表”,它本身是一份声明式 API 对象,不会自己执行任何拦截,只负责描述意图。

一个标准的 NetworkPolicy 长这样:

逐字段看:spec.podSelector 用标签挑选“本策略管哪批 Pod”,相当于门禁表上填的“适用房号”;policyTypes 声明策略管理的流量方向(Ingress 进、Egress 出);ingress 和 egress 是具体的放行细则。注意,如果 policyTypes 写了 Ingress 但 ingress 为空,就等于“没有放行任何入站流量”——这成为默认拒绝的机制来源。

策略的作用范围不是整个集群,而是被两层条件精确框定:

门禁制度的“适用范围”终于清晰了。但制度不能只约束一小部分人——它还得配合全小区的标准化设计。物业很快发现,要精准地“指认”每一户,光写房号不够,最靠谱的方式是给每户贴标签。

Scope(P)=NP∩{p∣Match(p,SP)}\text{Scope}(P) = \mathcal{N}_{P} \cap \{ p \mid \text{Match}(p, S_P) \}
这个公式表示:一条策略只对“自己命名空间内、且标签匹配选择器”的那些 Pod 生效。想管别的命名空间,得借助 namespaceSelector 显式说出来。
  1. 策略作用域Scope(P) 是策略 P 实际管理的 Pod 集合,所有后续规则都只在这个集合内生效。
    Scope(P)\text{Scope}(P)
  2. 命名空间边界N_P 是策略所在的命名空间,Pod 必须属于同一命名空间才会被纳入治理范围。
    NP\mathcal{N}_{P}
  3. 标签匹配集花括号内的集合表示“标签匹配选择器 S_P 的所有 Pod”,Match 函数返回真代表命中。
    {p∣Match(p,SP)}\lbrace p \mid \text{Match}(p, S_P) \rbrace
  4. 交集收窄两个条件取交集,命名空间不符或标签不匹配都会被排除在策略范围之外。
    ∩\cap
yaml18 行
apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata:  name: db-policy  namespace: prodspec:  podSelector:    matchLabels:      app: db  policyTypes: [Ingress]  ingress:    - from:        - podSelector:            matchLabels:              tier: fe      ports:        - protocol: TCP          port: 5432

标签选择器术:门禁怎么认人

小区物业在每户门口贴了不同颜色的贴纸:红色是“业主”、蓝色是“租客”、黄色是“快递员”。门禁识别一个人的身份,不是看脸,而是读贴纸。K8s 的标签(Label)就是贴纸,标签选择器(Selector)就是读贴纸的规矩。NetworkPolicy 提供三种认人方式:podSelector 按 Pod 的标签选人,namespaceSelector 按命名空间的标签选区域,ipBlock 则直接按 IP 网段认人。

podSelector 是最常用的方式。它写在 ingress.from 或 egress.to 里,表示“来源/目标的标签长这样才算数”。namespaceSelector 则用于跨命名空间场景,比如只允许“监控命名空间”的 Pod 来访问本空间的数据库。ipBlock 是最后的硬核手段,通常用于放行集群外的固定 IP,比如办公室出口 IP。

这里最关键的是 matchLabels 的匹配语义:里面多个键值对是逻辑与(AND)关系,必须全部满足才命中。用数学表示:

标签贴纸设计好后,门禁不仅能“认人”,还能知道“这个人从哪来、到哪去”。接下来要做的是梳理每户的出入规则——入站管谁进来,出站管谁能出去。

Match(p,S)=⋀(k,v)∈S[p.labelsk=v]\text{Match}(p, S) = \bigwedge_{(k,v) \in S} \Big[ p.\text{labels}_k = v \Big]
这个公式读作:对选择器 S 中的每一个键值对 (k,v),都要检查 Pod p 的标签里键 k 是否等于 v;全部相等,Match 才返回真。
  1. 遍历选择器条目S 是选择器包含的键值对集合,先逐个取出每一对 (k,v)。
    ∀(k,v)∈S\forall (k,v) \in S
  2. 单个标签比对p.labels_k = v 表示“Pod p 的 labels 字典中,键 k 的值必须正好等于 v”,只要有一个不符合,整个匹配失败。
    p.labelsk=vp.\text{labels}_k = v
  3. 逻辑与合并大写的和符号表示把所有条件的真假值做“与”运算,一票否决——这就是 matchLabels 多条件必须全中的原因。
    ⋀\bigwedge

入站出站规则:两张门禁登记表

物业给每户发了两张登记表:一张贴在门口,写下“允许哪些人进我屋”(入站规则);另一张放在玄关,写下“我可以去谁家”(出站规则)。人不能既当“进门者”又当“外出者”——流量也一样,必须分开管。NetworkPolicy 用 ingress 管理入站,egress 管理出站,policyTypes 决定本次策略管哪几张表。

入站规则的匹配逻辑用一句话概括:来源匹配,且端口匹配,才放行。这里的来源可以是 Pod 标签、命名空间标签、ipBlock 三者之一;端口则是 protocol + port 的组合。多条规则之间是或(OR)关系——只要命中任意一条规则就放行。出站规则同理,只是方向反过来了。

入站决策可以形式化地写成:

入站出站规则把“谁能访问谁”定义清楚了,但这套制度能不能运行,还取决于小区装的是哪家厂商的门禁设备。没有设备,制度就是废纸。CNI 插件就是那套门禁设备。

Allowin(s)=⋁r∈Rin(srcMatch(r,s)∧portMatch(r,port))\text{Allow}_{\text{in}}(s) = \bigvee_{r \in R_{\text{in}}} \Big( \text{srcMatch}(r, s) \land \text{portMatch}(r, \text{port}) \Big)
这个公式表示:遍历所有入站规则,任意一条同时满足来源匹配和端口匹配,就允许这个入站连接。
  1. 遍历入站规则集R_in 是策略里所有入站规则的集合,从第一条开始逐一评估。
    ∀r∈Rin\forall r \in R_{\text{in}}
  2. 来源匹配判断srcMatch(r, s) 检查来源 Pod s 是否满足规则 r 的 from 条件(标签、命名空间、IP 段三选一)。
    srcMatch(r,s)\text{srcMatch}(r, s)
  3. 端口匹配判断portMatch(r, port) 检查访问端口是否落在规则 r 的 ports 列表里,协议和端口号都要一致。
    portMatch(r,port)\text{portMatch}(r, \text{port})
  4. 或逻辑合并大或号把所有规则的判断结果做“或”运算,只要出现一个真值,整体就是放行。
    ⋁\bigvee

CNI 兼容差异:门禁设备选哪家

物业开招标会,三家厂商来投标。A 厂商(Calico)的方案是成熟的人脸识别闸机:将门禁制度逐条翻译成底层名单,社区公认稳定;B 厂商(Cilium)的方案是新一代指纹+虹膜识别:性能更强,还自带行为监控大屏;C 厂商(Flannel)的方案最便宜——但它根本没有门禁模块,只是把小区围墙修好了而已。K8s 的 NetworkPolicy 只是声明,真正落地必须靠 CNI 插件。

  • Calico:网络插件中的老牌厂商,核心基于 iptables。将 NetworkPolicy 翻译成一套 Felix 规则写入 iptables 链,可靠且应用最广,适合对 eBPF 有顾虑的传统集群。
  • Cilium:基于 eBPF 技术的新锐方案。策略查询用哈希表实现 O(1) 匹配,性能远超 iptables 的线性扫描,还提供 Hubble 可视化流量洞察,适合大规模和高性能场景。
  • Flannel:仅提供扁平网络(overlay),不支持 NetworkPolicy。如果集群装了 Flannel,创建再多的 NetworkPolicy 也不会产生任何拦截效果。

策略声明到数据面的翻译过程,可以抽象成一个数学映射:

设备选好后,物业开始研究一套更先进的管理理念——不再相信任何人。哪怕对方是业主,也要每次验证身份。这就是零信任。

NP→TCNIRdatapath\text{NP} \xrightarrow{T_{\text{CNI}}} R_{\text{datapath}}
这个映射表示:NetworkPolicy 只是一段 YAML,必须经过 CNI 插件的翻译函数 T,变成数据平面上真正生效的规则集合。
  1. 声明式策略NP 指 YAML 形式的 NetworkPolicy 对象,描述意图但不会自我执行。
    NP\text{NP}
  2. 翻译函数T_CNI 是 CNI 插件的翻译引擎——Calico 编译为 iptables 规则,Cilium 编译为 eBPF 字节码。
    TCNIT_{\text{CNI}}
  3. 数据面规则R_datapath 是最终写入内核的底层规则,只有这些规则成立,网络流量才会被真正拦截或放行。
    RdatapathR_{\text{datapath}}

零信任微隔离:全员验证的时代

传统安全模型像小区旧规矩:“业主免检”。但物业复盘盗窃案后发现,小偷就是混在业主里的。于是新制度规定:任何人都不能被默认信任,每次访问必须出示有效凭证,凭白名单放行。这就是零信任网络的核心——永不信任,始终验证。NetworkPolicy 正是这套理念在 Kubernetes 中的承载者。

零信任落地时,最关键的一步是微隔离:不再按“集群边界”做一道大墙,而是把安全边界缩小到每个工作负载。每个 Pod 都是一个小小的安全域,默认拒绝所有流量,只对显式白名单放行。这样即使攻击者攻破了一个 Pod,本应横向移动的空间也被大幅压缩。

零信任的判决逻辑很简单,可以用分段函数表达:

理念执行起来需要落地模板。物业把“全验证”的门禁制度固化成几套标准化图纸,任何楼栋拿来就能用。NetworkPolicy 社区也沉淀了类似的策略模板库。

D(s,t)={1,(s,t)∈R+0,otherwiseD(s,t) = \begin{cases} 1, & (s,t) \in R^{+}\\ 0, & \text{otherwise} \end{cases}
这个公式表示:如果源 s 到目标 t 的连接在显式允许的集合 R+ 里,就放行(1);否则一律拒绝(0)。没有“中间地带”和“默认放行”。
  1. 判决函数D(s,t) 对每一个源到目的连接返回决策结果,1 放行、0 拒绝。
    D(s,t)D(s,t)
  2. 白名单集合R+ 是所有显式策略允许的流量对的集合,它由多条 NetworkPolicy 叠加而成。
    (s,t)∈R+(s,t) \in R^{+}
  3. 默认拒绝分支不在白名单里的流量一律落到 otherwise 分支,返回 0——这就是零信任的数学本质。
    otherwise→0\text{otherwise} \to 0

策略模板库:物业的标准化图纸

物业把门禁方案整理成三张标准图纸:图纸 A 是“本户全封闭”,图纸 B 是“只让同标签的人进”,图纸 C 是“隔离不同楼栋”。遇到类似情况,直接套图纸,不用每次重新设计。K8s 运维中,NetworkPolicy 也有几个高频复用模板。

图纸 A:拒绝全部入站——这是零信任的起点。将 policyTypes 设为 Ingress,且不写任何 ingress 规则:

这条策略一经创建,所有访问 app: web Pod 的入站流量全部被拒绝。它单独存在时几乎没有意义,但放在应用的最外层,就能先“一刀切”,再逐条加白名单。

图纸 B:同标签白名单放行——在拒绝全部的基础上,允许前端访问后端:

图纸 C:命名空间隔离——利用 namespaceSelector 只允许“监控”命名空间访问,其余流量全部拒绝,实现不同团队之间的网络隔离。

拒绝全部模板的数学本质是空规则集必然导出拒绝:

标准图纸减少了重复设计,但门禁系统运行久了,偶尔也会出故障:明明装了闸机,为什么有人还能自由进出?物业开始排查,这就到了运维排错和性能调优的环节。

Rin=∅⇒∀s∈Pods: Deny(s)R_{\text{in}} = \varnothing \Rightarrow \forall s \in \text{Pods}:\ \text{Deny}(s)
这个公式说明:入站规则集合为空,意味着没有任何一条“允许”存在,于是对所有来源一律拒绝。
  1. 空规则集R_in 为空说明策略没有配置任何放行规则,白名单是空白状态。
    Rin=∅R_{\text{in}} = \varnothing
  2. 全称限定对集群中任意来源 Pod s,该结论都成立,不因命名空间或节点位置改变。
    ∀s∈Pods\forall s \in \text{Pods}
  3. 必然拒绝推导结论是对所有入站流量执行 Deny,这就是“不写规则就是拒绝全部”的机制原理。
    Deny(s)\text{Deny}(s)
yaml5 行
spec:  podSelector:    matchLabels:      app: web  policyTypes: [Ingress]
yaml10 行
spec:  podSelector:    matchLabels:      app: api  policyTypes: [Ingress]  ingress:    - from:        - podSelector:            matchLabels:              tier: fe

排错性能调优:门禁失灵怎么办

物业接到投诉:B 栋的门禁形同虚设。排查顺序往往是:先看闸机有没有通电(CNI 是否支持),再看登记表是否贴错门牌(选择器是否命中),最后看是不是两套制度互相覆盖(多策略叠加)。这套方法论同样适用于 NetworkPolicy 排错。

第一步:确认 CNI 支持。如果集群用的是 Flannel,策略根本不会执行。检查 kubectl get networkpolicy 能看到对象,但数据面无感,这是最隐蔽的坑。第二步:核对选择器标签。用 kubectl get pods --show-labels 确认目标 Pod 的标签是否与 podSelector 完全匹配,多一个空格、少一个键都会导致范围错误。第三步:检查规则覆盖。由于多策略是“或”逻辑,当一个 Pod 被多条策略命中时,只要有一条放行就通了,需要逐一检查所有相关策略。

关于性能,iptables 实现有一个先天短板——规则按顺序线性匹配。规则越多,每个数据包需要检查的条目就越多,延迟随之线性上升:

排查有方法论、性能有量化公式——门禁系统总算可以稳定运转了。从 A 小区到 K8s 集群,用一套门禁制度收编了默认全通的混乱,这正是 NetworkPolicy 的价值。

Ttotal=∑i=1nti=O(n)T_{\text{total}} = \sum_{i=1}^{n} t_i = O(n)
这个公式表示:每个数据包的总匹配时间是各条规则耗时的累加,规则数量 n 越大,整体开销越高,时间复杂度是 O(n)。
  1. 单条规则耗时t_i 是内核针对第 i 条 iptables 规则的匹配时间,通常与规则复杂度有关。
    tit_i
  2. 累加求和总耗时是 1 到 n 条规则耗时的总和,逐条顺序匹配决定了它必然随规则数增长。
    ∑i=1nti\sum_{i=1}^{n} t_i
  3. 线性复杂度O(n) 表示规则数翻倍,匹配时间大致翻倍。这就是大规模集群推荐 Cilium(哈希查表 O(1))的原因。
    O(n)O(n)

AI 应用场景

多租户 SaaS 平台网络隔离

阶段:基础设施安全管控

解决的问题:SaaS 平台常把不同客户(租户)的工作负载部署在同一个 K8s 集群中,传统方式依赖网络防火墙或 VLAN 划分,配置分散、与 Pod 生命周期脱节;一旦某个租户的 Pod 被攻破,威胁可能蔓延到其他租户。

具体案例:某 SaaS 厂商在共享集群中为每个租户建立独立命名空间,并用 Namespace 标签(如 tenant=a、tenant=b)标记。运维团队创建一组模板化 NetworkPolicy,在“拒绝全部入站”的基础上,只放行同租户命名空间内 tier: fe 到 tier: be 的流量,并结合 egress 限制 Pod 只能访问允许的外部 API 网关。租户间被彻底微隔离,横向移动路径被切断。

金融行业零信任合规建设

阶段:安全架构设计与合规

解决的问题:金融行业监管要求“最小权限、访问可控”,传统方式依赖人工维护防火墙策略,无法追踪工作负载级别的访问关系,审计时难以自证合规。

具体案例:某银行容器平台上线 Cilium 作为 CNI,将每项业务按标签(app=payment、app=ledger)建模。安全团队在默认拒绝策略之上,仅放行支付网关访问核心账务系统的 8080 端口,并开启 Hubble 采集全量通信日志。审计人员可随时导出“谁在什么时间访问了账务 Pod”的完整链路,顺利通过等保测评。

大规模集群东西向流量可观测

阶段:可观测性与故障诊断

解决的问题:集群规模超过千节点后,iptables 规则膨胀带来性能衰减,且策略出错时很难定位“哪条规则放行了预期外的流量”。

具体案例:某互联网公司使用 Calico 时频繁出现服务间访问超时,SRE 用 kubectl describe networkpolicy 比对策略,再用 calicoctl get policy 核查数据面下发状态;后来切换 Cilium,借助 Hubble 的 Flow 视图,在 10 分钟内定位到一条多余的宽松 egress 规则,并借助 cilium policy trace 精确预测策略变更的影响面。

Python 演示

python79 行
#!/usr/bin/env python3"""用 Python 模拟 K8s NetworkPolicy 的入站决策过程。 场景:微服务应用包含 web/api/db/cache 四个 Pod,加上一条 NetworkPolicy 后,逐个判断任意两个 Pod 之间的 8080 端口访问是否放行。"""  class Pod:    def __init__(self, name, labels, namespace="default"):        self.name = name        self.labels = labels        self.namespace = namespace     def __repr__(self):        return "%s/%s" % (self.namespace, self.name)  def labels_match(actual, selector):    """selector 是 {key: value} 字典,全部键值都要命中(AND 语义)。"""    if not selector:        return True    return all(actual.get(k) == v for k, v in selector.items())  def ingress_allowed(source, target, port, ingresses):    """按 NetworkPolicy 的 ingress 语义判断:来源匹配 AND 端口匹配才放行。"""    for rule in ingresses:        src_ok = False        if not rule.get("from"):            src_ok = True        for item in rule.get("from", []):            if "podSelector" in item:                src_ok |= labels_match(source.labels, item["podSelector"].get("matchLabels"))            if "namespaceSelector" in item:                src_ok |= labels_match({"ns": source.namespace}, item["namespaceSelector"].get("matchLabels"))        port_ok = False        if not rule.get("ports"):            port_ok = True        for p in rule.get("ports", []):            if p.get("port") == port:                port_ok = True        if src_ok and port_ok:            return True    return False  def main():    pods = [        Pod("web", {"app": "web", "tier": "fe"}),        Pod("api", {"app": "api", "tier": "be"}),        Pod("db", {"app": "db", "tier": "be"}),        Pod("cache", {"app": "cache", "tier": "be"}),        Pod("monitor", {"app": "monitor"}, namespace="ops"),    ]    policy = {        "spec": {            "podSelector": {"matchLabels": {"tier": "be"}},            "ingress": [                {                    "from": [{"podSelector": {"matchLabels": {"tier": "fe"}}}],                    "ports": [{"port": 8080}],                }            ],        }    }    targets = [p for p in pods if labels_match(p.labels, policy["spec"]["podSelector"]["matchLabels"])]    print("NetworkPolicy 命中的目标 Pod:", targets)    print("模拟入站流量 8080 端口判决(来源 -> 目标 : 结果)")    for target in targets:        for source in pods:            if source is target:                continue            result = ingress_allowed(source, target, 8080, policy["spec"].get("ingress", []))            print("  %-8s -> %-8s : %s" % (source.name, target.name, "ALLOW" if result else "DENY"))  if __name__ == "__main__":    main()
目的:模拟 NetworkPolicy 的标签匹配与入站规则判决,输出每个来源 Pod 到目标 Pod 的访问结果(ALLOW/DENY)。
  1. class Pod定义网络策略中的“房屋”:包含名称、标签、命名空间,等价于 K8s Pod 的核心属性。
  2. labels_match(actual, selector)实现 matchLabels 的 AND 语义:选择器里所有键值对必须全部命中。
  3. ingress_allowed(...)实现 K8s ingress 引擎:先用 from 判断来源,再用 ports 判断端口,两者同时满足才放行。
  4. policy = {...}声明式 NetworkPolicy:podSelector 选目标,ingress 定义白名单规则。
  5. targets = [p for p in pods if ...]用选择器筛选策略管理的目标 Pod 集合。
  6. if __name__ == "__main__":提供可执行入口,直接运行即可观察每对 Pod 的判决结果。

常见误区

误区创建一条 NetworkPolicy 就自动实现安全防护,相当于给 Pod 装上了防火墙,恶意流量会被拦截。

正解NetworkPolicy 只是一份声明式“规则单”,本身不执行任何拦截。真正落地必须靠 CNI:Calico 翻译成 iptables,Cilium 翻译成 eBPF。如果集群用的是 Flannel,策略写再多也不会生效。这就像物业贴了门禁制度,但闸机没有安装,制度就是一张废纸。

为何常见:看到"NetworkPolicy"这个名字,直觉认为它是 K8s 自带的防火墙组件,忽略了它在架构上只是 API 声明、由插件执行的事实。

误区多个 NetworkPolicy 同时命中一个 Pod 时,流量必须通过所有策略的检查才能放行,多策略叠加会让网络越来越严格。

正解多个 NetworkPolicy 取的是并集,只要任意一条策略放行了该流量,它就能通行;只有所有策略都拒绝,流量才真正被拒绝。这是“或”逻辑,而不是“与”逻辑。实际排查时经常发现:预期拒绝的流量被另一条宽松策略放行了。

为何常见:把策略类比成防火墙规则链,认为必须逐个通过;实际上 K8s 的语义是“有一条允许则放行”。

误区没有配置 NetworkPolicy 的 Pod 会自动被集群隔离,外部无法访问。

正解K8s 集群默认所有 Pod 全互通。只有当一个 Pod 被 NetworkPolicy 选中后,它才进入“受管理”状态,对应 policyTypes 的流量才默认拒绝。没有策略的 Pod 依然暴露在完全图之中。

为何常见:零信任理念深入人心,容易让人误以为集群默认是封锁的,而 K8s 的真实初始态是“默认全通、显式隔离”。

误区Flannel 是 CNI 插件,而 NetworkPolicy 是 K8s 标准 API,所以装了 Flannel 就能直接使用网络策略。

正解Flannel 只负责提供扁平 overlay 网络,内部没有策略执行引擎。K8s 的 API 层可以接受 NetworkPolicy 对象,但数据面不会产生任何规则。要获得策略能力,需要叠加 Calico 的策略组件或整体更换 CNI。

为何常见:混淆了“网络连通”和“策略执行”两个不同层次,未验证 CNI 的能力边界就误判策略已生效。

误区`matchLabels` 里同时写了 app=web 和 tier=fe,只要满足其中一个标签就能选中。

正解matchLabels 的所有键值对是逻辑与关系,必须全部满足才会选中目标 Pod。如果要表达“任一”的语义,需要拆成多条规则或利用 matchExpressions 的 In 操作符。

为何常见:把 YAML 的列表语义(多条 ingress 规则之间是 OR)错误迁移到 matchLabels 的字段内部,混淆了两套不同的逻辑。

面试题

Q1. K8s 集群中 Pod 之间默认的网络联通性是?

单选题 · easy

  • A.默认隔离,需建策略才互通
  • B.默认全通,需建策略才隔离← 正确答案
  • C.默认拒绝所有外部流量
  • D.取决于节点位置与网段

答案:B。K8s 的 CNI 网络插件把 Pod 放进扁平网络,默认完全双向可达,隔离不存在。NetworkPolicy 的唯一使命就是打破全通、创造隔离,选项 B 正确,A/C/D 都与默认行为不符。

Q2. NetworkPolicy 的 spec.podSelector 作用是什么?

单选题 · easy

  • A.选择策略作用的 Pod← 正确答案
  • B.选择集群的 Node
  • C.选择命名空间
  • D.选择外部 IP 段

答案:A。podSelector 用标签筛选本策略管理的目标 Pod,是策略作用域的第一层。Node、命名空间和外部 IP 都是由其他字段(namespaceSelector、ipBlock)负责,均不符合。

Q3. 一条 NetworkPolicy 的 policyTypes 包含 Ingress 但没有写任何 ingress 规则,入站流量会怎样?

单选题 · mid

  • A.全部放行
  • B.全部拒绝← 正确答案
  • C.只放行同空间流量
  • D.只放行集群外流量

答案:B。policyTypes 声明管理 Ingress 后,若对应规则为空,相当于空规则集,默认拒绝所有入站流量。这正是“拒绝全部”模板的机制来源,B 正确。

Q4. 以下哪些 CNI 插件支持 NetworkPolicy?

多选题 · mid

  • A.Flannel
  • B.Calico← 正确答案
  • C.Cilium← 正确答案
  • D.Docker bridge

答案:BC。Calico 用 iptables/Felix 翻译策略,Cilium 用 eBPF 实现高性能策略,两者原生支持。Flannel 是纯 overlay 网络不带策略引擎,Docker bridge 网络也不参与 K8s 集群的网络策略执行。

Q5. spec.ingress 里配置了 from 和 ports 都非空,流量要放行必须满足什么?

单选题 · mid

  • A.来源匹配且端口匹配← 正确答案
  • B.来源匹配或端口匹配
  • C.端口必须全部匹配
  • D.来源必须是 Pod

答案:A。一条 ingress 规则内部,from 与 ports 是 AND 关系,来源和端口都命中才放行;多条规则之间才是 OR 关系。A 正确,B/C/D 的表述不完整或不准确。

Q6. 关于多个 NetworkPolicy 同时匹配同一个 Pod,行为正确的是?

多选题 · hard

  • A.多条策略取并集← 正确答案
  • B.任一策略允许即放行← 正确答案
  • C.全部策略拒绝才拒绝← 正确答案
  • D.后创建的策略覆盖前者

答案:ABC。K8s 多策略叠加的语义是“或”:存在一条允许就放行,只有全部拒绝才拒绝,A/B/C 正确。策略之间不存在先后覆盖关系,D 错误。

Q7. namespaceSelector 匹配的对象是?

单选题 · mid

  • A.Pod 的标签
  • B.Node 的标签
  • C.命名空间的标签← 正确答案
  • D.集群的标签

答案:C。namespaceSelector 读取命名空间对象上的标签,用来限制跨命名空间的来源或目标。它不匹配 Pod、Node 或集群级标签,C 正确。

Q8. 要让 Pod 能够访问公网上的一个固定 IP 服务,应该定义?

单选题 · hard

  • A.ingress 的 ipBlock
  • B.egress 的 ipBlock← 正确答案
  • C.namespaceSelector 匹配
  • D.无需任何策略

答案:B。访问公网属于出站流量,需要在 egress 规则中用 to + ipBlock 指定目的网段(如特定 CIDR)。ingress 管的是入站,namespaceSelector 管的是命名空间范围,答案 B 正确。

Q9. matchLabels 里同时有 app=web 和 tier=fe,选中目标 Pod 的条件是?

单选题 · mid

  • A.满足任意一个即可
  • B.两个都满足才选中← 正确答案
  • C.满足 app=web 优先
  • D.满足 tier=fe 优先

答案:B。matchLabels 内多个键值对语义是逻辑与,所有标签必须全部命中才选中。选项 A 混淆了“规则之间是或”与“标签之间是与”两套逻辑,C/D 不存在优先级概念。

Q10. 关于 NetworkPolicy 的性能与排错,正确的说法是?

多选题 · hard

  • A.iptables 规则越多匹配越慢← 正确答案
  • B.Cilium 的 eBPF 性能更优← 正确答案
  • C.策略越多对性能零影响
  • D.必须用流量镜像才能排错

答案:AB。iptables 按链顺序线性匹配,复杂度 O(n),规则越多时延越大,A 正确;Cilium 用哈希表近似 O(1) 查表,B 正确。策略不可能零开销,C 错误;排错可以用 kubectl describe、calicoctl 或 cilium policy trace 等工具,D 错误。