自动化部署工具连接mysql卡在权限拒绝,根本原因是未按真实来源ip(如localhost或172.20.0.%)显式创建用户、未遵循create user→grant→flush privileges顺序、授予了过度权限,且未通过current_user()验证实际匹配账号。

自动化部署工具连接 MySQL 时卡在权限拒绝,不是密码错,而是它根本没被允许执行关键操作——比如创建用户、建库、刷权限。直接给 root 权限或用 '%'@'%' 是最常见翻车点,既不安全,又在容器、Ansible 等非交互场景下失效。
CREATE USER 必须显式限定 host,不能依赖 %
部署工具(如 Ansible mysql_user 模块、Docker init 脚本)通常从固定子网或 localhost 连接,但初始化 SQL 若写 CREATE USER 'deploy'@'%',MySQL 8+ 会因 host 匹配优先级问题拒绝认证,尤其当有 'deploy'@'localhost' 冲突时。
- 查清工具真实来源 IP:Ansible 默认走 SSH 跳板机 → 连本地 socket,实际是
'deploy'@'localhost';Docker Compose 则多为'deploy'@'172.20.0.%' - 建用户时必须写死:
CREATE USER 'deploy_dev'@'localhost' IDENTIFIED BY 'xxx';或CREATE USER 'deploy_prod'@'10.0.2.%' IDENTIFIED BY 'xxx'; - 绝不用
FLUSH PRIVILEGES前就授予权限——先CREATE USER,再GRANT,最后FLUSH PRIVILEGES(容器 init 脚本中这步不可省)
授予最小必要管理权限,避开 ALL PRIVILEGES
GRANT ALL ON *.* 看似省事,实则让部署工具拿到 CREATE USER、SHUTDOWN、RELOAD 等高危权限,CI/CD 流水线一旦被注入恶意脚本,后果严重。
- 只授部署真正需要的权限:
SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX, CREATE VIEW, SHOW VIEW, EXECUTE, CREATE ROUTINE, ALTER ROUTINE - 全局权限仅限必要项:
REPLICATION CLIENT(查主从状态)、PROCESS(看当前连接),不要授FILE或GRANT OPTION - 若需建库,用
GRANT CREATE ON `dev_%`.* TO 'deploy_dev'@'localhost';,靠库名前缀限制作用域
Ansible / Shell 脚本中避免明文密码和连接泄漏
非交互模式下,密码一旦出现在命令行或日志里,就会被 ps aux、/var/log/audit.log 或 CI 构建历史捕获。
- Ansible 中禁用
login_password参数直连,改用mysql_user模块的login_unix_socket: /var/run/mysqld/mysqld.sock(本地 socket 认证) - 密码变量必须用 Vault 加密,且按环境拆分:
mysql_deploy_user_dev_password、mysql_deploy_user_prod_password,不得复用 - Shell 脚本中不用
mysql -uuser -ppass,改用配置文件:mysql --defaults-file=/etc/mysql/deploy.cnf -e "GRANT ...",并设chmod 600 /etc/mysql/deploy.cnf
验证权限是否生效:别信 SHOW GRANTS,要看 CURRENT_USER()
部署后连不上?错误提示常是 Access denied for user,但你 SHOW GRANTS FOR 'deploy' 显示一切正常——这是因为 MySQL 匹配的是 CURRENT_USER(),不是你登录时声明的用户名。
- 进 MySQL 后立即执行:
SELECT USER(), CURRENT_USER();,前者是你“声称”的身份,后者才是 MySQL 实际匹配到的账号(含 host) - 如果返回
'deploy'@'%'和'deploy'@'localhost'不一致,说明 host 限定失败,得回去检查CREATE USER语句 - 用部署工具同一网络环境复现连接:
mysql -u deploy_dev -h 127.0.0.1 -p(强制走 TCP)vsmysql -u deploy_dev -S /var/run/mysqld/mysqld.sock(走 socket),行为可能不同
非交互场景下权限问题最难 debug 的地方,在于它不报具体缺哪个权限,而是在连接建立后某条语句突然失败。所以初始化脚本里每一步都要加 || exit 1,并且把 CURRENT_USER() 输出打到部署日志开头——否则你永远不知道工具到底以谁的身份在操作。











