环境: docker mysql 8.4.11 on linux (ubuntu 22.04)
容器名称: daily_test_mysql
1. 问题概述
1.1 问题描述
在一个 docker 容器中运行 mysql 8.4.11 服务,本地(通过 127.0.0.1 或 unix socket)可以正常登录,但另一台远程服务器无法通过公网 ip 进行远程访问,始终返回 error 1045 (28000): access denied。
1.2 环境信息
| 组件 | 配置 |
|---|---|
| 数据库 | mysql 8.4.11 (docker 容器) |
| 容器镜像 | mysql:8.4 |
| 宿主机内网 ip | 172.16.1.234/12 |
| 公网 ip | 103.236.97.252(通过云 nat 映射) |
| 远程服务器 ip | 117.72.76.108 |
| 远程客户端 | mysql 8.0.46-0ubuntu0.22.04.3 |
| 初始端口映射 | 容器 3306 → 宿主机 3308 |
| 最终端口映射 | 容器 3306 → 宿主机 3309 |
| 容器数据卷 | /var/lib/docker/volumes/61a6d717.../_data |
2. 排查流程
2.1 排查路线图

上图展示了从问题报告到最终解决的完整排查路径。关键转折点出现在 step 6(多维度连接对比测试)和 step 7(握手包分析),通过对比不同网络路径的连接结果,逐步排除了 mysql 配置、认证插件、防火墙等可能性,最终将问题锁定在云 nat 端口转发层面。
用户报告远程无法连接
│
▼
┌─────────────────────────────────────┐
│ step 1: 容器状态与端口映射检查 │
│ - docker ps → 容器运行正常 │
│ - docker port → 3306→3308 映射正确 │
│ - ss -tlnp → docker-proxy 监听正常 │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ step 2: mysql 用户权限检查 │
│ - docker exec 进入容器 │
│ - 查询 mysql.user 表 │
│ → root@% 存在,允许远程连接 │
│ → admin@% 存在,允许远程连接 │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ step 3: mysql 网络配置检查 │
│ - bind_address = * ✅ │
│ - skip_networking = off ✅ │
│ - require_secure_transport = off │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ step 4: 宿主机防火墙检查 │
│ - iptables input 策略 accept │
│ - 无 drop 3308 规则 │
│ - 未启用 ufw/firewalld │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ step 5: 认证插件兼容性排查 │
│ - mysql 8.4 默认 caching_sha2 │
│ - 改为 mysql_native_password │
│ → 本地连接成功,远程仍失败 │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ step 6: 多维度连接测试 │
│ - 127.0.0.1:3306 → ✅ 成功 │
│ - 172.16.1.234:3308 → ✅ 成功 │
│ - 容器内 → 公网ip:3308 → ❌ 失败 │
│ - 远程服务器 → 公网ip:3308 → ❌ │
│ → 所有用户均失败(root,admin等) │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ step 7: 网络层深入排查 ★关键发现★ │
│ - 抓取 mysql 握手包对比 │
│ → 公网ip:3308 → mysql 8.4.10 │
│ → 内网ip:3308 → mysql 8.4.11 │
│ → 这是两个不同的服务器! │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ step 8: 端口变更与最终验证 │
│ - 申请新端口 3309 转发 │
│ - 创建新容器,映射 3309:3306 │
│ - 配置 mysql_native_password │
│ - 放行云防火墙 │
│ → 远程连接成功 ✅ │
└─────────────────────────────────────┘
2.2 排查时间线
| 阶段 | 操作 | 结论 |
|---|---|---|
| t1 | 运行 docker ps 确认容器状态 | 容器 daily_test_mysql 运行正常 |
| t2 | 查询 mysql.user 表 | root@% 和 admin@% 均存在 |
| t3 | 检查 bind_address、skip_networking | 配置正常,允许 tcp 远程连接 |
| t4 | 检查 iptables 规则 | 防火墙未拦截 3308 端口 |
| t5 | 检查 authentication_policy | 默认仅允许 caching_sha2_password |
| t6 | 启用 mysql_native_password 插件 | 插件已激活,root@% 已切换 |
| t7 | 多维度连接对比测试 | 内网 ip 成功,公网 ip 失败 |
| t8 | 抓取 mysql 握手包版本对比 | 公网和内网指向不同 mysql 实例 |
| t9 | 确认云 nat 端口转发配置 | 3308 端口被转发到其他机器 |
| t10 | 申请 3309 端口转发,重建容器 | 最终连接成功 |
3. 各阶段详细分析
3.1 容器状态与端口映射检查
执行命令:
docker ps docker port daily_test_mysql ss -tlnp | grep 3308
结果分析:
- 容器正常运行,状态为
up - 端口映射正确:
3306/tcp → 0.0.0.0:3308 - docker 用户态代理(docker-proxy)监听在
0.0.0.0:3308和[::]:3308
结论: 容器层面无异常,端口映射配置正确。
3.2 mysql 用户权限检查
执行命令:
docker exec daily_test_mysql mysql -uroot -p123456 \ -e "select user, host, plugin from mysql.user;"
结果:
user host plugin root % caching_sha2_password root localhost caching_sha2_password admin % caching_sha2_password
分析:
root@%中的%是通配符,匹配所有主机admin@%同样允许任意主机连接- 用户权限配置正确,理论上应允许远程连接
3.3 mysql 网络配置检查
检查项:
show variables like 'bind_address'; -- → * (监听所有接口) show variables like 'skip_networking'; -- → off (允许tcp/ip) show variables like 'require_secure_transport'; -- → off (不强制ssl)
结论: mysql 网络配置无异常,所有接口均开放 tcp 连接。
3.3.1 mysql 配置文件详解
mysql 的配置通过多个配置文件层级加载,优先级从低到高如下:
/etc/my.cnf # 全局配置(mysql 服务级别) /etc/mysql/my.cnf # 全局配置(mysql 发行版级别) /etc/mysql/conf.d/*.cnf # 附加配置目录(推荐放置自定义配置) ~/.my.cnf # 用户级别配置(仅影响该用户客户端)
在 docker 容器中,mysql:8.4 镜像自带的配置文件位于 /etc/mysql/ 目录下。自定义配置应放在 /etc/mysql/conf.d/ 目录下,以 .cnf 扩展名结尾。
配置文件结构
mysql 配置文件采用 ini 风格的节(section)结构:
[client] # 客户端工具(mysql, mysqldump 等)的配置 port=3306 [mysql] # mysql 命令行客户端的配置 prompt="\\u@\\h [\\d]> " [mysqld] # mysqld 服务器进程的配置 ← 这是最关键的节 bind-address=* port=3306 max_connections=200
远程访问相关的核心配置项
| 配置项 | 默认值 | 说明 | 远程访问要求 |
|---|---|---|---|
bind-address | * (mysql 8.4) | 监听的网络接口 | 必须设为 * 或 0.0.0.0,不能是 127.0.0.1 |
skip-networking | off | 是否禁用 tcp/ip | 必须为 off(注释掉或设为 0) |
port | 3306 | mysql 监听端口 | 需与 docker 端口映射一致 |
require_secure_transport | off | 是否强制 ssl/tls | 可设为 off 允许非 ssl 连接 |
mysql_native_password | on (mysql 8.4+) | 启用传统认证插件 | 建议设为 on 兼容旧客户端 |
skip_name_resolve | off | 跳过 dns 反向解析 | 建议设为 on 避免 dns 超时 |
max_connect_errors | 100 | 最大连接错误次数 | 可调大防止 ip 被锁定 |
正确和错误的配置对比
❌ 错误配置(导致只能本地连接):
[mysqld] bind-address = 127.0.0.1 # 只监听本机,拒绝远程连接! skip-networking = on # 完全禁用 tcp/ip!
✅ 正确配置(允许远程连接):
[mysqld] bind-address = * # 监听所有网络接口 skip-networking = off # 允许 tcp/ip 连接(默认值,可不写) port = 3306 # mysql 监听端口 mysql_native_password = on # 启用传统认证插件(兼容旧客户端) skip_name_resolve = on # 跳过 dns 反向解析,避免连接卡顿
在 docker 容器中应用配置
方法一:通过 docker run 挂载配置目录(推荐)
# 1. 在宿主机创建配置文件 mkdir -p /root/mysql-conf cat > /root/mysql-conf/my-custom.cnf << 'eof' [mysqld] mysql_native_password = on skip_name_resolve = on max_connect_errors = 1000 eof # 2. 启动容器时挂载配置目录 docker run -d --name daily_test_mysql \ -p 3309:3306 \ -v /root/mysql-conf:/etc/mysql/conf.d \ -e mysql_root_password=123456 \ mysql:8.4
方法二:容器运行后复制配置文件(用于已有容器)
# 1. 创建配置文件 cat > /tmp/mysql_custom.cnf << 'eof' [mysqld] mysql_native_password = on skip_name_resolve = on eof # 2. 复制到容器内 docker cp /tmp/mysql_custom.cnf daily_test_mysql:/etc/mysql/conf.d/ # 3. 重启容器 docker restart daily_test_mysql
方法三:通过 docker-compose.yml 管理(推荐生产环境)
version: '3.8'
services:
mysql:
image: mysql:8.4
container_name: daily_test_mysql
ports:
- "3309:3306"
environment:
mysql_root_password: 123456
volumes:
- mysql_data:/var/lib/mysql
- ./mysql-conf:/etc/mysql/conf.d
restart: unless-stopped
volumes:
mysql_data:验证配置是否生效
# 查看配置文件是否被正确加载 docker exec daily_test_mysql mysql -uroot -p123456 \ -e "show variables like 'mysql_native_password';" # 输出应为 active # variable_name value # mysql_native_password on
排查配置文件常见问题
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 配置不生效 | 文件不在 /etc/mysql/conf.d/ 目录 | 使用 docker cp 复制到正确位置 |
| 配置不生效 | 文件名不是 .cnf 后缀 | 确保文件名以 .cnf 结尾 |
| 配置不生效 | 文件权限不正确 | 设为 644(chmod 644) |
| 容器启动失败 | 配置语法错误 | 检查 [mysqld] 节名拼写 |
| 配置被覆盖 | 多个配置文件中相同项冲突 | 后加载的配置会覆盖先加载的 |
3.4 认证插件兼容性分析
mysql 8.4 认证机制:
mysql 8.0 起默认认证插件从 mysql_native_password 变更为 caching_sha2_password,mysql 8.4 进一步将 mysql_native_password 标记为废弃(deprecated),默认不加载。
authentication_policy = *, ← 表示仅允许 caching_sha2_password
影响:
- 如果客户端(如旧版 php、python、java 连接器)不支持
caching_sha2_password,需要通过--get-server-public-key参数或 ssl 连接 - mysql 8.0.46 客户端支持
caching_sha2_password,但需额外握手步骤
处理过程:
- 写入配置文件
/etc/mysql/conf.d/mysql_native.cnf启用插件 - 重启容器使配置生效
- 执行
alter user 'root'@'%' identified with mysql_native_password by '123456'
注意: 即使切换了认证插件,本地连接(127.0.0.1)成功,但远程连接仍然失败——这说明问题不在认证插件本身。
3.5 多维度连接对比测试
这是排查过程中最关键的一步。通过对比不同连接路径的结果,可以精确判断问题出在哪个网络层面。

