使用Ansible一键自动化部署MySQL主从复制配置的详细教程与常见问题排查

引言:为什么选择Ansible自动化MySQL主从复制
MySQL主从复制是现代Web架构中保证高可用性和负载均衡的关键技术。然而,手动配置主从复制涉及多个复杂步骤,容易出错且难以维护。Ansible作为一款强大的自动化工具,能够通过声明式YAML语法实现一键部署,大大降低了运维门槛。
本文将详细介绍如何使用Ansible自动化部署MySQL主从复制,包括环境准备、Playbook编写、执行步骤以及常见问题排查。我们将提供完整的、可直接运行的代码示例,帮助您快速构建生产级的自动化部署方案。
环境准备与架构设计
硬件与软件要求
在开始之前,请确保满足以下条件:
操作系统
:CentOS
7
⁄
8
或 Ubuntu 20.04/22.04(本文以CentOS 7为例)
节点规划
:
主节点(Master)
:192.168.1.100
从节点(Slave)
:192.168.1.101
Ansible控制节点
:可以是任意一台安装Ansible的机器(建议与主节点分开)
网络要求
:所有节点之间SSH端口(默认22)和MySQL端口(默认3306)互通
软件版本
:
Ansible 2.9+
MySQL 5.7+ 或 MariaDB 10.2+
网络拓扑图
┌─────────────────┐
│ Ansible控制节点 │
│ (192.168.1.50) │
└────────┬────────┘
│ SSH
▼
┌─────────────────┐ ┌─────────────────┐
│ MySQL主节点 │◄────►│ MySQL从节点 │
│ (192.168.1.100)│ │ (192.168.1.101)│
└─────────────────┘ └─────────────────┘
│ MySQL Replication
└───────────────────►
核心概念:MySQL主从复制原理
在深入Ansible配置之前,我们需要理解MySQL主从复制的基本原理,这有助于我们编写正确的自动化脚本。
复制机制概述
MySQL主从复制基于二进制日志(Binary Log)实现,主要包含三个线程:
Binlog Dump线程
:在主节点上运行,负责发送二进制日志内容
I/O线程
:在从节点上运行,负责接收主节点的二进制日志并写入中继日志(Relay Log)
SQL线程
:在从节点上运行,负责读取中继日志并执行SQL语句
关键配置参数
参数名
作用
主节点
从节点
server-id
唯一标识符
必须唯一
必须唯一且≠主节点
log_bin
二进制日志开关
必须开启
关闭
binlog_format
日志格式
推荐ROW
–
relay_log
中继日志路径
–
必须指定
read_only
只读模式
–
推荐开启
Ansible Playbook完整实现
目录结构
首先创建以下目录结构,便于管理:
mysql-replication/
├── inventory
├── group_vars/
│ ├── all.yml
│ ├── master.yml
│ └── slave.yml
├── roles/
│ ├── common/
│ │ └── tasks/
│ │ └── main.yml
│ ├── mysql_master/
│ │ ├── tasks/
│ │ │ └── main.yml
│ │ └── templates/
│ │ └── my.cnf.j2
│ └── mysql_slave/
│ ├── tasks/
│ │ └── main.yml
│ └── templates/
│ └── my.cnf.j2
├── site.yml
└── vars/
└── main.yml
1. 定义Inventory文件
创建
inventory
文件,定义主从节点:
[master]
master-node ansible_host=192.168.1.100 ansible_user=root

[slave]
slave-node ansible_host=192.168.1.101 ansible_user=root

[all:vars]
ansible_ssh_private_key_file=~/.ssh/id_rsa
ansible_python_interpreter=/usr/bin/python3
2. 定义变量文件
group_vars/all.yml(全局变量)
# MySQL通用配置
mysql_version: “5.7”
mysql_root_password: “RootPassword123!”
mysql_repl_user: “repl_user”
mysql_repl_password: “ReplPassword123!”
mysql_port: 3306
mysql_data_dir: “/var/lib/mysql”
mysql_log_dir: “/var/log/mysql”

