454df86021
- mysql-instance.cnf.j2 按 mysql_node_role=replica 注入 read-only/super-read-only, 阻止业务直接写入从库导致主从数据分叉(复制线程与复制管理语句不受影响) - 账号配置任务在从库上临时放开只读、配置完成后回锁, 避免 cnf 只读拒绝 ALTER/CREATE USER;主库与 standalone 不受影响
461 lines
20 KiB
YAML
461 lines
20 KiB
YAML
---
|
|
- name: Native MySQL delivery
|
|
hosts: "{{ target_hosts }}"
|
|
become: true
|
|
gather_facts: true
|
|
any_errors_fatal: true
|
|
vars:
|
|
# --- identity ---
|
|
mysql_instance: "{{ instance_name }}"
|
|
mysql_version_value: "{{ mysql_version | default('8.0') }}"
|
|
# 版本包映射:8.0 用 Ubuntu 24.04 自带源(精确锁版);8.4 用 MySQL 官方 APT 源的 LTS 组件
|
|
# (noble 自带源无 8.4,官方源组件内即为该系列,不再锁小版本)。
|
|
# 5.6/5.7 已官方 EOL 且 Ubuntu 24.04 无可用 apt 包,明确不纳入白名单。
|
|
mysql_package_map:
|
|
"8.0":
|
|
package: "mysql-server=8.0.46-0ubuntu0.24.04.3"
|
|
repo_component: ""
|
|
"8.4":
|
|
package: "mysql-community-server"
|
|
repo_component: "mysql-8.4-lts"
|
|
mysql_package_name: "{{ (mysql_package_map[mysql_version_value] | default({})).package | default('') }}"
|
|
mysql_repo_component: "{{ (mysql_package_map[mysql_version_value] | default({})).repo_component | default('') }}"
|
|
|
|
# --- platform-allocated inputs (safe fallbacks when not passed in) ---
|
|
# 端口池 13306–13999:平台从池分配并传入;未传时兜底池首端口,占用探测在目标机执行。
|
|
mysql_port_value: "{{ mysql_port | default(13306) | int }}"
|
|
# GR 组通信端口:仅 mgr_3 使用,平台成对分配;兜底为 SQL 端口 +10000。
|
|
mysql_gr_port_value: "{{ gr_port | default((mysql_port | default(13306) | int) + 10000) | int }}"
|
|
# 数据盘挂载点:平台按白名单选定并传入;兜底 /data。
|
|
mysql_data_disk: "{{ data_disk | default('/data') }}"
|
|
|
|
# --- resource quota (Go→AWX 契约单位保持 MB/GB,不擅改) ---
|
|
mysql_memory_mb_value: "{{ memory_mb | default(4096) | int }}"
|
|
mysql_storage_gb_value: "{{ storage_gb | default(50) | int }}"
|
|
|
|
# --- mount-point-agnostic layout: {data_disk}/mysql-delivery/{instance_id}/... ---
|
|
mysql_base_dir: "{{ mysql_data_disk }}/mysql-delivery/{{ mysql_instance }}"
|
|
mysql_install_dir: "/opt/mysql-delivery/{{ mysql_instance }}"
|
|
mysql_data_dir: "{{ mysql_base_dir }}/data"
|
|
mysql_log_dir: "{{ mysql_base_dir }}/logs"
|
|
mysql_binlog_dir: "{{ mysql_base_dir }}/logs/binlog"
|
|
mysql_redo_dir: "{{ mysql_base_dir }}/logs/redo"
|
|
mysql_tmp_dir: "{{ mysql_base_dir }}/tmp"
|
|
# socket/pid 走 /run tmpfs(重启自动清理),由 systemd RuntimeDirectory 创建。
|
|
mysql_run_dir: "/run/mysql-delivery-{{ mysql_instance }}"
|
|
mysql_config_file: "/etc/mysql/mysql-delivery/{{ mysql_instance }}.cnf"
|
|
|
|
# --- database config (user form, with doc default baselines) ---
|
|
mysql_timezone: "{{ timezone | default('+08:00') }}"
|
|
mysql_lower_case_table_names: "{{ lower_case_table_names | default(1) | int }}"
|
|
mysql_character_set: "{{ character_set | default('utf8mb4') }}"
|
|
mysql_collation: "{{ collation | default('utf8mb4_general_ci') }}"
|
|
|
|
# --- advanced params (overridable; default baseline assumes SSD for io_capacity) ---
|
|
mysql_flush_log_at_trx_commit: "{{ innodb_flush_log_at_trx_commit | default(1) | int }}"
|
|
mysql_sync_binlog: "{{ sync_binlog | default(1) | int }}"
|
|
mysql_io_capacity: "{{ innodb_io_capacity | default(2000) | int }}"
|
|
mysql_long_query_time: "{{ long_query_time | default(1) }}"
|
|
mysql_binlog_expire_logs_seconds: "{{ binlog_expire_logs_seconds | default(604800) | int }}"
|
|
mysql_max_binlog_size: "{{ max_binlog_size | default('256M') }}"
|
|
mgr_group_name: "{{ group_replication_group_name | default('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa') }}"
|
|
|
|
# --- expected node count per topology ---
|
|
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) }}"
|
|
|
|
pre_tasks:
|
|
- name: Validate delivery parameters
|
|
ansible.builtin.assert:
|
|
that:
|
|
- topology in ['standalone', 'primary_replica', 'mgr_3']
|
|
- mysql_instance is match('^[a-z0-9][a-z0-9-]{0,62}$')
|
|
- mysql_version_value in mysql_package_map
|
|
- mysql_data_disk is match('^/.+')
|
|
- mysql_data_disk != '/'
|
|
- "'..' not in mysql_data_disk"
|
|
- "'//' not in mysql_data_disk"
|
|
- (mysql_port_value | int) >= 13306
|
|
- (mysql_port_value | int) <= 13999
|
|
- (mysql_memory_mb_value | int) >= 2048
|
|
- (mysql_memory_mb_value | int) <= 65536
|
|
- (mysql_storage_gb_value | int) >= 20
|
|
- (mysql_storage_gb_value | int) <= 2000
|
|
- (mysql_lower_case_table_names | int) in [0, 1]
|
|
- mysql_root_password_value | length >= 16
|
|
fail_msg: >-
|
|
Delivery parameters out of the supported target-state whitelist
|
|
(topology / version-package-map / port pool 13306-13999 / memory 2-64G / storage 20-2000G).
|
|
quiet: true
|
|
no_log: true
|
|
|
|
- name: Validate topology host count
|
|
ansible.builtin.assert:
|
|
that:
|
|
- (ansible_play_hosts_all | length | int) == (mysql_expected_hosts | int)
|
|
fail_msg: "topology={{ topology }} expects {{ mysql_expected_hosts }} host(s), got {{ ansible_play_hosts_all | length }}"
|
|
run_once: true
|
|
|
|
- name: Probe target-host port occupancy (SQL + GR)
|
|
# cmd 字典形式不经过 free-form split_args 解析,避免引号/Jinja 块导致的解析失败。
|
|
ansible.builtin.shell:
|
|
cmd: |
|
|
set -o pipefail
|
|
{% for port in mysql_probe_ports %}
|
|
if ss -lntH "sport = :{{ port }}" | grep -q .; then
|
|
# already listening: only tolerated when owned by this instance service
|
|
if ! systemctl is-active --quiet "mysql-delivery@{{ mysql_instance }}.service"; then
|
|
echo "port {{ port }} already in use on target host" >&2
|
|
exit 3
|
|
fi
|
|
fi
|
|
{% endfor %}
|
|
executable: /bin/bash
|
|
vars:
|
|
mysql_probe_ports: "{{ [mysql_port_value, mysql_gr_port_value] if topology == 'mgr_3' else [mysql_port_value] }}"
|
|
changed_when: false
|
|
|
|
- name: Read currently available memory (point-in-time guard)
|
|
ansible.builtin.shell: awk '/^MemAvailable:/ { print int($2 / 1024) }' /proc/meminfo
|
|
args:
|
|
executable: /bin/bash
|
|
register: mysql_available_memory
|
|
changed_when: false
|
|
|
|
- name: Read available bytes from the backing filesystem
|
|
ansible.builtin.shell:
|
|
cmd: |
|
|
set -euo pipefail
|
|
candidate="{{ mysql_data_disk }}"
|
|
while [ ! -e "$candidate" ] && [ "$candidate" != "/" ]; do
|
|
candidate="$(dirname "$candidate")"
|
|
done
|
|
df -P -B1 "$candidate" | awk 'NR == 2 { print $4 }'
|
|
executable: /bin/bash
|
|
register: mysql_available_disk_bytes
|
|
changed_when: false
|
|
|
|
- name: Check available memory and data-disk space
|
|
ansible.builtin.assert:
|
|
that:
|
|
- (mysql_available_memory.stdout | int) >= (mysql_memory_mb_value | int)
|
|
- (mysql_available_disk_bytes.stdout | int) >= (mysql_storage_gb_value | int) * 1073741824
|
|
fail_msg: >-
|
|
Target host lacks memory or free space on {{ mysql_data_disk }};
|
|
host-wide Σ-quota budgeting is the platform's responsibility, this is a last-resort guard.
|
|
|
|
tasks:
|
|
- name: Detect an existing MySQL server package
|
|
ansible.builtin.command: dpkg-query -W -f '${Package}=${Version}\n' mysql-server mysql-community-server
|
|
register: mysql_package_before
|
|
failed_when: false
|
|
changed_when: false
|
|
|
|
- name: Resolve the installed MySQL server version
|
|
ansible.builtin.set_fact:
|
|
mysql_installed_version: >-
|
|
{{ (mysql_package_before.stdout_lines | select('search', '=') | list
|
|
| first | default('')).split('=') | last }}
|
|
|
|
# /usr 下的 mysqld/mysql 二进制全机共享,一台主机只能承载一个 MySQL 版本系列。
|
|
- name: Enforce host-level MySQL series consistency
|
|
ansible.builtin.assert:
|
|
that:
|
|
- mysql_installed_version == '' or mysql_installed_version.startswith(mysql_version_value ~ '.')
|
|
fail_msg: >-
|
|
Host already runs MySQL {{ mysql_installed_version }} but {{ mysql_version_value }} was requested;
|
|
native binaries under /usr are shared host-wide, so one host serves exactly one MySQL series.
|
|
|
|
- name: Install the MySQL APT repository signing key
|
|
ansible.builtin.get_url:
|
|
url: https://repo.mysql.com/RPM-GPG-KEY-mysql-2023
|
|
dest: /etc/apt/keyrings/mysql.asc
|
|
owner: root
|
|
group: root
|
|
mode: '0644'
|
|
when: mysql_repo_component != ''
|
|
|
|
- name: Configure the MySQL APT repository component
|
|
ansible.builtin.apt_repository:
|
|
repo: >-
|
|
deb [signed-by=/etc/apt/keyrings/mysql.asc]
|
|
http://repo.mysql.com/apt/ubuntu {{ ansible_distribution_release }} {{ mysql_repo_component }}
|
|
filename: xinfra-mysql-delivery
|
|
state: present
|
|
when: mysql_repo_component != ''
|
|
|
|
- name: Install the MySQL server package
|
|
ansible.builtin.apt:
|
|
name: "{{ mysql_package_name }}"
|
|
state: present
|
|
update_cache: true
|
|
cache_valid_time: 3600
|
|
|
|
- name: Stop the automatically created default instance on a clean host
|
|
ansible.builtin.systemd_service:
|
|
name: mysql.service
|
|
state: stopped
|
|
enabled: false
|
|
when: mysql_installed_version == ''
|
|
|
|
- name: Check for the bundled MySQL AppArmor profile
|
|
ansible.builtin.stat:
|
|
path: /etc/apparmor.d/usr.sbin.mysqld
|
|
register: mysql_apparmor_profile
|
|
|
|
- name: Authorize the delivery paths in the MySQL AppArmor profile
|
|
ansible.builtin.copy:
|
|
dest: /etc/apparmor.d/local/usr.sbin.mysqld
|
|
owner: root
|
|
group: root
|
|
mode: '0644'
|
|
content: |
|
|
# Managed by XINFRA MySQL delivery - grant per-instance native paths
|
|
{{ mysql_data_disk }}/mysql-delivery/ r,
|
|
{{ mysql_data_disk }}/mysql-delivery/** rwk,
|
|
/run/mysql-delivery-*/ rw,
|
|
/run/mysql-delivery-*/** rwk,
|
|
when: mysql_apparmor_profile.stat.exists
|
|
register: mysql_apparmor_local
|
|
|
|
- name: Reload the MySQL AppArmor profile
|
|
ansible.builtin.command: apparmor_parser -r /etc/apparmor.d/usr.sbin.mysqld
|
|
when: mysql_apparmor_profile.stat.exists and mysql_apparmor_local.changed
|
|
changed_when: true
|
|
|
|
- name: Create instance directories (mount-point-agnostic layout)
|
|
ansible.builtin.file:
|
|
path: "{{ item.path }}"
|
|
state: directory
|
|
owner: "{{ item.owner }}"
|
|
group: "{{ item.group }}"
|
|
mode: "{{ item.mode }}"
|
|
loop:
|
|
- { path: /etc/mysql/mysql-delivery, owner: root, group: mysql, mode: '0750' }
|
|
- { path: "{{ mysql_install_dir }}", owner: root, group: root, mode: '0755' }
|
|
- { path: "{{ mysql_base_dir }}", owner: mysql, group: mysql, mode: '0750' }
|
|
- { path: "{{ mysql_data_dir }}", owner: mysql, group: mysql, mode: '0750' }
|
|
- { path: "{{ mysql_log_dir }}", owner: mysql, group: mysql, mode: '0750' }
|
|
- { path: "{{ mysql_binlog_dir }}", owner: mysql, group: mysql, mode: '0750' }
|
|
- { path: "{{ mysql_redo_dir }}", owner: mysql, group: mysql, mode: '0750' }
|
|
- { path: "{{ mysql_run_dir }}", owner: mysql, group: mysql, mode: '0755' }
|
|
- { path: "{{ mysql_tmp_dir }}", owner: mysql, group: mysql, mode: '0750' }
|
|
|
|
- name: Link native binaries into the instance directory
|
|
ansible.builtin.file:
|
|
src: "{{ item.src }}"
|
|
dest: "{{ mysql_install_dir }}/{{ item.dest }}"
|
|
state: link
|
|
loop:
|
|
- { src: /usr/sbin/mysqld, dest: mysqld }
|
|
- { src: /usr/bin/mysql, dest: mysql }
|
|
- { src: /usr/bin/mysqladmin, dest: mysqladmin }
|
|
|
|
- name: Calculate host role and server id
|
|
ansible.builtin.set_fact:
|
|
mysql_node_index: "{{ ansible_play_hosts_all.index(inventory_hostname) }}"
|
|
mysql_node_role: >-
|
|
{{ 'standalone' if topology == 'standalone' 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).
|
|
mysql_server_id_value: >-
|
|
{{ mysql_server_id | default((mysql_port_value | int)
|
|
+ (ansible_play_hosts_all.index(inventory_hostname))) | int }}
|
|
|
|
- name: Resolve memory tier (GB)
|
|
ansible.builtin.set_fact:
|
|
mysql_memory_gb: "{{ ((mysql_memory_mb_value | int) // 1024) | int }}"
|
|
|
|
- name: Resolve linkage-derived defaults (illustrative tiers, pending hardware calibration)
|
|
ansible.builtin.set_fact:
|
|
mysql_max_connections: >-
|
|
{{ (max_connections | int) if (max_connections is defined and (max_connections | string) != 'auto')
|
|
else (200 if (mysql_memory_gb | int) <= 2
|
|
else 500 if (mysql_memory_gb | int) <= 4
|
|
else 1000 if (mysql_memory_gb | int) <= 8
|
|
else 2000 if (mysql_memory_gb | int) <= 16
|
|
else 4000 if (mysql_memory_gb | int) <= 32
|
|
else 8000 if (mysql_memory_gb | int) <= 64
|
|
else 16000) }}
|
|
mysql_redo_capacity: >-
|
|
{{ innodb_redo_log_capacity if (innodb_redo_log_capacity is defined and (innodb_redo_log_capacity | string) != 'auto')
|
|
else ('128M' if (mysql_memory_gb | int) <= 4
|
|
else '256M' if (mysql_memory_gb | int) <= 16
|
|
else '512M' if (mysql_memory_gb | int) <= 32
|
|
else '1G') }}
|
|
|
|
- name: Write instance configuration
|
|
ansible.builtin.template:
|
|
src: templates/mysql-instance.cnf.j2
|
|
dest: "{{ mysql_config_file }}"
|
|
owner: root
|
|
group: mysql
|
|
mode: '0640'
|
|
notify: Restart MySQL delivery instance
|
|
|
|
- name: Initialize the data directory once
|
|
ansible.builtin.command:
|
|
argv:
|
|
- /usr/sbin/mysqld
|
|
- "--defaults-file={{ mysql_config_file }}"
|
|
- --initialize-insecure
|
|
- --user=mysql
|
|
args:
|
|
creates: "{{ mysql_data_dir }}/auto.cnf"
|
|
|
|
- name: Install the delivery systemd template
|
|
ansible.builtin.copy:
|
|
src: files/mysql-delivery@.service
|
|
dest: /etc/systemd/system/mysql-delivery@.service
|
|
owner: root
|
|
group: root
|
|
mode: '0644'
|
|
register: mysql_systemd_unit
|
|
|
|
- name: Reload systemd units
|
|
ansible.builtin.systemd_service:
|
|
daemon_reload: true
|
|
when: mysql_systemd_unit.changed
|
|
|
|
- name: Start the MySQL delivery instance
|
|
ansible.builtin.systemd_service:
|
|
name: "mysql-delivery@{{ mysql_instance }}.service"
|
|
state: started
|
|
enabled: true
|
|
|
|
- name: Wait for the local MySQL socket
|
|
ansible.builtin.wait_for:
|
|
path: "{{ mysql_run_dir }}/mysql.sock"
|
|
timeout: 60
|
|
|
|
- name: Configure local administrative accounts
|
|
# cmd 字典形式不经过 free-form split_args 解析,heredoc SQL 中的奇数个单引号才不会报错。
|
|
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
|
|
if ! /usr/bin/mysql --defaults-extra-file="$client_file" -e 'SELECT 1' >/dev/null 2>&1; then
|
|
cat >"$sql_file" <<'EOF'
|
|
{% if topology == 'primary_replica' and inventory_hostname != mysql_primary_host_value %}
|
|
SET GLOBAL super_read_only = OFF;
|
|
SET GLOBAL read_only = OFF;
|
|
{% endif %}
|
|
ALTER USER 'root'@'localhost' IDENTIFIED BY '{{ mysql_root_password_value | replace("'", "''") }}';
|
|
EOF
|
|
/usr/bin/mysql --protocol=socket --socket={{ mysql_run_dir }}/mysql.sock -uroot <"$sql_file"
|
|
fi
|
|
cat >"$sql_file" <<'EOF'
|
|
{% if topology == 'primary_replica' and inventory_hostname != mysql_primary_host_value %}
|
|
SET GLOBAL super_read_only = OFF;
|
|
SET GLOBAL read_only = OFF;
|
|
{% endif %}
|
|
CREATE USER IF NOT EXISTS 'root'@'%' IDENTIFIED BY '{{ mysql_root_password_value | replace("'", "''") }}';
|
|
ALTER USER 'root'@'%' IDENTIFIED BY '{{ mysql_root_password_value | replace("'", "''") }}';
|
|
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
|
|
FLUSH PRIVILEGES;
|
|
{% if topology == 'primary_replica' and inventory_hostname != mysql_primary_host_value %}
|
|
SET GLOBAL read_only = ON;
|
|
SET GLOBAL super_read_only = ON;
|
|
{% endif %}
|
|
EOF
|
|
/usr/bin/mysql --defaults-extra-file="$client_file" <"$sql_file"
|
|
executable: /bin/bash
|
|
changed_when: false
|
|
no_log: true
|
|
|
|
- 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 Group Replication orchestration
|
|
ansible.builtin.debug:
|
|
msg: "MGR remains unsupported; primary_replica GTID replication is configured and healthy."
|
|
when: topology == 'mgr_3'
|
|
run_once: true
|
|
|
|
handlers:
|
|
- name: Restart MySQL delivery instance
|
|
ansible.builtin.systemd_service:
|
|
name: "mysql-delivery@{{ mysql_instance }}.service"
|
|
daemon_reload: true
|
|
state: restarted
|