上图汇总了 5 组不同网络路径的连接测试结果。关键规律是:所有经过公网 ip 的路径全部失败,所有内网路径全部成功。这一对比强烈暗示问题不在 mysql 本身,而在公网到内网的网络转发层。
测试 1:容器内回环接口连接(验证 mysql 服务本身是否正常)
# 在容器内通过 127.0.0.1 连接 mysql(不经过 docker 端口映射) docker exec daily_test_mysql mysql -u root -p123456 \ -h 127.0.0.1 -p 3306 \ -e "select current_user(), '连接成功' as status;"
输出:
current_user() status root@% 连接成功
结论: mysql 服务正常,root@% 用户密码正确,tcp 协议栈工作正常。
测试 2:宿主机内网 ip 连接(验证 docker 端口映射是否正常)
# 在容器内通过宿主机内网 ip 连接(经过 docker 端口映射 3308→3306) docker exec daily_test_mysql mysql -u root -p123456 \ -h 172.16.1.234 -p 3308 \ -e "select '内网ip连接测试' as status;"
输出:
status 内网ip连接测试
结论: docker 端口映射正常,docker-proxy 正确转发连接。
测试 3:公网 ip 连接(验证云 nat 转发是否正常)
# 在容器内通过公网 ip 连接(经过云 nat → docker 端口映射) docker exec daily_test_mysql mysql -u root -p123456 \ -h 103.236.97.252 -p 3308 \ -e "select '公网ip连接测试' as status;"
输出:
error 1045 (28000): access denied for user 'root'@'103.236.97.252' (using password: yes)
测试 4:其他用户也失败(排除密码错误可能性)
# 使用 admin 用户测试 docker exec daily_test_mysql mysql -u admin -p123456 \ -h 103.236.97.252 -p 3308 \ -e "select 'admin公网测试' as status;" # 使用 testuser 用户测试 docker exec daily_test_mysql mysql -u testuser -ptest123 \ -h 103.236.97.252 -p 3308 \ -e "select 'testuser公网测试' as status;"
输出:
error 1045 (28000): access denied for user 'admin'@'103.236.97.252' (using password: yes) error 1045 (28000): access denied for user 'testuser'@'103.236.97.252' (using password: yes)
关键推论: 所有用户、所有密码在公网 ip 路径下全部失败,这排除了密码错误、用户权限、认证插件等 mysql 层面的可能性。
测试 5:直连容器内部 ip(绕过 docker 代理,验证容器网络)
# 查看容器内网 ip docker inspect daily_test_mysql | grep "ipaddress" # 输出: "ipaddress": "192.168.0.2" # 从宿主机直接连接容器内部 ip(不经过 docker-proxy) docker run --rm --network host mysql:8.4 mysql -u root -p123456 \ -h 192.168.0.2 -p 3306 \ -e "select '直连容器' as status;"
输出:
status 直连容器
结论: 容器内部 mysql 服务完全正常,问题出在公网 ip 到本机的网络路径上。
测试结果汇总
| 测试编号 | 连接路径 | 命令 | 结果 |
|---|---|---|---|
| 1 | 容器内 → 127.0.0.1:3306 | mysql -h 127.0.0.1 -p 3306 | ✅ 成功 |
| 2 | 容器内 → 172.16.1.234:3308 | mysql -h 172.16.1.234 -p 3308 | ✅ 成功 |
| 3 | 容器内 → 103.236.97.252:3308 | mysql -h 103.236.97.252 -p 3308 | ❌ 失败 |
| 4 | 容器内 → 103.236.97.252:3308 (admin) | mysql -u admin -h 103.236.97.252 -p 3308 | ❌ 失败 |
| 5 | 宿主机 → 192.168.0.2:3306 | mysql -h 192.168.0.2 -p 3306 | ✅ 成功 |
关键发现:
- 所有通过公网 ip
103.236.97.252:3308的连接全部失败 - 所有通过内网 ip(
127.0.0.1、172.16.1.234、192.168.0.2)的连接全部成功 - 失败和成功的路径唯一的区别是:公网 ip 的流量经过云 nat 网关
- 这强烈暗示问题出在云 nat 的端口转发配置上
3.6 握手包分析(决定性证据)
这一步是最终找到根本原因的关键。通过解析 mysql 协议握手包,可以精确识别出对端 mysql 服务器的版本号,从而判断公网 ip 和内网 ip 是否指向同一个实例。

