“菲哥!新机房要搭 10 套 MySQL 主从,手动配到半夜还没弄完,刚才改 my.cnf 多输个空格,从库直接起不来了!”—— 周六晚上十点,运维小林的消息带着哭腔。我给他发了个 Ansible playbook,10 分钟后他回复:“全好了!还自动验证了状态,再也不用对着配置文件逐行检查了!”
手动运维 MySQL 就像 “用手搬砖盖房子”—— 搭 1 套主从要 2 小时,改 10 个节点的参数要 1 小时,备份忘了传异地还得补,稍微走神多输个符号就翻车。而自动化运维就是 “开挖掘机盖房子”,一键搞定部署、备份、升级,还能自动校验,省下来的时间能多喝两杯奶茶。
这篇就带你用 “Ansible+Shell” 这对组合,把 MySQL 运维的重复活全自动化。从 “一键部署主从” 到 “定时备份 + 校验”,再到 “版本升级不翻车”,每个模块都给你现成的脚本和步骤,中间穿插 “运维血泪史吐槽”,保证你看完能把手动操作砍半,从此告别加班。
一、先扎心:手动运维的 “五大酷刑”
没搞自动化前,运维做 MySQL 的日常就是 “重复 + 踩坑”,这五个坑几乎人人都中过:
搭主从配到吐:手动改 my.cnf、传文件、建复制用户,1 套 2 小时,10 套就是 1 天,还常因 “server-id 重复”“binlog 格式错” 失败;备份忘传异地:手动备份完,忘了 scp 到异地服务器,主库崩了才发现备份也没了,拍大腿骂自己;改参数改到眼瞎:10 个节点要改 innodb_buffer_pool_size,手动登录每个节点改 my.cnf,改完还得逐个重启,漏一个就出问题;升级版本怕翻车:手动升级没做回滚方案,中途报错,数据库起不来,业务停了 2 小时,被领导骂到怀疑人生;校验全靠肉眼:搭完主从,手动查 Slave_IO_Running,备份完手动 count 数据,累还容易漏看。
先上一张 “手动运维 vs 自动化运维” 对比图,感受下差距:
plaintext
【手动运维流程】
搭主从:查文档→改my.cnf(改错server-id)→传文件→建用户→启动复制→手动查状态(发现IO线程没起来)→返工(2小时)
备份:执行mysqldump→手动scp到异地(忘传了)→第二天发现备份丢了→补备份(1小时)
【自动化运维流程】
搭主从:执行ansible-playbook deploy_mysql_slave.yml→等待10分钟→自动校验状态(全绿)→搞定
备份:Shell脚本自动执行→自动传异地+校验→失败发告警→不用管
二、自动化核心工具:为什么选 “Ansible+Shell”?(不用学 Python 也能会)
很多人觉得自动化要写复杂代码,其实不用 ——Ansible 是 “不用写代码的自动化工具”,用 YAML 配置文件就能发指令;Shell 脚本是 “运维老熟人”,简单几行就能搞定备份、校验这些活。这俩组合就像 “汉堡 + 可乐”,绝配!
1. 工具选型理由(表格 + 幽默解读)
| 工具 | 作用 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| Ansible | 运维的 “遥控器”,能远程控制 N 个服务器 | 不用装客户端(被控节点只要有 SSH),用 YAML 写指令,简单易懂 | 复杂逻辑(如条件判断)不如 Python 灵活 | 批量部署(主从、监控)、批量改参数 |
| Shell | 运维的 “瑞士军刀”,处理备份、校验这些小活 | 系统自带,不用装,写几行就能跑 | 跨服务器操作不如 Ansible 方便 | 单节点自动化(本地备份、日志清理) |
选型口诀:批量操作找 Ansible(比如 10 台机器搭主从),单节点小活找 Shell(比如某台机器定时备份),两者结合,天下我有。
三、实战 1:Ansible 一键部署 MySQL 主从(10 套也只要 10 分钟)
手动搭主从要改 my.cnf、传文件、建复制用户、启动复制,步骤多还容易错。用 Ansible 写个 playbook,一键跑完所有步骤,还能自动验证状态。
1. 先搭 Ansible 环境(控制节点 + 被控节点)
(1)环境准备(架构图)
plaintext
【Ansible自动化架构】
控制节点(192.168.1.200):装Ansible,写playbook→通过SSH控制被控节点
被控节点:主库(192.168.1.100)、从库1(192.168.1.101)、从库2(192.168.1.102)
(2)控制节点安装 Ansible(CentOS 7)
bash
# 装EPEL源(Ansible在EPEL里)
yum install -y epel-release
# 装Ansible
yum install -y ansible
# 验证:出现版本号就对了
ansible --version
(3)配置 Ansible 免密登录(被控节点不用输密码)
运维手动输密码登录 10 个节点能疯,免密登录是自动化的前提:
bash
# 控制节点生成SSH密钥(一路回车,不用设密码)
ssh-keygen -t rsa
# 把公钥传到所有被控节点(主库、从库1、从库2)
ssh-copy-id root@192.168.1.100
ssh-copy-id root@192.168.1.101
ssh-copy-id root@192.168.1.102
# 测试免密登录:能进去就成
ssh root@192.168.1.100 exit
(4)配置 Ansible 主机清单(告诉 Ansible 要管哪些节点)
bash
# 编辑主机清单文件
vi /etc/ansible/hosts
# 加入以下内容(分组:mysql_master是主库,mysql_slaves是从库)
[mysql_master]
192.168.1.100
[mysql_slaves]
192.168.1.101
192.168.1.102
# 测试: ping所有被控节点,都通就对了
ansible all -m ping
2. 写 Ansible Playbook(一键部署主从的 “指令清单”)
Playbook 是 Ansible 的核心,就像 “给运维小助手的操作手册”,写清楚 “要做什么、按什么顺序做”。我们分 “主库配置” 和 “从库配置” 两部分。
(1)主库配置 Playbook(mysql_master.yml)
yaml
- name: 一键配置MySQL主库
hosts: mysql_master # 作用于主库节点
remote_user: root # 用root用户执行
tasks:
# 1. 安装MySQL(这里假设已装,若未装可加安装步骤)
- name: 1. 复制主库my.cnf配置文件到被控节点
copy:
src: /etc/ansible/mysql_conf/master/my.cnf # 控制节点上的主库配置文件
ql_conf/master/my.cnf # 控制节点上的主库配置文件
dest: /etc/my.cnf # 被控节点的my.cnf路径
mode: 0644 # 文件权限
# 2. 重启MySQL,让配置生效
- name: 2. 重启MySQL服务
service:
name: mysqld
state: restarted
enabled: yes # 开机自启
# 3. 主库创建复制用户(给从库用)
- name: 3. 主库创建复制用户repl
mysql_user:
login_user: root
login_password: 123456 # 主库root密码
name: repl # 复制用户名
password: Repl@123 # 复制用户密码
host: "192.168.1.%" # 允许从库网段连接
priv: "*.*:REPLICATION SLAVE" # 复制权限
state: present
# 4. 主库锁表,获取binlog信息(给从库用)
- name: 4. 获取主库binlog文件和位置
mysql_query:
login_user: root
login_password: 123456
query: "SHOW MASTER STATUS;"
register: master_status # 把结果存到master_status变量
# 5. 把主库binlog信息写到本地文件(给从库Playbook用)
- name: 5. 保存主库binlog信息到控制节点
local_action:
module: copy
content: "binlog_file={{ master_status.query_result[0].File }}
binlog_pos={{ master_status.query_result[0].Position }}"
dest: /etc/ansible/mysql_conf/master_binlog.info # 保存路径
(2)从库配置 Playbook(mysql_slave.yml)
yaml
- name: 一键配置MySQL从库
hosts: mysql_slaves # 作用于所有从库节点
remote_user: root
vars:
# 从控制节点读取主库binlog信息(主库Playbook生成的)
master_info: "{{ lookup('file', '/etc/ansible/mysql_conf/master_binlog.info').split('
') }}"
master_binlog_file: "{{ master_info[0].split('=')[1] }}"
master_binlog_pos: "{{ master_info[1].split('=')[1] }}"
tasks:
# 1. 复制从库my.cnf到被控节点
- name: 1. 复制从库my.cnf
copy:
src: /etc/ansible/mysql_conf/slave/my.cnf
dest: /etc/my.cnf
mode: 0644
# 2. 重启MySQL
- name: 2. 重启MySQL服务
service:
name: mysqld
state: restarted
enabled: yes
# 3. 从库停止现有复制(防止之前有配置)
- name: 3. 停止从库复制
mysql_query:
login_user: root
login_password: 123456
query: "STOP SLAVE;"
# 4. 从库配置主库信息
- name: 4. 配置从库连接主库
mysql_query:
login_user: root
login_password: 123456
query: |
CHANGE MASTER TO
MASTER_HOST='192.168.1.100',
MASTER_USER='repl',
MASTER_PASSWORD='Repl@123',
MASTER_LOG_FILE='{{ master_binlog_file }}',
MASTER_LOG_POS={{ master_binlog_pos }};
# 5. 启动从库复制
- name: 5. 启动从库复制
mysql_query:
login_user: root
login_password: 123456
query: "START SLAVE;"
# 6. 验证从库状态(IO和SQL线程是否Running)
- name: 6. 验证从库复制状态
mysql_query:
login_user: root
login_password: 123456
query: "SHOW SLAVE STATUS;"
register: slave_status
# 7. 输出验证结果(失败就报错)
- name: 7. 检查IO和SQL线程状态
fail:
msg: "从库{{ inventory_hostname }}复制失败!IO线程: {{ slave_status.query_result[0].Slave_IO_Running }}, SQL线程: {{ slave_status.query_result[0].Slave_SQL_Running }}"
when: slave_status.query_result[0].Slave_IO_Running != 'Yes' or slave_status.query_result[0].Slave_SQL_Running != 'Yes'
- name: 8. 复制成功提示
debug:
msg: "从库{{ inventory_hostname }}复制配置成功!延迟: {{ slave_status.query_result[0].Seconds_Behind_Master }}秒"
3. 准备配置文件(主从 my.cnf)
在控制节点上创建配置文件目录,放主从的 my.cnf:
bash
# 创建目录
mkdir -p /etc/ansible/mysql_conf/master /etc/ansible/mysql_conf/slave
# 主库my.cnf(/etc/ansible/mysql_conf/master/my.cnf)
vi /etc/ansible/mysql_conf/master/my.cnf
主库 my.cnf 内容(关键配置,其他默认):
ini
[mysqld]
server-id=100
log_bin=/var/lib/mysql/mysql-bin
binlog_format=ROW
gtid_mode=ON
enforce_gtid_consistency=ON
从库 my.cnf(/etc/ansible/mysql_conf/slave/my.cnf):
ini
[mysqld]
server-id=101 # 从库2设102,Ansible会自动处理(可在Playbook里用变量动态设)
log_bin=/var/lib/mysql/mysql-bin # 若要级联复制就开,否则可关
gtid_mode=ON
enforce_gtid_consistency=ON
read_only=ON # 从库只读
4. 执行 Playbook,一键部署
bash
# 1. 先执行主库配置Playbook
ansible-playbook /etc/ansible/mysql_master.yml
# 2. 再执行从库配置Playbook(主库Playbook生成binlog信息后再跑)
ansible-playbook /etc/ansible/mysql_slave.yml
执行结果:
控制节点会输出每个步骤的状态,比如 “复制 my.cnf 成功”“重启 MySQL 成功”;最后会提示 “从库 192.168.1.101 复制配置成功!延迟: 0 秒”;不用手动登录每个节点查状态,Ansible 自动校验,失败会报错。
四、实战 2:Shell+Crond 自动化备份(不用再记着点备份)
手动备份容易忘,还容易漏传异地,用 Shell 写个备份脚本,加 Crond 定时任务,自动备份、传异地、校验,失败还发告警。
1. 自动化备份 Shell 脚本(mysql_backup.sh)
bash
#!/bin/bash
# 功能:MySQL全量备份+binlog备份+传异地+校验
# 作者:运维摸鱼组
# 日期:2025-11-15
# 1. 配置参数(根据自己环境改)
BACKUP_DIR="/data/mysql_backup/$(date +%Y%m%d)" # 本地备份目录
MYSQL_USER="root"
MYSQL_PASS="123456"
MYSQL_HOST="localhost"
MYSQL_PORT="3306"
REMOTE_HOST="192.168.1.200" # 异地服务器IP
REMOTE_DIR="/data/backup/mysql/" # 异地备份目录
ALERT_EMAIL="admin@xxx.com" # 告警邮箱
LOG_FILE="/var/log/mysql_backup.log" # 日志文件
# 2. 函数:写日志
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" >> $LOG_FILE
}
# 3. 检查备份目录,不存在就创建
if [ ! -d $BACKUP_DIR ]; then
mkdir -p $BACKUP_DIR
log "创建本地备份目录成功:$BACKUP_DIR"
else
log "本地备份目录已存在:$BACKUP_DIR"
fi
# 4. 全量备份(用xtrabackup,比mysqldump快)
log "开始全量备份..."
xtrabackup --user=$MYSQL_USER --password=$MYSQL_PASS --host=$MYSQL_HOST --port=$MYSQL_PORT --backup --target-dir=$BACKUP_DIR/full > /dev/null 2>&1
# 5. 检查全量备份是否成功
if [ $? -eq 0 ]; then
log "全量备份成功:$BACKUP_DIR/full"
else
log "全量备份失败!!!"
echo "MySQL全量备份失败,查看日志:$LOG_FILE" | mail -s "MySQL备份告警" $ALERT_EMAIL
exit 1
fi
# 6. 备份binlog(只备份当天的)
log "开始备份binlog..."
mysqlbinlog --user=$MYSQL_USER --password=$MYSQL_PASS --host=$MYSQL_HOST --port=$MYSQL_PORT --read-from-remote-server --raw --stop-datetime="$(date +'%Y-%m-%d 23:59:59')" mysql-bin.$(date +%Y%m%d)* > /dev/null 2>&1
mv mysql-bin.$(date +%Y%m%d)* $BACKUP_DIR/binlog/
if [ $? -eq 0 ]; then
log "binlog备份成功:$BACKUP_DIR/binlog"
else
log "binlog备份失败!!!"
echo "MySQL binlog备份失败,查看日志:$LOG_FILE" | mail -s "MySQL备份告警" $ALERT_EMAIL
exit 1
fi
# 7. 传备份到异地服务器
log "开始传备份到异地服务器..."
scp -r $BACKUP_DIR $REMOTE_HOST:$REMOTE_DIR > /dev/null 2>&1
if [ $? -eq 0 ]; then
log "异地传输成功:$REMOTE_HOST:$REMOTE_DIR/$(date +%Y%m%d)"
else
log "异地传输失败!!!"
echo "MySQL备份异地传输失败,查看日志:$LOG_FILE" | mail -s "MySQL备份告警" $ALERT_EMAIL
exit 1
fi
# 8. 校验异地备份(查文件大小是否一致)
log "开始校验异地备份..."
LOCAL_SIZE=$(du -s $BACKUP_DIR | awk '{print $1}')
REMOTE_SIZE=$(ssh $REMOTE_HOST "du -s $REMOTE_DIR/$(date +%Y%m%d)" | awk '{print $1}')
if [ $LOCAL_SIZE -eq $REMOTE_SIZE ]; then
log "异地备份校验成功,大小一致:$LOCAL_SIZE KB"
else
log "异地备份校验失败!本地大小:$LOCAL_SIZE KB,异地大小:$REMOTE_SIZE KB"
echo "MySQL备份校验失败,查看日志:$LOG_FILE" | mail -s "MySQL备份告警" $ALERT_EMAIL
exit 1
fi
# 9. 清理7天前的本地备份(避免占满磁盘)
log "开始清理7天前的本地备份..."
find /data/mysql_backup -mtime +7 -type d -exec rm -rf {} ;
log "备份任务全部完成!"
exit 0
2. 配置定时任务(每天凌晨 2 点执行)
bash
# 1. 给脚本加执行权限
chmod +x /usr/local/bin/mysql_backup.sh
# 2. 编辑crontab定时任务
crontab -e
# 3. 加入以下内容(每天凌晨2点执行,输出日志)
0 2 * * * /usr/local/bin/mysql_backup.sh >> /var/log/mysql_backup_cron.log 2>&1
验证:
bash
# 手动执行一次,看是否成功
/usr/local/bin/mysql_backup.sh
# 查看日志
cat /var/log/mysql_backup.log
日志里会显示 “全量备份成功”“异地传输成功”,失败会发邮件告警。
五、实战 3:自动化版本升级(不怕翻车,有回滚)
手动升级 MySQL 容易忘做回滚,中途报错就傻眼。自动化升级要加 “前置检查 + 备份 + 回滚”,确保万无一失。
1. 升级脚本(mysql_upgrade.sh)
bash
#!/bin/bash
# 功能:MySQL 8.0.32→8.0.36 自动化升级+回滚
# 风险提示:升级前务必在测试环境验证!
# 1. 配置参数
OLD_VERSION="8.0.32"
NEW_VERSION="8.0.36"
MYSQL_HOME="/usr/local/mysql"
BACKUP_DIR="/data/mysql_upgrade_backup/$(date +%Y%m%d)"
LOG_FILE="/var/log/mysql_upgrade.log"
ALERT_EMAIL="admin@xxx.com"
# 2. 前置检查(关键!避免升级条件不满足)
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" >> $LOG_FILE
}
log "开始前置检查..."
# 检查MySQL是否运行
if ! systemctl is-active --quiet mysqld; then
log "MySQL未运行,无法升级!"
echo "MySQL未运行,升级终止" | mail -s "MySQL升级告警" $ALERT_EMAIL
exit 1
fi
# 检查当前版本是否是目标旧版本
CURRENT_VERSION=$(mysql -V | awk '{print $3}' | cut -d'.' -f1-3)
if [ "$CURRENT_VERSION" != "$OLD_VERSION" ]; then
log "当前版本$CURRENT_VERSION不是目标旧版本$OLD_VERSION,终止升级!"
echo "版本不匹配,升级终止" | mail -s "MySQL升级告警" $ALERT_EMAIL
exit 1
fi
# 检查备份目录
mkdir -p $BACKUP_DIR
log "前置检查通过,开始备份..."
# 3. 升级前全量备份(回滚用)
xtrabackup --user=root --password=123456 --backup --target-dir=$BACKUP_DIR/full > /dev/null 2>&1
if [ $? -ne 0 ]; then
log "升级前备份失败,终止升级!"
echo "升级前备份失败,升级终止" | mail -s "MySQL升级告警" $ALERT_EMAIL
exit 1
fi
log "升级前备份成功:$BACKUP_DIR/full"
# 4. 停止MySQL
log "停止MySQL服务..."
systemctl stop mysqld
if [ $? -ne 0 ]; then
log "停止MySQL失败,终止升级!"
echo "停止MySQL失败,升级终止" | mail -s "MySQL升级告警" $ALERT_EMAIL
exit 1
fi
# 5. 备份旧版本目录
log "备份旧版本MySQL目录..."
mv $MYSQL_HOME $MYSQL_HOME-$OLD_VERSION
if [ $? -ne 0 ]; then
log "备份旧版本目录失败,开始回滚(启动旧版本)..."
systemctl start mysqld
echo "备份旧目录失败,已回滚启动旧版本" | mail -s "MySQL升级告警" $ALERT_EMAIL
exit 1
fi
# 6. 安装新版本(假设已下载rpm包到/root目录)
log "安装新版本$NEW_VERSION..."
rpm -ivh /root/mysql-community-server-$NEW_VERSION-1.el7.x86_64.rpm --force --nodeps > /dev/null 2>&1
if [ $? -ne 0 ]; then
log "安装新版本失败,开始回滚..."
mv $MYSQL_HOME-$OLD_VERSION $MYSQL_HOME
systemctl start mysqld
echo "安装新版本失败,已回滚到$OLD_VERSION" | mail -s "MySQL升级告警" $ALERT_EMAIL
exit 1
fi
# 7. 执行升级语句(更新系统表)
log "执行mysql_upgrade升级系统表..."
mysql_upgrade -u root -p123456 > /dev/null 2>&1
if [ $? -ne 0 ]; then
log "升级系统表失败,开始回滚..."
systemctl stop mysqld
mv $MYSQL_HOME-$OLD_VERSION $MYSQL_HOME
systemctl start mysqld
echo "升级系统表失败,已回滚到$OLD_VERSION" | mail -s "MySQL升级告警" $ALERT_EMAIL
exit 1
fi
# 8. 启动新版本,验证
log "启动新版本MySQL..."
systemctl start mysqld
if ! systemctl is-active --quiet mysqld; then
log "启动新版本失败,开始回滚..."
mv $MYSQL_HOME-$OLD_VERSION $MYSQL_HOME
systemctl start mysqld
echo "启动新版本失败,已回滚到$OLD_VERSION" | mail -s "MySQL升级告警" $ALERT_EMAIL
exit 1
fi
# 9. 验证版本
NEW_CURRENT_VERSION=$(mysql -V | awk '{print $3}' | cut -d'.' -f1-3)
if [ "$NEW_CURRENT_VERSION" != "$NEW_VERSION" ]; then
log "版本验证失败,开始回滚..."
systemctl stop mysqld
mv $MYSQL_HOME-$OLD_VERSION $MYSQL_HOME
systemctl start mysqld
echo "版本验证失败,已回滚到$OLD_VERSION" | mail -s "MySQL升级告警" $ALERT_EMAIL
exit 1
fi
log "MySQL升级成功!从$OLD_VERSION→$NEW_VERSION"
echo "MySQL升级成功!版本:$NEW_VERSION" | mail -s "MySQL升级成功" $ALERT_EMAIL
exit 0
六、运维自动化 指南
Ansible 免密登录失败原因:被控节点的 sshd_config 禁用了密码登录,或公钥没传对。解决:编辑被控节点,确保
/etc/ssh/sshd_config,重新传公钥:
PasswordAuthentication yes。
ssh-copy-id -i ~/.ssh/id_rsa.pub root@被控节点IP
备份脚本权限不够原因:脚本用普通用户执行,没 MySQL 权限或目录写权限。解决:用 root 用户执行脚本,或给脚本加权限:
sudo。
chmod u+s /usr/local/bin/mysql_backup.sh
升级脚本没回滚机制原因:只写了升级步骤,没考虑失败情况。解决:每步失败都要触发回滚(如恢复旧版本目录、启动旧服务),就像上面的升级脚本,任何一步错了都能回滚到旧版本。
定时任务没日志原因:crontab 没重定向日志,失败了不知道。解决:定时任务后加,把 stdout 和 stderr 都写日志。
>> /var/log/xxx.log 2>&1
七、运维自动化的 “核心不是偷懒,是少犯错”
很多人觉得自动化是 “偷懒”,其实不是 —— 手动运维 10 次可能对 9 次,但 1 次错就可能导致业务停摆;自动化能把 “重复操作” 的错误率降到 0,还能解放时间去做更重要的事(比如优化架构、学习新技术)。
记住:
自动化不是 “一蹴而就”,先从简单的备份、部署开始,再逐步覆盖升级、故障恢复;任何自动化脚本都要先在测试环境验证,尤其是升级、删数据这类高风险操作;一定要加 “日志 + 告警 + 回滚”,出问题能快速定位和恢复。