feat(ansible): MySQL 交付剧本接入 GTID 异步主从复制
- mysql_expected_hosts 按 replica_count 推导预期节点数 - 主节点判定改为 mysql_primary_host 显式指定,IP 经 hostvars 回退解析 - 副本节点等待主节点端口就绪后执行 CHANGE REPLICATION SOURCE (defaults-extra-file 临时凭据文件 + SOURCE_AUTO_POSITION + GET_SOURCE_PUBLIC_KEY) - 复制线程验证含 IO/SQL Running 与错误字段空值检查 (锚点带 ^[[:space:]]* 适配 SHOW REPLICA STATUS\G 的字段名前导空格)
This commit is contained in:
@@ -61,7 +61,13 @@
|
||||
mgr_group_name: "{{ group_replication_group_name | default('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa') }}"
|
||||
|
||||
# --- expected node count per topology ---
|
||||
mysql_expected_hosts: "{{ {'standalone': 1, 'primary_replica': 2, 'mgr_3': 3}[topology | default('standalone')] }}"
|
||||
mysql_expected_hosts: "{{ 1 if (topology | default('standalone')) == 'standalone' else ((replica_count | default(1) | int) + 1 if (topology | default('standalone')) == 'primary_replica' else 3) }}"
|
||||
# 平台为 primary_replica 显式传入主从节点;standalone 仍按旧逻辑工作。
|
||||
mysql_primary_host_value: "{{ mysql_primary_host | default(ansible_play_hosts_all[0]) }}"
|
||||
mysql_primary_ip_value: "{{ mysql_primary_ip | default(hostvars[mysql_primary_host_value].ansible_host | default(mysql_primary_host_value)) }}"
|
||||
# 交付只暴露 root 管理员;复制链路复用已配置好的 root@%。
|
||||
mysql_replication_user: "root"
|
||||
mysql_replication_password: "{{ mysql_root_password_value }}"
|
||||
|
||||
# --- secrets (prefer launch extra_vars; fall back to AWX credential-injected env) ---
|
||||
mysql_root_password_value: "{{ mysql_root_password | default(lookup('ansible.builtin.env', 'XINFRA_MYSQL_ROOT_PASSWORD'), true) }}"
|
||||
@@ -258,7 +264,7 @@
|
||||
mysql_node_index: "{{ ansible_play_hosts_all.index(inventory_hostname) }}"
|
||||
mysql_node_role: >-
|
||||
{{ 'standalone' if topology == 'standalone' else
|
||||
('primary' if ansible_play_hosts_all.index(inventory_hostname) == 0 else
|
||||
('primary' if inventory_hostname == mysql_primary_host_value else
|
||||
('replica' if topology == 'primary_replica' else 'mgr')) }}
|
||||
# host-wide unique: platform may pass explicit mysql_server_id; otherwise derive
|
||||
# port + node index (ports are unique per host in the pool model).
|
||||
@@ -364,19 +370,74 @@
|
||||
changed_when: false
|
||||
no_log: true
|
||||
|
||||
- name: Verify MySQL TCP health
|
||||
- name: Wait for the replica to reach the primary SQL port
|
||||
ansible.builtin.wait_for:
|
||||
host: "{{ mysql_primary_ip_value }}"
|
||||
port: "{{ mysql_port_value }}"
|
||||
timeout: 60
|
||||
when: topology == 'primary_replica' and inventory_hostname != mysql_primary_host_value
|
||||
|
||||
- name: Configure and start GTID replication on the replica
|
||||
ansible.builtin.shell:
|
||||
cmd: |
|
||||
set -euo pipefail
|
||||
client_file="$(mktemp)"
|
||||
sql_file="$(mktemp)"
|
||||
trap 'rm -f "$client_file" "$sql_file"' EXIT
|
||||
chmod 600 "$client_file" "$sql_file"
|
||||
cat >"$client_file" <<'EOF'
|
||||
[client]
|
||||
user=root
|
||||
password={{ mysql_root_password_value }}
|
||||
socket={{ mysql_run_dir }}/mysql.sock
|
||||
EOF
|
||||
cat >"$sql_file" <<'EOF'
|
||||
STOP REPLICA;
|
||||
RESET REPLICA ALL;
|
||||
CHANGE REPLICATION SOURCE TO SOURCE_HOST='{{ mysql_primary_ip_value }}', SOURCE_PORT={{ mysql_port_value }}, SOURCE_USER='{{ mysql_replication_user }}', SOURCE_PASSWORD='{{ mysql_replication_password | replace("'", "''") }}', SOURCE_AUTO_POSITION=1, GET_SOURCE_PUBLIC_KEY=1;
|
||||
START REPLICA;
|
||||
EOF
|
||||
/usr/bin/mysql --defaults-extra-file="$client_file" <"$sql_file"
|
||||
executable: /bin/bash
|
||||
when: topology == 'primary_replica' and inventory_hostname != mysql_primary_host_value
|
||||
no_log: true
|
||||
|
||||
- name: Verify GTID replication threads
|
||||
ansible.builtin.shell:
|
||||
cmd: |
|
||||
set -euo pipefail
|
||||
client_file="$(mktemp)"
|
||||
trap 'rm -f "$client_file"' EXIT
|
||||
chmod 600 "$client_file"
|
||||
cat >"$client_file" <<'EOF'
|
||||
[client]
|
||||
user=root
|
||||
password={{ mysql_root_password_value }}
|
||||
socket={{ mysql_run_dir }}/mysql.sock
|
||||
EOF
|
||||
status="$(/usr/bin/mysql --defaults-extra-file="$client_file" -e 'SHOW REPLICA STATUS\G')"
|
||||
grep -q 'Replica_IO_Running: Yes' <<<"$status"
|
||||
grep -q 'Replica_SQL_Running: Yes' <<<"$status"
|
||||
grep -qE '^[[:space:]]*Last_IO_Error:[[:space:]]*$' <<<"$status"
|
||||
grep -qE '^[[:space:]]*Last_SQL_Error:[[:space:]]*$' <<<"$status"
|
||||
executable: /bin/bash
|
||||
register: mysql_replica_status
|
||||
retries: 12
|
||||
delay: 5
|
||||
until: mysql_replica_status.rc == 0
|
||||
when: topology == 'primary_replica' and inventory_hostname != mysql_primary_host_value
|
||||
no_log: true
|
||||
|
||||
- name: Verify MySQL TCP health on every node
|
||||
ansible.builtin.wait_for:
|
||||
host: "{{ ansible_host | default(inventory_hostname) }}"
|
||||
port: "{{ mysql_port_value }}"
|
||||
timeout: 30
|
||||
|
||||
- name: Note pending HA runtime orchestration
|
||||
- name: Note pending Group Replication orchestration
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
topology={{ topology }} deployed with HA-ready config (GTID/binlog/relay/GR settings in place),
|
||||
but replication wiring (CHANGE REPLICATION SOURCE) and Group Replication bootstrap
|
||||
(START GROUP_REPLICATION) are not yet automated — nodes start config-ready only.
|
||||
when: topology != 'standalone'
|
||||
msg: "MGR remains unsupported; primary_replica GTID replication is configured and healthy."
|
||||
when: topology == 'mgr_3'
|
||||
run_once: true
|
||||
|
||||
handlers:
|
||||
|
||||
Reference in New Issue
Block a user