上图展示了从公网 ip 和内网 ip 分别抓取的 mysql 握手包对比。版本号 8.4.10 vs 8.4.11 的差异是决定性证据,证明公网和内网指向的是两台 完全不同的 mysql 服务器实例。
技术原理:mysql 握手包格式
mysql 客户端与服务器建立 tcp 连接后,服务器会首先发送一个握手包(handshake packet),其格式如下:
┌─────────────────────────────────────────────────┐ │ 包长度 (3 bytes) | 序列号 (1 byte) │ │ protocol version (1 byte) │ │ server version (null-terminated string) │ │ connection id (4 bytes) │ │ auth plugin data part 1 (8 bytes) │ │ filler (1 byte) │ │ capability flags (2 bytes) │ │ character set (1 byte) │ │ status flags (2 bytes) │ │ capability flags (upper 2 bytes) │ │ ... │ │ auth plugin name (null-terminated string) │ └─────────────────────────────────────────────────┘
其中服务器版本字符串位于偏移量 5 字节处(第 5 个字节开始),以 null 字节(\x00)结尾。
执行命令
使用 python 脚本分别从公网 ip 和内网 ip 抓取 mysql 握手包:
# 测试 1:从公网 ip 103.236.97.252:3308 抓取握手包
python3 -c "
import socket
s = socket.socket()
s.settimeout(5)
s.connect(('103.236.97.252', 3308))
data = s.recv(1024)
s.close()
version = data[1:data.index(0, 1)].decode()
print(f'公网ip连接的mysql版本: {version}')
print(f'握手包前50字节(hex): {data[:50].hex()}')
"
# 测试 2:从内网 ip 172.16.1.234:3308 抓取握手包
python3 -c "
import socket
s = socket.socket()
s.settimeout(5)
s.connect(('172.16.1.234', 3308))
data = s.recv(1024)
s.close()
version = data[1:data.index(0, 1)].decode()
print(f'内网ip连接的mysql版本: {version}')
print(f'握手包前50字节(hex): {data[:50].hex()}')
"原始输出
![[pasted image 20260819011659.png]]
公网ip连接的mysql版本: 8.4.10 握手包前50字节(hex): 4a0000000a382e342e31300069da00001c7f7c05524d5a7500ffffe00200ffdf15 内网ip连接的mysql版本: 8.4.11 握手包前50字节(hex): 4a0000000a382e342e313100280000000c52665a6d550c3000ffffff0200ffdf15
逐字节解析
公网 ip 握手包:4a0000000a382e342e313000...
| 位置 | 字节(hex) | 含义 | 值 |
|---|---|---|---|
| 0-2 | 4a 00 00 | 包长度(小端序) | 74 字节 |
| 3 | 00 | 序列号 | 0 |
| 4 | 0a | 协议版本 | 10(mysql 协议 v10) |
| 5 | 38 | 版本字符串: ‘8’ | |
| 6 | 2e | 版本字符串: ‘.’ | |
| 7 | 34 | 版本字符串: ‘4’ | |
| 8 | 2e | 版本字符串: ‘.’ | |
| 9 | 31 | 版本字符串: ‘1’ | |
| 10 | 30 | 版本字符串: ‘0’ | |
| 11 | 00 | null 终止符 | 版本: “8.4.10” |
内网 ip 握手包:4a0000000a382e342e313100...
| 位置 | 字节(hex) | 含义 | 值 |
|---|---|---|---|
| 0-2 | 4a 00 00 | 包长度(小端序) | 74 字节 |
| 3 | 00 | 序列号 | 0 |
| 4 | 0a | 协议版本 | 10(mysql 协议 v10) |
| 5 | 38 | 版本字符串: ‘8’ | |
| 6 | 2e | 版本字符串: ‘.’ | |
| 7 | 34 | 版本字符串: ‘4’ | |
| 8 | 2e | 版本字符串: ‘.’ | |
| 9 | 31 | 版本字符串: ‘1’ | |
| 10 | 31 | 版本字符串: ‘1’ | |
| 11 | 00 | null 终止符 | 版本: “8.4.11” |
关键差异对比
| 特性 | 公网 103.236.97.252:3308 | 内网 172.16.1.234:3308 |
|---|---|---|
| mysql 版本 | 8.4.10 | 8.4.11 |
| 版本字符串 hex | 38 2e 34 2e 31 30 00 | 38 2e 34 2e 31 31 00 |
| connection id | 69 da 00 00 (0xda69=55913) | 28 00 00 00 (0x28=40) |
| auth plugin data | 1c 7f 7c 05 52 4d 5a 75 | 0c 52 66 5a 6d 55 0c 30 |
| capability flags | ff ff e0 02 | ff ff 02 00 |
connection id 不同:公网和内网的 connection id 不同,说明是两次独立的 mysql 连接,而非同一连接的镜像。
auth plugin data 不同:认证数据(随机盐值)不同,说明是两台不同的 mysql 服务器实例。
版本号不同:8.4.10 和 8.4.11 是两个不同的发行版本,这证明了公网 ip 指向的是完全不同的 mysql 实例。
决定性结论
公网 ip 103.236.97.252:3308 指向的 mysql 服务器(8.4.10)与当前机器的 mysql 容器(8.4.11)不是同一个实例。
云 nat 网关将 3308 端口转发到了内网中另一台机器的 mysql 8.4.10 实例上。这就是为什么所有用户、所有密码都验证失败的根本原因——用户一直在连接别人的 mysql。
4. 根本原因分析
4.1 直接原因
云网络端口转发配置错误: 云平台 nat 网关将公网 ip 103.236.97.252 的 3308 端口转发到了内网中另一台机器上运行的 mysql 8.4.10 实例,而不是用户当前所在的 172.16.1.234 机器。
4.2 技术原理
云 nat 端口转发机制