# Ansible连接参数
ansible_ssh_common_args: ‘-o StrictHostKeyChecking=no’
group_vars/master.yml(主节点变量)
# 主节点特有配置
mysql_server_id: 1
mysql_binlog_do_db: “” # 留空表示同步所有数据库
mysql_binlog_format: “ROW”
mysql_log_bin: “/var/log/mysql/mysql-bin”
group_vars/slave.yml(从节点变量)
# 从节点特有配置
mysql_server_id: 2
mysql_relay_log: “/var/log/mysql/mysql-relay-bin”
mysql_read_only: 1
3. 编写Common Role(通用任务)
roles/common/tasks/main.yml
:
—
– name: 安装MySQL Yum仓库(CentOS)
yum:
name: https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm
state: present
when: ansible_os_family == “RedHat”

– name: 安装MySQL服务器
yum:
name: mysql-community-server
state: present
when: ansible_os_family == “RedHat”

– name: 创建MySQL日志目录
file:
path: “{{ mysql_log_dir }}”
state: directory
owner: mysql
group: mysql
mode: ‘0755’

– name: 启动MySQL服务
systemd:
name: mysqld
state: started
enabled: yes

– name: 获取MySQL临时密码(CentOS 7)
shell: “grep ‘temporary password’ /var/log/mysqld.log | awk ‘{print $NF}’”
register: temp_password
changed_when: false
when: ansible_os_family == “RedHat”

– name: 修改MySQL root密码
shell: |
mysql -e “ALTER USER ‘root’@’localhost’ IDENTIFIED BY ‘{{ mysql_root_password }}’;”
args:
executable: /bin/bash
no_log: true
when: temp_password is defined and temp_password.stdout != “”
4. 编写Master Role(主节点任务)
roles/mysql_master/tasks/main.yml
:
—
– name: 生成主节点my.cnf配置
template:
src: my.cnf.j2
dest: /etc/my.cnf
owner: mysql
group: mysql
mode: ‘0644’
notify: 重启MySQL

– name: 创建复制用户
mysql_user:
name: “{{ mysql_repl_user }}”
password: “{{ mysql_repl_password }}”
priv: “*.*:REPLICATION SLAVE,REPLICATION CLIENT”
host: “%”
state: present
login_user: root
login_password: “{{ mysql_root_password }}”

– name: 获取主节点二进制日志位置
shell: |
mysql -u root -p'{{ mysql_root_password }}’ -e “SHOW MASTER STATUS\G” | grep -E “File|Position” | awk ‘{print $2}’
register: master_status
changed_when: false
no_log: true

– name: 保存主节点状态信息
copy:
content: |
File: {{ master_status.stdout_lines[0] }}
Position: {{ master_status.stdout_lines[1] }}
dest: /tmp/master_status.txt
delegate_to: localhost
roles/mysql_master/templates/my.cnf.j2
:
[mysqld]
# 服务器唯一ID
server-id = {{ mysql_server_id }}

# 二进制日志配置
log_bin = {{ mysql_log_bin }}
binlog_format = {{ mysql_binlog_format }}
{% if mysql_binlog_do_db %}
binlog_do_db = {{ mysql_binlog_do_db }}
{% endif %}

# 性能优化
innodb_flush_log_at_trx_commit = 1
sync_binlog = 1

# 网络和端口
port = {{ mysql_port }}
socket = /var/lib/mysql/mysql.sock

# 数据目录
datadir = {{ mysql_data_dir }}
log-error = /var/log/mysqld.log
pid-file = /var/run/mysqld/mysqld.pid

# 复制相关
expire_logs_days = 7
max_binlog_size = 100M
5. 编写Slave Role(从节点任务)
roles/mysql_slave/tasks/main.yml
:
—
– name: 生成从节点my.cnf配置
template:
src: my.cnf.j2
dest: /etc/my.cnf
owner: mysql
group: mysql
mode: ‘0644’
notify: 重启MySQL

– name: 读取主节点状态信息
delegate_to: “{{ groups[‘master’][0] }}”
shell: cat /tmp/master_status.txt
register: master_status_info
changed_when: false
run_once: true

– name: 解析主节点二进制日志文件和位置
set_fact:
master_log_file: “{{ master_status_info.stdout_lines[0].split(‘: ‘)[1] }}”
master_log_pos: “{{ master_status_info.stdout_lines[1].split(‘: ‘)[1] }}”

