☰
升级 Cilium 后 MySQL 突然拒绝连接?全网 5.3 万人围观过的 Masquerading 大坑
2026/10/9 21:08:44 网站建设 项目流程

升级 Cilium 后 MySQL 突然拒绝连接?全网 5.3 万人围观过的 Masquerading 大坑

【免费下载链接】ciliumeBPF-based Networking, Security, and Observability项目地址: https://gitcode.com/GitHub_Trending/ci/cilium

深夜升级集群,helm upgrade cilium一切绿灯;第二天业务方报障:应用 Pod 连不上 MySQL,报Access denied for user授权错误。明明代码没改、账号没删、密码没换,数据库日志里显示的却是一个"陌生"的 client IP——不是你业务 Pod 的地址。这是 2022 年在 Cilium 社区被 5.3 万人围观过的真实事故(juejin 上 5.3 万浏览量),它的根因,正是 Cilium 的Masquerading(源地址伪装)行为在版本升级后发生了静默改变。本文结合官方文档与仓库源码,把这条链路完整拆开:表象、根因、排查手段,以及升级前必须核对的那几个开关。

事故复现:升级后 MySQL 授权报错的表象

事故的典型场景是这样的:

  1. 集群原本运行 Cilium v1.8.x,某个业务 Pod(IP 如10.244.1.15)访问集群外(或同 VPC 内)的 MySQL 实例;
  2. 升级到 Cilium v1.11.x 后,业务 Pod 突然报 MySQL 授权失败:ERROR 1045 (28000): Access denied for user 'app'@'10.0.0.5';
  3. 检查 MySQL 侧的授权表,app@10.244.1.15是存在的,但 MySQL 收到的来源 IP 却变成了节点 IP(10.0.0.5),于是按"新来源 IP"匹配授权规则,直接拒绝。

MySQL 会基于 client IP 做主机维度授权,这是最常见的中招点;同理,任何依赖源 IP 做鉴权/白名单/风控的服务(Redis ACL、对象存储桶策略、防火墙规则、审计系统)都可能出现类似症状。表象千奇百怪,本质只有一个:升级前后,到达 MySQL 的数据包源地址变了。

定位:clientIP 被 Masquerading 伪装的根因

Cilium 为什么要做 Masquerading

Pod 的 IPv4 地址通常从 RFC1918 私网段分配,默认不可公网路由。Cilium 会把离开集群的所有流量的源 IP 自动伪装成节点 IP,因为节点 IP 在网络上是可路由的。官方文档对默认行为描述得很直接(见 Documentation/network/concepts/masquerading.rst):

Cilium will automatically masquerade the source IP address of all traffic that is leaving the cluster to the IPv4 address of the node。

换句话说,Pod → 集群外 MySQL 的连接,源地址被换成节点 IP,是 Cilium 的默认设计,不是 bug。问题出在"什么算集群外/什么算集群内"的判定边界,在升级后变了。

判定边界的两个关键 CIDR

Cilium 判定"是否需要伪装"的核心,是看目的地址是否落在SNAT 排除 CIDR内。仓库源码pkg/datapath/iptables/iptables.go中,remoteSNATDstAddrExclusionCIDR的逻辑非常直白:

