一、故障环境与问题现象
1.1 现场基础环境
MySQL服务节点(宿主机A,MySQL 旧主库):10.2.17.1
Docker业务节点(宿主机B,MySQL新主库):10.2.17.2 (部署了docker网桥方式的服务,估计是考虑到节约资源)
容器网络模式:Docker默认docker0网桥(容器内网段 172.17.0.0/16)
MySQL权限策略:账号白名单仅放行 10.2.17.% 物理网段,未配置容器私有网段权限
1.2 故障复现过程
VIP切换前(正常):MySQL业务VIP绑定在Docker宿主机A,容器访问数据库正常,业务无报错;
VIP切换后(异常):MySQL VIP漂移至宿主机B, 同一容器、同一数据库连接地址、无任何业务配置修改,数据库连接提示IP无访问权限。报错信息: Access denied for user 'app_user'@'172.17.0.12' (using password: YES)
1.3 初步排查结论
排除代码、账号密码、端口、防火墙、数据库配置问题,唯一变量为 MySQL VIP归属主机发生迁移,故障根因为Docker默认网桥差异化SNAT转发机制。二、故障根因:Docker0网桥的双重网络特性
这是运维、开发极易踩坑的Docker经典隐性问题: 多数人默认容器出站IP固定为172.17.0.x内网IP,实际Docker网桥流量IP动态变化。Docker0网桥核心核心机制: 访问本机服务不SNAT,访问跨机服务必SNAT。容器对外访问的源IP,不由容器自身决定,而是由 访问目标是否为宿主机本地服务决定,两种场景流量转发逻辑完全不同。三、两种流量场景全网路拆解
3.1 正常场景:VIP在宿主机A,宿主机B容器访问跨机MySQL
VIP在宿主机A(10.2.17.1)后,容器访问为 跨主机物理网络流量。 流量路径:容器(172.17.0.x) → docker0网桥 → 宿主机B上eth0物理网卡 → 物理交换机 → 远端宿主机A的MySQL(10.2.17.1) 流量特征:- 流量必须经过物理网卡与物理网络设备,属于跨主机外网流量;
- 强制触发Docker SNAT地址伪装,容器172内网IP被完全屏蔽;
- MySQL溯源IP变为 Docker宿主机物理IP 10.2.17.2。
- MySQL白名单为10.2.17.%,覆盖宿主机B的ip,可以正常登录
3.1 异常场景:VIP本地绑定,容器访问本机MySQL
VIP漂移后,MySQL VIP属于宿主机B本地IP,宿主机B上的容器访问属于 本机内网流量。 流量路径:容器(172.17.0.x) → docker0网桥 → 内核本地转发 → 宿主机B的MySQL 流量特征:- 流量全程在内核网络栈闭环, 不经过物理网卡eth0、不经过物理交换机;
- 不触发Docker SNAT伪装规则, 保留容器真实172.17.0.x私有IP;
- MySQL日志溯源客户端IP为容器内网IP(172.17.0.%)。
- MySQL白名单为10.2.17.%,未覆盖宿主机B的容器内网ip,拒绝登录。
四、Docker SNAT核心规则详解
上述差异化转发,由Docker默认自动生成的iptables NAT规则控制,属于标准设计而非BUG:iptables -t nat -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE 规则解析:- 源网段:172.17.0.0/16(Docker默认容器网段);
- 匹配条件:出站网卡非docker0(即跨本机、走物理网卡出站);
- 执行动作:MASQUERADE地址伪装,替换为宿主机物理IP。