上图清晰展示了云 nat 网关的端口转发逻辑。3308 端口被错误地 dnat 到了内网另一台机器(172.16.x.x),而 3309 端口才正确映射到本机(172.16.1.234)。这是多租户云环境中端口分配冲突的典型场景。
远程服务器 云平台 nat 网关 内部网络
117.72.76.108 │ 172.16.0.0/12
│ │ │
│ ┌──────────────────────────────────────────┐ │
├──→ 103.236.97.252:3308 │ │
│ │ dnat 规则匹配 │ │
│ │ 3308/tcp → 172.16.x.x:3308 (其他机器) ├──→ ❌ │
│ │ 3309/tcp → 172.16.1.234:3309 (本机) ├──→ ✅ │
│ └──────────────────────────────────────────┘ │
│ │
└──→ 103.236.97.252:3309 → 本机 → mysql 8.4.11 ✅
nat(network address translation)网关在云环境中负责将公网 ip 的端口映射到内网机器的对应端口。当多个内网机器需要暴露服务时,端口分配是互斥的——同一个端口不能被映射到两台不同的机器。
为什么之前会有"密码错误"的错觉
当远程服务器连接到 103.236.97.252:3308 时:
- 连接成功到达另一台机器的 mysql 8.4.10
- 该 mysql 的 root 密码不是
123456 - mysql 返回标准的
error 1045 (28000): access denied - 用户误以为是自己的 mysql 配置有问题
这正是 access denied 作为通用错误码的迷惑性——它可能意味着密码错误,也可能意味着连接到了错误的服务器。
4.3 ssh 端口与 mysql 端口的关系
用户提到 ssh 使用 103.236.97.252:52704 登录,这表明:
- 公网 ip
103.236.97.252对应一个云 nat 网关 - ssh 端口
52704被映射到用户远程服务器的 ssh 服务 - 不同端口可以映射到不同的内网机器
- 这种多端口映射是多租户云环境的常见做法
5. 基础设施评估
5.1 物理机划分评估
评估结论: 不存在物理机划分不当的问题。
判断依据:
- 当前环境是云平台上的虚拟化环境,而非裸金属物理机划分
- 内网 ip
172.16.1.234/12表明所有机器在同一个大二层网络内 - 云 nat 网关的端口转发是标准的多租户网络隔离方案
- 不同机器通过 nat 共享同一个公网 ip,各自暴露不同端口,这是合理的架构设计
5.2 资源配置评估
评估结论: 不存在明显的资源配置不足问题。
判断依据:
- mysql 容器运行正常,无 oom 或资源争抢现象
- docker 数据卷映射正常,磁盘 i/o 无异常
- 网络延迟在正常范围内(连接建立时间 < 5ms)
- 容器创建时间仅 41 分钟,处于初始运行阶段,资源使用量低
5.3 虚拟化实现缺陷评估
评估结论: 未发现虚拟化技术实现缺陷。
判断依据:
- docker 容器网络(bridge 模式)工作正常
- docker-proxy 正确转发 tcp 连接
- iptables dnat 规则正确:
dnat tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:3309 to:192.168.0.2:3306
- 内网 ip 直连容器成功,证明 docker 网络堆栈无异常
- 云 nat 网关的端口转发问题是配置层面(人为/策略)而非技术缺陷
5.4 根本原因归类
问题类别:配置错误(configuration error) 具体类型:云网络端口转发配置错误(cloud nat port forwarding misconfiguration) 严重程度:中(medium) 影响范围:仅影响公网远程访问,本地和内网访问不受影响
6. 解决方案与建议
6.1 已实施的解决方案
- 申请新端口转发:将公网 ip
103.236.97.252的3309端口转发到本机172.16.1.234:3309 - 重建容器:删除旧容器,使用新端口映射创建新容器
docker run -d --name daily_test_mysql \ -p 3309:3306 \ -e mysql_root_password=123456 \ mysql:8.4
- 配置认证插件:启用
mysql_native_password插件并切换root@%认证方式 - 放行防火墙:在云平台安全组中添加入站规则,允许 tcp 3309 端口
6.2 长期建议
6.2.1 网络层面
- 使用独立公网 ip:如果条件允许,为 mysql 服务申请独立的公网 ip,避免端口冲突
- 端口规划:建立端口分配清单,记录每个端口映射的目标机器和服务,避免端口冲突
- 监控 nat 规则:定期审计云 nat 网关的端口转发规则,确保配置正确
6.2.2 mysql 层面
- 认证插件策略:mysql 8.4 已废弃
mysql_native_password,建议评估客户端兼容性,逐步迁移到caching_sha2_password - ssl/tls 加密:启用强制 ssl 连接提供更高的安全性
- 连接日志:开启 mysql 审计日志,记录所有连接尝试(包括失败的),便于快速定位问题
6.2.3 运维层面
- 连接验证脚本:部署前执行自动化连接测试,验证从不同网络路径的访问
- 文档化:维护网络拓扑和端口映射文档,类似本报告可作为参考
6.3 快速排查清单
未来遇到类似问题,可按以下顺序排查:
□ 1. docker ps / docker port → 确认容器和端口映射 □ 2. docker exec → 查询 mysql.user 表 □ 3. show variables → 检查 bind_address, skip_networking □ 4. ss -tlnp / iptables → 检查防火墙 □ 5. 对比测试:内网 ip vs 公网 ip □ 6. 抓取握手包:对比 mysql 版本号 □ 7. 检查云平台 nat 端口转发规则 □ 8. 检查云平台安全组/防火墙规则
7. 附录
7.1 关键命令速查
# 查看容器状态和端口映射
docker ps
docker port daily_test_mysql
# 查看 mysql 用户权限
docker exec daily_test_mysql mysql -uroot -p123456 \
-e "select user, host, plugin from mysql.user;"
# 查看 mysql 配置
docker exec daily_test_mysql mysql -uroot -p123456 \
-e "show variables like 'bind_address';"
# 查看端口监听状态
ss -tlnp | grep 3309
# 抓取 mysql 握手包
python3 -c "
import socket
s = socket.socket()
s.settimeout(5)
s.connect(('目标ip', 端口))
data = s.recv(1024)
s.close()
version = data[1:data.index(0, 1)].decode()
print(f'mysql版本: {version}')
"
# 测试端口连通性
curl -v telnet://103.236.97.252:3309 --connect-timeout 5
以上就是mysql远程访问故障排查与解决方法的详细内容,更多关于mysql远程访问故障的资料请关注代码网其它相关文章!
发表评论