func (m *manager) remoteSNATDstAddrExclusionCIDR(nativeRoutingCIDR, allocCIDR netip.Prefix) netip.Prefix { if nativeRoutingCIDR.IsValid() { // ip{v4,v6}-native-routing-cidr is set, so use it return nativeRoutingCIDR } return allocCIDR }

也就是说:

  • 如果配置了ipv4-native-routing-cidr,排除 CIDR 就是它:目的地址落在该 CIDR 内的流量不做SNAT,Pod 源 IP 原样送达;
  • 如果没有配置,则退回到本节点 Pod 分配 CIDR(allocCIDR)。

iptables 模式下,最终下发的规则链是cilium masquerade non-cluster(见同一文件中的allEgressMasqueradeCmds):

-t nat -A CILIUM_POST_nat -! -d <snatDstExclusionCIDR> -s <allocRange> ! -o cilium_+ -j MASQUERADE

翻译成人话:只要目的地址不在排除 CIDR 里,且来源是 Pod 地址、出口不是 cilium_ 隧道口,一律 MASQUERADE 成节点 IP。

升级为什么会让 MySQL 中招

结合 v1.8 → v1.11 这个具体区间,升级前后最容易踩的坑有三个:

  1. ipv4-native-routing-cidr未显式配置:老版本里"原生路由/排除伪装"的判定依赖集群 Pod CIDR 等默认推断;升级后推断逻辑、IPAM 模式(如从 cluster-pool 切换为 ENI/Azure IPAM)或节点 CIDR 划分发生变化,导致原本被当作"集群内可直达、不伪装"的 MySQL 地址,在新版本里被划到了"需要伪装"的一侧。于是 Pod 源 IP 消失,MySQL 收到的是节点 IP。

  2. BPF Masquerading 与 iptables 模式的判定差异:文档明确警告(见 Documentation/network/concepts/masquerading.rst 与 kubeproxy-free.rst):eBPF 实现与 iptables 实现存在行为差异。典型例子是Pod → 节点 External IP 的流量:在 eBPF masquerading 下不会被伪装,而 iptables 模式会伪装;反过来,eBPF 模式下 pod-to-remote-node 在 overlay 路由里默认要伪装(代码注释里专门提到了 cilium/cilium#12624 这个坑,见bpf/lib/nat.h)。升级时如果顺手把bpf.masquerade=true打开了,伪装判定边界就整体换了实现,行为自然漂移。

  3. enable-remote-node-masquerade默认值变化:该选项控制"发往远端节点地址的流量是否伪装"。升级后若被显式/隐式打开,Pod → 节点 InternalIP 的流量也会被 SNAT。官方文档还特别提示:这会削弱对 Pod → Node 流量的 ingress host firewall 管控,建议默认关闭、谨慎开启。

BPF 数据路径的判定顺序在bpf/lib/nat.h的__snat_v4_needs_masquerade里写得很清楚:先看是否是回包(回包不 SNAT)→ 看是否命中 egress gateway 策略 → 再看目的地址是否落在ipv4_snat_exclusionCIDR 内(命中则放行不 SNAT)→ 再看是否命中 ip-masq-agent 的cilium_ipmasq_v4表 → 再判断远端节点/overlay 场景。任何一个边界条件在升级前后的配置差异,都可能改变 MySQL 连接最终呈现的源 IP。

规避方案与升级前检查清单

现场快速止血

确认是伪装导致的授权失败后,最快的止血手段是在 MySQL/目标服务侧,把节点 IP 也加入授权白名单——但这只是临时方案,会引入"来源不可信"的安全问题,必须尽快回到正轨。

更根本的止血:显式配置ipv4-native-routing-cidr,把 MySQL 所在网段纳入"原生路由、不做伪装"的 CIDR。文档给出的语义是:

ipv4-native-routing-cidr: 10.0.0.0/8(或 IPv6 用ipv6-native-routing-cidr),该 CIDR 内的所有目的地址不会被伪装。

配置后 Pod 源 IP 会原样到达 MySQL,授权表无需改动。注意:原生路由 CIDR 意味着 Cilium 依赖底层网络栈直接路由这些包,需要确保节点间路由/云 VPC 路由真的可达(自建集群需配合auto-direct-node-routes或手工路由)。

利用 ip-masq-agent 做精细豁免

如果不想全局放宽native-routing-cidr,Cilium 还内置了 eBPF 版 ip-masq-agent(Helm 选项ipMasqAgent.enabled=true),可以按目的 CIDR 白名单豁免伪装。仓库自带示例 examples/kubernetes-ip-masq-agent/rfc1918.yaml:

apiVersion: v1 kind: ConfigMap metadata: name: ip-masq-agent data: config: | nonMasqueradeCIDRs: - 10.0.0.0/8 - 172.16.0.0/12 - 192.168.0.0/16 masqLinkLocal: true

默认空配置时,agent 会内置 RFC1918 全段、100.64.0.0/10、192.0.0.0/24等一批非伪装 CIDR。把 MySQL 网段加进nonMasqueradeCIDRs,就能在不改变整体伪装策略的前提下恢复源 IP。下发后可用cilium-dbg bpf ipmasq list验证豁免表是否生效。

升级前检查清单

把这次事故沉淀成清单,升级 Cilium 前逐项核对:

  1. 记录升级前基线:先跑cilium-dbg status | grep Masquerading,记下当前 Masquerading 模式(BPF 还是 iptables)、生效设备与排除 CIDR,升级后对比同一输出。命令行参考见 Documentation/network/concepts/masquerading.rst。
  2. 锁定 masquerading 相关配置:在 Helm values 里显式写明bpf.masquerade、enableIPv4Masquerade、enableIPv6Masquerade、ipv4NativeRoutingCIDR、enableRemoteNodeMasquerade、egress-masquerade-interfaces,不要依赖默认值漂移。相关选项定义可查 pkg/option/config.go。
  3. 核对 IPAM 模式与 CIDR:升级常伴随 IPAM 模式调整(cluster-pool / ENI / Azure / GKE),确认ipv4NativeRoutingCIDR与云厂商 VPC CIDR 一致。云环境下若不配置,Cilium 会自动探测 VPC CIDR 作为原生路由范围(见 masquerading 文档),务必确认探测结果符合预期。
  4. 盘点依赖源 IP 鉴权的服务:升级前梳理所有"按 client IP 做授权"的目标(MySQL、Redis、云数据库白名单、防火墙、审计),在升级窗口内对它们的连接做抓包对比(tcpdump -n host <mysql-ip> and port 3306即可看到源 IP 是否被替换)。
  5. 灰度与回滚预案:先在非核心节点灰度升级,用上述抓包/cilium-dbg bpf ipmasq list验证 Pod 源 IP 是否原样到达目标服务;保留上一版本 Helm values 快照,便于快速回滚或 diff。

写在最后

这次事故的本质,不是 Cilium"坏了",而是伪装边界的默认判定在版本演进中发生了变化,而大多数集群没有显式锁定这些配置,导致"升级"这个动作本身成了变量。Masquerading 是 Cilium 面向公网出口的必要设计,但对集群内/同 VPC 内的"伪公网"流量(如云数据库),它反而会改写源 IP、破坏基于 IP 的授权模型。升级 Cilium 之前,把ipv4-native-routing-cidr、bpf.masquerade、enable-remote-node-masquerade这几个开关逐一显式化,比事后在 MySQL 里加一百条白名单更省心——毕竟,下一个被 5 万人围观的,可能是你的集群。

【免费下载链接】ciliumeBPF-based Networking, Security, and Observability项目地址: https://gitcode.com/GitHub_Trending/ci/cilium

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询