– name: 配置从节点复制参数
mysql_replication:
mode: changemaster
master_host: “{{ hostvars[groups[‘master’][0]].ansible_host }}”
master_user: “{{ mysql_repl_user }}”
master_password: “{{ mysql_repl_password }}”
master_log_file: “{{ master_log_file }}”
master_log_pos: “{{ master_log_pos }}”
login_user: root
login_password: “{{ mysql_root_password }}”

– name: 启动从节点复制
mysql_replication:
mode: startreplica
login_user: root
login_password: “{{ mysql_root_password }}”

– name: 设置从节点为只读模式
mysql_user:
name: root
password: “{{ mysql_root_password }}”
check_implicit_admin: yes
login_user: root
login_password: “{{ mysql_root_password }}”
cmd: “SET GLOBAL read_only = ON;”

– name: 验证复制状态
mysql_replication:
mode: getreplica
login_user: root
login_password: “{{ mysql_root_password }}”
register: repl_status

– name: 显示复制状态
debug:
msg: “复制状态: Slave_IO_Running={{ repl_status.Slave_IO_Running }}, Slave_SQL_Running={{ repl_status.Slave_SQL_Running }}”
roles/mysql_slave/templates/my.cnf.j2
:
[mysqld]
# 服务器唯一ID(必须与主节点不同)
server-id = {{ mysql_server_id }}

# 从节点不需要开启二进制日志(可选)
# log_bin = {{ mysql_log_bin }}

# 中继日志配置
relay_log = {{ mysql_relay_log }}
log_slave_updates = 1

# 只读模式
read_only = {{ mysql_read_only }}

# 性能优化
innodb_flush_log_at_trx_commit = 1

# 网络和端口
port = {{ mysql_port }}
socket = /var/lib/mysql/mysql.sock

# 数据目录
datadir = {{ mysql_data_dir }}
log-error = /var/log/mysqld.log
pid-file = /var/run/mysqld/mysqld.pid

# 复制相关
skip_slave_start = 0
6. 主Playbook文件
site.yml
:
—
– name: 部署MySQL主从复制
hosts: all
become: yes
gather_facts: yes
vars_files:
– vars/main.yml

pre_tasks:
– name: 检查MySQL是否已安装
stat:
path: /usr/sbin/mysqld
register: mysql_installed

– name: 如果MySQL未安装,执行通用安装任务
include_role:
name: common
when: not mysql_installed.stat.exists

handlers:
– name: 重启MySQL
systemd:
name: mysqld
state: restarted
enabled: yes

– name: 配置MySQL主节点
hosts: master
become: yes
roles:
– mysql_master

– name: 配置MySQL从节点
hosts: slave
become: yes
roles:
– mysql_slave
7. vars/main.yml(额外变量)
—
# 防火墙配置
firewall_ports:
– 3306/tcp
– 22/tcp

# MySQL监控用户(可选)
monitor_user: “monitor”
monitor_password: “MonitorPass123!”
执行部署
1. 测试Ansible连接
# 确保可以SSH到所有节点
ansible all -i inventory -m ping

# 检查变量是否正确加载
ansible-inventory -i inventory –list
2. 执行Playbook
# 执行完整部署
ansible-playbook -i inventory site.yml

# 如果只想配置主节点
ansible-playbook -i inventory site.yml –tags “master”

# 如果只想配置从节点
ansible-playbook -i inventory site.yml –tags “slave”

# 使用并行执行加速(适用于多从节点)
ansible-playbook -i inventory site.yml –forks 10
3. 验证部署结果
# 在主节点检查二进制日志
mysql -u root -p’RootPassword123!’ -e “SHOW MASTER STATUS;”

# 在从节点检查复制状态
mysql -u root -p’RootPassword123!’ -e “SHOW SLAVE STATUS\G”

