MGR集群auto认证脚本
#!/bin/bash
# *************************************
# * 功能: Shell脚本模板
# * 作者: 刘丹玉
# * 联系: v649352141@163.com
# * 版本: 2025-05-18
# *************************************
# 错误处理:如果命令执行失败,脚本将终止
set -e
# 调试处理
# set -x
MYSQL_PWD="Mysql.123456"
MYSQL_VERION="8.0.41"
MYSQL_PORT="3306"
PORT="33061"
OS_NAME="Rocky"
MY_UUID=$(uuidgen)
# 预定义主机列表
#HOSTS=("10.0.0.12" "10.0.0.15" "10.0.0.18")
HOSTS=()
PRIMARY_HOST="10.0.0.12"
# 集群总节点数n
ONLINE_COUNT="${#HOSTS[@]}"
# 颜色脚本,通用
color () {
RES_COL=80
MOVE_TO_COL="echo -en \\033[${RES_COL}G"
SETCOLOR_SUCCESS="echo -en \\033[1;32m"
SETCOLOR_FAILURE="echo -en \\033[1;31m"
SETCOLOR_WARNING="echo -en \\033[1;33m"
SETCOLOR_NORMAL="echo -en \E[0m"
echo -n "$1" && $MOVE_TO_COL
echo -n "["
if [[ $2 = "success" || $2 = "0" ]] ;then
${SETCOLOR_SUCCESS}
echo -n $" OK "
elif [[ $2 = "failure" || $2 = "1" ]] ;then
${SETCOLOR_FAILURE}
echo -n $"FAILED"
else
${SETCOLOR_WARNING}
echo -n $"WARNING"
fi
${SETCOLOR_NORMAL}
echo -n "]"
echo
}
# 查看属于Rocky、Ubuntu、openEuler系列
os_type () {
$OS_NAME=$(awk -F'[ "]' '/^NAME/{print $2}' /etc/os-release)
}
# 免密认证
pwd_auth_across_host(){
color "==========免密认证 start============" 0
# 设置本机ip和密码
#read -s -p "请输入本机的密码:" secret
read -p "请输入本机的密码:" secret
echo # 换行,让后续输出从新行开始
# 检查密码是否为空
if [ -z "$secret" ]; then
echo "错误:密码不能为空!" >&2
exit 1
fi
# 可选:使用密码进行后续操作
echo "密码已设置,进行后续操作..."
echo "清空 ip_up.txt 文件"
> ip_up.txt
# 生成 SSH 密钥对,不输出信息到屏幕
echo "生成 SSH 密钥对"
ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa > /dev/null 2>&1
# 循环读取用户输入
while true;do
read -p "请输入主机IP和密码(格式为 ip:password,输入空行结束): " str
# 检查是否为空行(用户想结束输入)
if [ -z "$str" ]; then
break
fi
# 将输入追加到ip_up.txt
echo "$str" >> ip.txt
echo "已添加: $str"
done
# 检查是否添加了任何内容
if [ -s "ip.txt" ]; then
echo "已成功将内容保存到 ip.txt"
else
exit 1
fi
echo "判断是否安装expect"
! rpm -q expect &>/dev/null && { echo "未安装 expect,正在尝试安装..."; yum -y install expect &>/dev/null || { echo "错误:expect 安装失败,请检查网络连接或权限" >&2; exit 1; }; }
echo "将ssh密钥对复制到本地主机"
# 使用 expect 实现 ssh-copy-id 到本地主机
/usr/bin/expect <<-END &>/dev/null
spawn ssh-copy-id 127.1
expect {
"yes/no" { send "yes\r"; exp_continue }
"password:" { send "$secret\r" }
timeout {
puts "连接 127.0.0.1 超时,推送失败。"
exit 1
}
eof {
if { [exp_status] != 0 } {
puts "向 127.0.0.1 推送公钥失败。"
exit 1
}
}
}
expect eof
END
# 循环判断主机是否 ping 通,如果 ping 通则推送秘钥对
while IFS=: read -r ip pass; do
# 检查读取的行是否符合格式
if [ -z "$ip" ] || [ -z "$pass" ]; then
echo "ip.txt 文件中存在格式错误的行:$ip:$pass"
continue
fi
ping -c1 $ip &>/dev/null
if [ $? -eq 0 ];then
echo $ip $pass
echo $ip $pass >> ip_up.txt
/usr/bin/expect <<-END &>/dev/null
spawn rsync -avz /root/.ssh/ $ip:/root/.ssh/
expect {
"yes/no" { send "yes\r";exp_continue }
"password:" { send "$pass\r" }
timeout {
puts "连接 $ip 超时,推送失败。"
exit 1
}
eof {
if { [regexp -nocase "Permission denied" \$expect_out(buffer)] } {
puts "认证失败: $ip"
exit 1
} else {
puts "密钥推送成功: $ip"
exit 0
}
}
}
expect eof
END
fi
done < ip.txt
wait
echo "秘钥已经推送完毕,正在测试..."
echo "测试所有可达主机的公钥推送是否成功"
all_success=true
while read -r line; do
remote_ip=$(echo "$line" | cut -d' ' -f1)
ssh root@"$remote_ip" hostname &>/dev/null
if [ $? -ne 0 ]; then
echo "向 $remote_ip 推送公钥失败。"
all_success=false
fi
done < ip_up.txt
# 或从文件读取(确保文件每行一个主机名)
HOSTS=()
while IFS= read -r line; do
# 过滤掉空行
if [[ -n "$line" ]]; then
# 提取每行的第一个字段(以空格分隔)
host=$(echo "$line" | cut -d' ' -f1)
# 正确添加到数组
HOSTS+=("$host")
fi
done < ip_up.txt
echo ${HOSTS[@]}
PRIMARY_HOST="${HOSTS[0]}"
if $all_success; then
echo "秘钥成功推送完毕"
else
echo "部分主机公钥推送失败,请检查。"
fi
color "==========免密认证 end============" 0
}
# 基础环境
base_environment(){
color "=============== MySQL 基础环境 start ===============" 0
for host in "${HOSTS[@]}"; do
# 获取ip最后一位
last_octet=${host##*.}
# 获取数组索引(正确方式)
for i in "${!HOSTS[@]}"; do
if [[ "${HOSTS[$i]}" == "$host" ]]; then
index=$i
break
fi
done
name="rocky9-$last_octet-mgr0$((index + 1))"
# 修改主机名
ssh root@$host "hostnamectl set-hostname $name"
# 配置主机名称解析
echo $host $name >> /etc/hosts
sleep 1
# 临时关闭SELINUX
ssh root@10.0.0.12 '
if grep -q "SELINUX=enforcing" /etc/selinux/config; then
setenforce 0
else
echo "SELinux 已禁用,无需操作" 0
fi
'
# 永久关闭SELINUX
ssh root@$host "sed -i.bak 's/^SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config"
sleep 1
# 临时关闭防火墙
ssh root@$host "systemctl disable --now firewalld && nft flush ruleset"
done
for host in "${HOSTS[@]}"; do
scp /etc/hosts root@$host:/etc/hosts
done
color "=============== MySQL 基础环境 end ===============" 0
}
# 安装并启动 MySQL 服务
install_mysql_soft() {
color "=============== 正在安装 MySQL 软件 start ===============" 0
for host in "${HOSTS[@]}"; do
# 检查是否已安装
if ! ssh root@"$host" "rpm -q mysql-server";then
color "节点 $host 未安装 MySQL,开始安装..." 2
if ssh root@"$host" "yum -y install mysql-server >/dev/null 2>&1";then
color "节点 $host MySQL 安装成功!" 0
else
color "错误:节点 $host MySQL 安装失败!" 2
fi
else
color "节点 $host 已安装 MySQL" 0
fi
sleep 1
done
color "=============== 正在安装 MySQL 软件 end ===============" 3
}
# 检查Mysql是否启动
check_mysql_is_active(){
color "=============== 正在测试 MySQL 是否存活 ===============" 0
for host in "${HOSTS[@]}"; do
# 检查服务状态
if ! ssh root@"$host" "systemctl is-active mysqld"; then
# 尝试重启
if ssh root@"$host" "systemctl restart mysqld && systemctl enable mysqld" >/dev/null 2>&1; then
color "节点 $host MySQL 服务已启动并设置为开机自启!" 0
else
color "错误:节点 $host MySQL 服务启动失败!" 2
fi
fi
sleep 1
done
sleep 2
color "=============== MySQL 状态检查完成 ===============" 0
}
# 正在测试 MySQL 是否存活
check_mysql_status(){
color "=============== 正在测试 MySQL 是否存活 start ============" 0
for host in "${HOSTS[@]}"; do
# 使用 mysqladmin ping 检查连接状态
if ssh root@"$host" "mysqladmin ping -h localhost -uroot -p'$MYSQL_PWD' --silent" >/dev/null 2&>1; then
color "节点 $host MySQL 服务已运行且可连接" 0
else
# 检查Mysql是否启动
check_mysql_is_active
color "节点 $host MySQL 无响应,尝试重启..." 1
fi
done
color "=============== 正在测试 MySQL 是否存活 end ============" 0
}
# 修改mysql密码
alter_mysql_pwd() {
color "========正在修改mysql密码 start ======" 0
for host in "${HOSTS[@]}"; do
color "正在修改主机 $host 的 MySQL 密码..." 0
# 使用当前密码执行 SQL 命令
ssh root@"$host" "mysql -e \"ALTER USER root@'localhost' IDENTIFIED BY '$MYSQL_PWD';\""
color "节点 $host MySQL 服务密码已修改" 0
sleep 1
done
sleep 2
color "=========正在修改mysql密码 end ========" 0
# 检查mysql连接状态
check_mysql_status
}
get_mysql_version() {
color "========正在获取mysql版本信息 start =======" 0
# 定义获取版本的 SQL 命令
local SQL_COMMAND="SELECT VERSION();"
# 遍历所有主机获取 MySQL 版本
for host in "${HOSTS[@]}"; do
color "正在获取主机 $host 的 MySQL 版本..." 0
# 通过 SSH 执行 MySQL 命令并获取版本
local version=$(ssh root@"$host" "mysql -u root -p'$MYSQL_PWD' --batch --skip-column-names <<EOF
$SQL_COMMAND
EOF")
# 检查命令是否成功执行
if [[ $? -ne 0 || -z "$version" ]]; then
color "错误:无法获取主机 $host 的 MySQL 版本!" 2
continue
fi
# 显示版本信息
color "主机 $host 的 MySQL 版本为: $version" 0
# 短暂延迟,避免并发查询过多
sleep 2
done
sleep 2
color "=======正在获取mysql版本信息 end ========" 0
}
# 配置文件
mysql_config(){
color "=======正在集群配置文件 start =======" 0
# 格式化并拼接
hosts_with_port=$(printf "%s:$PORT,\n" "${HOSTS[@]}" | tr -d '\n' | sed 's/,$//')
for host in "${HOSTS[@]}";do
color "$host 主机正在写入配置文件"
last_octet=$(echo $host | awk -F'.' '{print $4}')
ssh root@$host <<- EOF
cat > /etc/my.cnf.d/mysql-server.cnf <<- 'eof'
[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
log-error=/var/log/mysql/mysqld.log
pid-file=/run/mysqld/mysqld.pid
# 为了版本兼容性,更改默认的用户认证插件
default_authentication_plugin=mysql_native_password
# 设置MySQL插件目录:MGR基于插件,必须设置插件路径
plugin_dir=/usr/lib64/mysql/plugin
# 复制框架
server_id=$last_octet
# 开启binlog的GTID模式(MGR强制要求)
gtid_mode=ON
# 开启后MySQL只允许能够保障事务安全,并且能够被日志记录的SQL语句被执行
enforce_gtid_consistency=ON
# 关闭binlog校验(MGR强制要求)
binlog_checksum=NONE
log_bin=binlog
log_slave_updates=ON
binlog_format=ROW
master_info_repository=TABLE
relay_log_info_repository=TABLE
# 组复制设置
# server必须为每个事务收集写集合,并使用XXHASH64哈希算法将其编码为散列
transaction_write_set_extraction=XXHASH64
# 启用组复制模块
plugin_load_add='group_replication.so'
# 告知插件加入或创建组命名,UUID
group_replication_group_name="$MY_UUID"
# server启动时不自启组复制,为了避免每次启动自动引导具有相同名称的第二个组,所以设置为OFF。
group_replication_start_on_boot=off
# 告诉插件使用IP地址,端口33061用于接收组中其他成员转入连接
# 注意:此处可以使用ip地址,也可以使用主机名方式
group_replication_local_address="$host:$PORT"
# 启动组server,种子server,加入组应该连接这些的ip和端口;其他server要加入组得由组成员同意
# 注意:此处可以使用ip地址,也可以使用主机名方式
group_replication_group_seeds="$hosts_with_port"
# 配置此服务器为引导组,这个选项必须仅在一台服务器上设置,
# 并且仅当第一次启动组或者重新启动整个组时。成功引导组启动后,将此选项设置为关闭。
group_replication_bootstrap_group=off
# 指定当前节点向集群其他成员报告的自身的主机名或 IP 地址,用于成员间通信和连接
# 这是保证集群成员间正确通信的基础配置。
report_host=$host
report_port=$MYSQL_PORT
eof
EOF
done
# 重启MySQL服务
for host in "${HOSTS[@]}"; do
ssh root@$host systemctl restart mysqld
sleep 1
done
sleep 2
color "=======正在集群配置文件 end ========" 0
}
# 集群认证环境
cluster_auth_init_sql(){
color "=======正在集群认证环境 start ========" 0
# SQL 脚本内容
SQL_SCRIPT=$(cat <<-'EOF'
# 如下操作不记录二进制日志
SET SQL_LOG_BIN=0;
# 创建rpl_user账户,此账户用于实现主从数据同步
CREATE USER rpl_user@'%' IDENTIFIED BY '123456';
GRANT REPLICATION SLAVE ON *.* TO rpl_user@'%';
GRANT CONNECTION_ADMIN ON *.* TO rpl_user@'%';
GRANT BACKUP_ADMIN ON *.* TO rpl_user@'%';
GRANT GROUP_REPLICATION_STREAM ON *.* TO rpl_user@'%';
# 创建一个远程连接用户,便于图形化管理工具使用
create user 'remote'@'%' identified with mysql_native_password by '123456';
grant all privileges on *.* to remote@'%';
FLUSH PRIVILEGES;
# 恢复二进制日志功能并且重置二进制日志
SET SQL_LOG_BIN=1;
RESET MASTER;
EOF
)
# 循环执行
for host in "${HOSTS[@]}"; do
color "$host 主机正在集群认证" 0
# 通过 SSH 执行 MySQL 命令
ssh root@$host "mysql -u root -p'$MYSQL_PWD' <<EOF
$SQL_SCRIPT
EOF"
if [ $? -eq 0 ]; then
color "主机 $host 执行集群认证成功!" 0
else
color "主机 $host 执行集群认证失败!" 2
fi
done
color "=======正在集群认证环境 end =======" 0
}
# 获取集群的MEMBER_STATE状态信息:OFFLINE离线状态、ONLINE在线状态、RECOVERING恢复状态、UNREACHABLE不可达状态、ERROR错误状态
# 检查集群状态信息
check_cluster_online_exists() {
color "=====正在检查集群状态信息 start ======" 0
local total_online=0
local host_count=${#HOSTS[@]}
# 循环执行
for host in "${HOSTS[@]}"; do
color "正在检查主机 $host 的集群状态..." 0
# 获取当前主机看到的在线节点数
ONLINE_COUNT=$(ssh root@"$host" "mysql -u root -p'$MYSQL_PWD' -Ns -e '
SELECT COUNT(*)
FROM performance_schema.replication_group_members
WHERE MEMBER_STATE = \"ONLINE\"
'")
# 验证查询结果是否有效
if [[ -z "$ONLINE_COUNT" || "$ONLINE_COUNT" -lt 0 ]]; then
color "错误:无法从主机 $host 获取有效集群状态!" 1
continue
fi
color "主机 $host 报告:集群中有 $ONLINE_COUNT 个节点在线" 0
# 记录最大在线节点数(避免因网络分区导致部分节点看到不同结果)
if [[ "$ONLINE_COUNT" -gt "$total_online" ]]; then
total_online="$ONLINE_COUNT"
fi
sleep 1
done
sleep 2
# 判断集群状态
if [[ "$total_online" -eq 0 ]]; then
color "错误:集群中没有在线节点,集群处于离线状态!" 2
elif [[ "$total_online" -lt "$host_count" ]]; then
color "警告:集群不完整!总节点数: $host_count, 在线节点: $total_online" 2
else
color "成功:所有 $total_online 个节点均在线,集群状态正常!" 0
fi
color "======正在检查集群状态信息 end =======" 0
}
# 检查集群中是否存在 PRIMARY 节点
check_cluster_primary_exists() {
color "=====正在检查集群中是否存在 PRIMARY 节点 start =====" 0
local total_primary=0
local host_count=${#HOSTS[@]}
# 遍历所有主机,统计 PRIMARY 节点总数
for host in "${HOSTS[@]}"; do
color "正在检查主机 $host 的 PRIMARY 节点..." 1
# 获取当前主机视角的 PRIMARY 节点数(单主模式下应为 1)
local primary_count=$(ssh root@"$host" "mysql -u root -p'$MYSQL_PWD' -Ns -e '
SELECT COUNT(*)
FROM performance_schema.replication_group_members
WHERE MEMBER_STATE = \"ONLINE\" AND MEMBER_ROLE = \"PRIMARY\"
'")
# 验证结果有效性
if [[ -z "$primary_count" || ! "$primary_count" =~ ^[0-9]+$ ]]; then
color "错误:主机 $host 返回无效数据!" 2
continue
fi
color "主机 $host 报告:PRIMARY 节点数 = $primary_count" 1
# 累加 PRIMARY 节点数(正常情况下所有主机的统计应一致)
((total_primary += primary_count))
sleep 1
done
sleep 2
# 判断集群 PRIMARY 节点状态
if [[ "$total_primary" -eq 0 ]]; then
color "错误:集群中没有 ONLINE 状态的 PRIMARY 节点!" 2
#return 1 # 返回失败状态码
elif [[ "$total_primary" -gt 1 && "$group_replication_single_primary_mode" == "ON" ]]; then
color "警告:单主模式下出现多个 PRIMARY 节点(可能脑裂)!" 1
#return 2 # 返回警告状态码
else
color "成功:集群中有 $total_primary 个 PRIMARY 节点" 0
#return 0 # 返回成功状态码
fi
color "=====正在检查集群中是否存在 PRIMARY 节点 end =======" 0
}
# 检查集群中的 PRIMARY 节点并返回其主机名
check_cluster_primary_node() {
color "=====正在检查集群中的 PRIMARY 节点 start ========" 0
PRIMARY_HOST=""
local host_count=${#HOSTS[@]}
local found=0
# 遍历所有主机,查找 PRIMARY 节点
for host in "${HOSTS[@]}"; do
color "正在从主机 $host 查询 PRIMARY 节点..." 0
# 获取当前主机视角的 PRIMARY 节点
local primary_candidate=$(ssh root@"$host" "mysql -u root -p'$MYSQL_PWD' -Ns -e '
SELECT MEMBER_HOST
FROM performance_schema.replication_group_members
WHERE MEMBER_STATE = \"ONLINE\" AND MEMBER_ROLE = \"PRIMARY\"
'")
# 验证查询结果有效性
if [[ -n "$primary_candidate" ]]; then
if [[ -z "$PRIMARY_HOST" ]]; then
PRIMARY_HOST="$primary_candidate"
color "发现 PRIMARY 节点: $PRIMARY_HOST" 1
found=1
elif [[ "$primary_candidate" != "$PRIMARY_HOST" ]]; then
color "警告: 主机 $host 报告的 PRIMARY 节点($primary_candidate)与之前不一致!" 1
color "可能存在脑裂,请检查网络分区!" 1
fi
else
color "主机 $host 未发现 ONLINE 状态的 PRIMARY 节点" 0
fi
# 短暂延迟,避免并发查询过多
sleep 2
done
sleep 2
# 判断最终结果
if [[ $found -eq 0 ]]; then
color "错误: 集群中未发现 PRIMARY 节点!" 1
else
color "成功: 集群 PRIMARY 节点为 $PRIMARY_HOST" 2
echo "$PRIMARY_HOST" # 输出 PRIMARY 节点主机名
fi
color "=====正在检查集群中的 PRIMARY 节点 end ========" 0
}
# 显示集群状态摘要
show_cluster_status() {
color "=======正在显示集群状态摘要 start =======" 0
for host in "${HOSTS[@]}"; do
# 使用 mysqladmin ping 检查连接状态
if ssh root@"$host" "mysqladmin ping -h localhost -uroot -p'$MYSQL_PWD' --silent"; then
service_status="active"
# 获取节点在集群中的角色和状态
node_info=$(ssh root@"$host" "mysql -u root -p'$MYSQL_PWD' -Ns <<EOF
SELECT CONCAT(MEMBER_HOST, ':', MEMBER_STATE, ':', MEMBER_ROLE)
FROM performance_schema.replication_group_members
WHERE MEMBER_HOST = '$host';
EOF")
# 输出结果
if [[ -n "$node_info" ]]; then
color "节点 $host: MySQL状态=$service_status, 集群角色=$node_info" 0
fi
else
service_status="inactive"
color "节点 $host: MySQL状态=$service_status, 未加入集群或无法连接" 2
fi
sleep 1
done
sleep 1
color "=======正在显示集群状态摘要 end =====" 0
}
# 启动集群环境
cluster_start_sql(){
color "-------正在启动集群环境 start -----" 0
# 确保至少有一个主机
if [[ ${#HOSTS[@]} -lt 1 ]]; then
color "错误: 主机列表为空" 2
return 1
fi
# SQL 脚本内容
PRIMARY_SQL=$(cat <<-'EOF'
# 主服务器启动时并不会直接启动复制组,通过下面的命令动态的开启复制组是我们的集群更安全
SET GLOBAL group_replication_bootstrap_group=ON;
# 开启组网数据同步,该步骤尽在master节点上运行
START GROUP_REPLICATION USER='rpl_user', PASSWORD='123456';
SET GLOBAL group_replication_bootstrap_group=OFF;
EOF
)
# 执行主节点启动命令
color "$PRIMARY_HOST 主机正在启动集群主角色环境" 0
# 通过 SSH 执行 MySQL 命令
ssh root@$PRIMARY_HOST "mysql -u root -p'$MYSQL_PWD'<<'EOF'
$PRIMARY_SQL
EOF"
# 等待主节点完全启动
sleep 2
# 从节点启动脚本
# SQL 脚本内容
SECONDARY_SQL=$(cat <<-'EOF'
# 配置复制源并加入集群
change replication source to source_user='rpl_user',source_password='123456' for channel 'group_replication_recovery';
start group_replication user='rpl_user',password='123456';
EOF
)
# 启动其他从节点
for host in "${HOSTS[@]}"; do
if [[ "$host" == "$PRIMARY_HOST" ]]; then
continue # 跳过主节点
fi
color "$host 主机正在启动集群从角色环境" 0
# 通过 SSH 执行 MySQL 命令
ssh root@$host "mysql -u root -p'$MYSQL_PWD'<<'EOF'
$SECONDARY_SQL
EOF"
# 短暂延迟,避免同时启动导致网络压力
sleep 2
done
sleep 1
# 检查集群中是否存在 PRIMARY 节点
check_cluster_primary_exists
# 检查集群状态信息
check_cluster_online_exists
# 检查集群中的 PRIMARY 节点并返回其主机名
check_cluster_primary_node
# 显示集群状态摘要
show_cluster_status
color "-----正在启动集群环境 end ----" 0
}
# 安装msyql环境
install_mysql_server () {
# 安装并启动 MySQL 服务
install_mysql_soft
# 检查Mysql是否启动
check_mysql_is_active
alter_mysql_pwd
get_mysql_version
mysql_config
# 集群认证环境
cluster_auth_init_sql
# 启动集群环境
cluster_start_sql
# 显示集群状态摘要
show_cluster_status
}
main(){
# 免密认证
pwd_auth_across_host
# 基础环境
base_environment
# 安装msyql环境
install_mysql_server
}
main
效果






endl
评论