229 lines
8.5 KiB
YAML
229 lines
8.5 KiB
YAML
|
|
---
|
||
|
|
- name: Preflight native MySQL delivery
|
||
|
|
hosts: "{{ target_hosts }}"
|
||
|
|
become: true
|
||
|
|
gather_facts: true
|
||
|
|
any_errors_fatal: true
|
||
|
|
vars:
|
||
|
|
mysql_instance: "{{ instance_name }}"
|
||
|
|
mysql_port_value: "{{ mysql_port | default(3307) | int }}"
|
||
|
|
mysql_memory_mb_value: "{{ memory_mb | default(2048) | int }}"
|
||
|
|
mysql_storage_gb_value: "{{ storage_gb | default(20) | int }}"
|
||
|
|
mysql_install_dir: "/opt/mysql-delivery/{{ mysql_instance }}"
|
||
|
|
mysql_data_dir: "/data/mysql-delivery/{{ mysql_instance }}/data"
|
||
|
|
mysql_log_dir: "/data/mysql-delivery/{{ mysql_instance }}/log"
|
||
|
|
mysql_run_dir: "/run/mysql-delivery-{{ mysql_instance }}"
|
||
|
|
mysql_config_file: "/etc/mysql/mysql-delivery/{{ mysql_instance }}.cnf"
|
||
|
|
mysql_root_password_value: "{{ lookup('ansible.builtin.env', 'XINFRA_MYSQL_ROOT_PASSWORD') }}"
|
||
|
|
mysql_admin_password_value: "{{ lookup('ansible.builtin.env', 'XINFRA_MYSQL_ADMIN_PASSWORD') }}"
|
||
|
|
pre_tasks:
|
||
|
|
- name: Validate prototype parameters
|
||
|
|
ansible.builtin.assert:
|
||
|
|
that:
|
||
|
|
- topology == 'standalone'
|
||
|
|
- mysql_instance is match('^[a-z0-9][a-z0-9-]{0,62}$')
|
||
|
|
- (mysql_port_value | int) >= 1024
|
||
|
|
- (mysql_port_value | int) <= 65535
|
||
|
|
- (mysql_memory_mb_value | int) >= 1024
|
||
|
|
- (mysql_memory_mb_value | int) <= 4096
|
||
|
|
- (mysql_storage_gb_value | int) >= 10
|
||
|
|
- (mysql_storage_gb_value | int) <= 100
|
||
|
|
- mysql_root_password_value | length >= 16
|
||
|
|
- mysql_admin_password_value | length >= 16
|
||
|
|
fail_msg: The first machine prototype only supports safe standalone parameters
|
||
|
|
no_log: true
|
||
|
|
|
||
|
|
- name: Validate topology host count
|
||
|
|
ansible.builtin.assert:
|
||
|
|
that:
|
||
|
|
- ansible_play_hosts_all | length == 1
|
||
|
|
fail_msg: The selected topology does not match the target host count
|
||
|
|
run_once: true
|
||
|
|
|
||
|
|
- name: Check port ownership
|
||
|
|
ansible.builtin.shell: |
|
||
|
|
set -o pipefail
|
||
|
|
if ss -lntH "sport = :{{ mysql_port_value }}" | grep -q .; then
|
||
|
|
systemctl is-active --quiet "mysql-delivery@{{ mysql_instance }}.service"
|
||
|
|
fi
|
||
|
|
args:
|
||
|
|
executable: /bin/bash
|
||
|
|
changed_when: false
|
||
|
|
|
||
|
|
- name: Read currently available memory
|
||
|
|
ansible.builtin.shell: awk '/^MemAvailable:/ { print int($2 / 1024) }' /proc/meminfo
|
||
|
|
args:
|
||
|
|
executable: /bin/bash
|
||
|
|
register: mysql_available_memory
|
||
|
|
changed_when: false
|
||
|
|
|
||
|
|
- name: Check available memory and disk
|
||
|
|
ansible.builtin.assert:
|
||
|
|
that:
|
||
|
|
- (mysql_available_memory.stdout | int) >= (mysql_memory_mb_value | int)
|
||
|
|
- (ansible_mounts | selectattr('mount', 'equalto', '/') | map(attribute='size_available') | first | int) >= (mysql_storage_gb_value | int) * 1073741824
|
||
|
|
fail_msg: Target host does not have enough available memory or disk
|
||
|
|
|
||
|
|
tasks:
|
||
|
|
- name: Detect an existing Ubuntu MySQL package
|
||
|
|
ansible.builtin.command: dpkg-query -W mysql-server
|
||
|
|
register: mysql_package_before
|
||
|
|
failed_when: false
|
||
|
|
changed_when: false
|
||
|
|
|
||
|
|
- name: Install Ubuntu MySQL package
|
||
|
|
ansible.builtin.apt:
|
||
|
|
name: mysql-server=8.0.46-0ubuntu0.24.04.3
|
||
|
|
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_package_before.rc != 0
|
||
|
|
|
||
|
|
- name: Check for the bundled MySQL AppArmor profile
|
||
|
|
ansible.builtin.stat:
|
||
|
|
path: /etc/apparmor.d/usr.sbin.mysqld
|
||
|
|
register: mysql_apparmor_profile
|
||
|
|
|
||
|
|
- name: Allow the delivery data and run 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
|
||
|
|
/data/mysql-delivery/ r,
|
||
|
|
/data/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
|
||
|
|
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_data_dir }}", owner: mysql, group: mysql, mode: '0750' }
|
||
|
|
- { path: "{{ mysql_log_dir }}", owner: mysql, group: mysql, mode: '0750' }
|
||
|
|
- { path: "{{ mysql_run_dir }}", owner: mysql, group: mysql, mode: '0755' }
|
||
|
|
|
||
|
|
- 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 ansible_play_hosts_all.index(inventory_hostname) == 0 else
|
||
|
|
('replica' if topology == 'primary_replica' else 'mgr')) }}
|
||
|
|
|
||
|
|
- 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"
|
||
|
|
no_log: true
|
||
|
|
|
||
|
|
- 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
|
||
|
|
ansible.builtin.shell: |
|
||
|
|
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
|
||
|
|
if ! /usr/bin/mysql --defaults-extra-file="$client_file" -e 'SELECT 1' >/dev/null 2>&1; then
|
||
|
|
/usr/bin/mysql --protocol=socket --socket={{ mysql_run_dir }}/mysql.sock -uroot <<'SQL'
|
||
|
|
ALTER USER 'root'@'localhost' IDENTIFIED BY '{{ mysql_root_password_value }}';
|
||
|
|
SQL
|
||
|
|
fi
|
||
|
|
/usr/bin/mysql --defaults-extra-file="$client_file" <<'SQL'
|
||
|
|
CREATE USER IF NOT EXISTS 'xinfra_admin'@'%' IDENTIFIED BY '{{ mysql_admin_password_value }}';
|
||
|
|
ALTER USER 'xinfra_admin'@'%' IDENTIFIED BY '{{ mysql_admin_password_value }}';
|
||
|
|
GRANT ALL PRIVILEGES ON *.* TO 'xinfra_admin'@'%' WITH GRANT OPTION;
|
||
|
|
FLUSH PRIVILEGES;
|
||
|
|
SQL
|
||
|
|
args:
|
||
|
|
executable: /bin/bash
|
||
|
|
changed_when: false
|
||
|
|
no_log: true
|
||
|
|
|
||
|
|
- name: Verify MySQL TCP health
|
||
|
|
ansible.builtin.wait_for:
|
||
|
|
host: "{{ ansible_host | default(inventory_hostname) }}"
|
||
|
|
port: "{{ mysql_port_value }}"
|
||
|
|
timeout: 30
|
||
|
|
|
||
|
|
handlers:
|
||
|
|
- name: Restart MySQL delivery instance
|
||
|
|
ansible.builtin.systemd_service:
|
||
|
|
name: "mysql-delivery@{{ mysql_instance }}.service"
|
||
|
|
state: restarted
|