# 在Ansible控制节点运行验证命令
ansible slave -i inventory -m shell -a “mysql -u root -p’RootPassword123!’ -e ‘SHOW SLAVE STATUS\G’ | grep -E ‘Slave_IO_Running|Slave_SQL_Running|Last_Error’”
常见问题排查指南
问题1:从节点无法连接到主节点
症状
:
Last_IO_Error: error connecting to master ‘repl_user@192.168.1.100:3306′ – retry-time: 60 retries: 86400
排查步骤
:
检查网络连通性
:
# 在从节点执行
telnet 192.168.1.100 3306
# 或者
nc -zv 192.168.1.100 3306
检查防火墙
:
# 在主节点检查
firewall-cmd –list-ports
firewall-cmd –add-port=3306/tcp –permanent
firewall-cmd –reload
检查MySQL用户权限
:
# 在主节点执行
mysql -u root -p’RootPassword123!’ -e “SELECT user,host FROM mysql.user WHERE user=’repl_user’;”
mysql -u root -p’RootPassword123!’ -e “SHOW GRANTS FOR ‘repl_user’@’%’;”
检查主节点bind_address
:
# 在主节点检查my.cnf
grep bind_address /etc/my.cnf
# 如果存在且为127.0.0.1,修改为0.0.0.0或注释掉
问题2:复制延迟过高
症状
:
Seconds_Behind_Master: 3600
排查与优化
:
检查主从节点负载
:
# 在主从节点分别执行
top
iostat -x 1
检查网络带宽
:
# 在从节点执行
iperf3 -c 192.168.1.100
优化MySQL配置
:
# 在主节点my.cnf添加
innodb_flush_log_at_trx_commit = 2 # 牺牲一点持久性换取性能
sync_binlog = 0 # 不强制每次提交都同步磁盘
检查大事务
:
# 在主节点检查
mysql -u root -p’RootPassword123!’ -e “SHOW PROCESSLIST;” | grep -i “Waiting”
问题3:主从数据不一致
症状
:
Last_SQL_Error: Error ‘Table ‘test.users’ doesn’t exist’ on query
修复步骤
:
停止从节点复制
:
# 在从节点执行
mysql -u root -p’RootPassword123!’ -e “STOP SLAVE;”
跳过错误事务
(谨慎使用):
# 跳过1个事务
mysql -u root -p’RootPassword123!’ -e “SET GLOBAL sql_slave_skip_counter=1; START SLAVE;”
使用pt-table-checksum检查数据一致性
:
“`bash
安装Percona Toolkit
yum install percona-toolkit
# 在主节点执行检查
pt-table-checksum h=192.168.1.100,u=root,p=RootPassword123! –databases=test
4. **重新构建从节点**(终极方案):
“`bash
# 在主节点
mysqldump -u root -p’RootPassword123!’ –all-databases –master-data=2 –single-transaction > /tmp/full_backup.sql

# 传输到从节点
scp /tmp/full_backup.sql root@192.168.1.101:/tmp/

# 在从节点导入
mysql -u root -p’RootPassword123!’ < /tmp/full_backup.sql 问题4:Ansible执行失败 症状 : fatal: [master-node]: FAILED! => {“changed”: false, “msg”: “No module named MySQLdb”}
解决方案
:
安装Python MySQL模块
:
# 在所有节点执行
yum install python3-PyMySQL
# 或者
pip3 install pymysql
在Ansible中指定Python解释器
:
# 在inventory或group_vars中添加
ansible_python_interpreter: /usr/bin/python3
检查Ansible版本兼容性
:
ansible –version
# 确保版本 >= 2.9
问题5:复制用户密码错误
症状
:
Last_IO_Error: Access denied for user ‘repl_user’@’192.168.1.101′ (using password: YES)
排查
:
检查密码是否包含特殊字符
:
# 在group_vars/all.yml中,确保密码用引号包裹
mysql_repl_password: “ReplPassword123!” # 正确
mysql_repl_password: ReplPassword123! # 错误,可能解析异常
重置复制用户密码
:
# 在主节点执行
mysql -u root -p’RootPassword123!’ -e “ALTER USER ‘repl_user’@’%’ IDENTIFIED BY ‘NewPassword123!’;”
更新Ansible变量并重新执行
:
# 更新group_vars/all.yml中的密码
# 然后重新执行
ansible-playbook -i inventory site.yml –tags “master”
高级配置与最佳实践
1. 使用GTID复制(推荐)
GTID(Global Transaction ID)简化了复制管理,避免了传统基于文件名的复制问题。
修改主节点配置
:
# 在my.cnf.j2中添加
gtid_mode = ON
enforce_gtid_consistency = ON
修改从节点配置
:
# 在my.cnf.j2中添加
gtid_mode = ON
enforce_gtid_consistency = ON
log_slave_updates = ON
修改从节点任务
:
– name: 配置GTID复制
mysql_replication:
mode: changemaster
master_host: “{{ hostvars[groups[‘master’][0]].ansible_host }}”
master_user: “{{ mysql_repl_user }}”
master_password: “{{ mysql_repl_password }}”
master_auto_position: 1 # 启用GTID自动定位
login_user: root
login_password: “{{ mysql_root_password }}”
2. 多从节点配置
如果需要配置多个从节点,只需在inventory中添加:
[slave]
slave-node1 ansible_host=192.168.1.101
slave-node2 ansible_host=192.168.1.102
slave-node3 ansible_host=192.168.1.103
然后在
group_vars/slave.yml
中为每个从节点设置不同的
server_id
:
# 为每个从节点单独设置变量
# 可以在inventory中使用host_vars
# host_vars/slave-node1.yml
mysql_server_id: 2

# host_vars/slave-node2.yml
mysql_server_id: 3
3. 监控与告警集成
集成Prometheus监控
# 在roles/mysql_slave/tasks/main.yml中添加
– name: 安装mysqld_exporter
get_url:
url: https://github.com/prometheus/mysqld_exporter/releases/download/v0.14.0/mysqld_exporter-0.14.0.linux-amd64.tar.gz
dest: /tmp/mysqld_exporter.tar.gz

– name: 解压并安装
unarchive:
src: /tmp/mysqld_exporter.tar.gz
dest: /usr/local/
remote_src: yes

– name: 创建监控用户
mysql_user:
name: “{{ monitor_user }}”
password: “{{ monitor_password }}”
priv: “*.*:PROCESS,REPLICATION CLIENT,SELECT”
host: localhost
state: present
login_user: root
login_password: “{{ mysql_root_password }}”

– name: 配置mysqld_exporter
copy:
content: |
[client]
user={{ monitor_user }}
password={{ monitor_password }}
dest: /etc/.my.cnf
owner: mysql
group: mysql
mode: ‘0600’

– name: 启动mysqld_exporter服务
systemd:
name: mysqld_exporter
state: started
enabled: yes
4. 自动化备份策略
# 在roles/mysql_slave/tasks/main.yml中添加
– name: 创建备份脚本
copy:
content: |
#!/bin/bash
BACKUP_DIR=”/backup/mysql”
DATE=$(date +%Y%m%d_%H%M%S)
mysqldump -u root -p'{{ mysql_root_password }}’ –all-databases –single-transaction –master-data=2 > $BACKUP_DIR/full_$DATE.sql
find $BACKUP_DIR -name “*.sql” -mtime +7 -delete
dest: /usr/local/bin/mysql_backup.sh
mode: ‘0755’

– name: 设置定时备份
cron:
name: “MySQL daily backup”
minute: “0”
hour: “2”
job: “/usr/local/bin/mysql_backup.sh >> /var/log/mysql_backup.log 2>&1”
user: root
总结
通过Ansible自动化部署MySQL主从复制,我们实现了:
一键部署
:单条命令完成所有配置
配置标准化
:所有环境使用相同配置,避免人为错误
可重复性
:随时可以重新部署或扩展从节点
易于维护
:变量分离,逻辑清晰
关键要点回顾
安全性
:始终使用强密码,限制复制用户访问IP
监控
:部署后立即设置监控,关注Seconds_Behind_Master
测试
:在生产环境前,务必在测试环境验证
备份
:定期备份主节点数据,确保可恢复性
下一步建议
集成CI/CD
:将Ansible Playbook集成到Jenkins/GitLab CI
配置管理
:使用Git管理Playbook版本
文档化
:记录所有自定义配置和修改
定期演练
:模拟故障转移,确保高可用方案有效
通过本文提供的完整代码和详细排查指南,您应该能够成功部署并维护生产级的MySQL主从复制环境。如果在实际操作中遇到问题,请参考”常见问题排查”章节,或根据错误信息具体分析。