diff --git a/.travis-pre-commit-config.yaml b/.travis-pre-commit-config.yaml new file mode 100644 index 0000000..9ce3990 --- /dev/null +++ b/.travis-pre-commit-config.yaml @@ -0,0 +1,66 @@ +--- +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.5.0 + hooks: + - id: check-ast + - id: check-case-conflict + - id: check-executables-have-shebangs + - id: check-yaml + name: Check yaml files + - id: debug-statements + name: Check for Python debug statements + - id: trailing-whitespace + name: Check trailing whitespace + args: [--markdown-linebreak-ext=md] + - repo: https://gitlab.com/pycqa/flake8.git + rev: 3.7.9 + hooks: + - id: flake8 + name: Check python (flake8) + args: ["--ignore=E501"] + - repo: https://github.com/adrienverge/yamllint.git + rev: v1.23.0 + hooks: + - id: yamllint + name: Check yaml files (yamllint) + - repo: https://github.com/ansible/ansible-lint.git + rev: v4.3.0a0 + hooks: + - id: ansible-lint + name: Check yaml files (ansible-lint) + args: ["-x", "602"] + types: [yaml] + - repo: local + hooks: + - id: docker-markdownlint + name: Run markdownlint with docker + entry: markdownlint/markdownlint -r "~MD013" + language: docker_image + types: [markdown] + - id: docker-shell-shfmt + name: Run shfmt with docker + entry: mvdan/shfmt:latest -d -i 2 -ci + language: docker_image + types: [shell] + - id: docker-shell-lint + name: Run shellcheck with docker + language: docker_image + entry: koalaman/shellcheck:stable + types: [shell] + - id: docker-prettier + name: Run prettier with docker + entry: tmknom/prettier:latest -l + language: docker_image + files: "\\.(\ + css|less|scss\ + |graphql|gql\ + |html\ + |js|jsx\ + |json\ + |md|markdown|mdown|mkdn\ + |mdx\ + |ts|tsx\ + |vue\ + |yaml|yml\ + )$" diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c997a81 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,37 @@ +--- +language: python +services: docker + +env: + global: + - ROLE_NAME: mariadb + matrix: + - MOLECULE_DISTRO: debian-10 + - MOLECULE_DISTRO: debian-sid + - MOLECULE_DISTRO: ubuntu-18.04 + - MOLECULE_DISTRO: ubuntu-20.04 + +cache: + directories: + - $HOME/.cache/pre-commit + +install: + - pip3 install -r requirements.txt + +jobs: + include: + - name: lint with pre-commit + install: skip + script: + - pip3 install pre-commit + - pre-commit run -c .travis-pre-commit-config.yaml -a + +script: + - export ANSIBLE_STRATEGY=mitogen_linear + - export ANSIBLE_STRATEGY_PLUGINS=${VIRTUAL_ENV}/lib/python${TRAVIS_PYTHON_VERSION}/site-packages/ansible_mitogen/plugins/strategy + - molecule --version + - molecule test +# +# //TEMP import later to galaxy +# notifications: +# webhooks: https://galaxy.ansible.com/api/v1/notifications/ diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..5d7b323 --- /dev/null +++ b/.yamllint @@ -0,0 +1,7 @@ +--- +extends: default +ignore: | + .direnv +rules: + line-length: { max: 200, level: warning } + braces: { min-spaces-inside: 1, max-spaces-inside: 1 } diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6b51add --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +venv: + pip3 install -r requirements.txt + +venv_upgrade: + for i in $$(cat requirements.txt|cut -d "=" -f1); do pip3 install $$i -U; done + +test: + molecule test diff --git a/README.md b/README.md index 5e79b21..639cf17 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,237 @@ -# ansible-role-mariadb -Ansible Role: MariaDB +# Ansible role: MariaDB + +[![travis build status](https://img.shields.io/travis/fauust/ansible-role-mariadb?logo=travis)](https://travis-ci.org/fauust/ansible-role-mariadb) + +Install and configure MariaDB server on Debian/Ubuntu. + +Optionally, this role also permits to: + +- deploy a master/slave cluster; +- setup backups and rotation of the dumps. + +## Requirements + +The role use +[`mysql_user`](https://docs.ansible.com/ansible/latest/modules/mysql_user_module.html) +and +[`mysql_db`](https://docs.ansible.com/ansible/latest/modules/mysql_db_module.html) +ansible modules that depends on [PyMySQL](https://github.com/PyMySQL/PyMySQL) so +the following Python 2.7 or Python 3.X versions are needed. + +For older Python versions, you may use +[MySQLdb](http://mysql-python.sourceforge.net/MySQLdb.html) but then the role +may not be idempotent (not tested). + +## Fact gathering (performance) + +Unless you want to use the MariaDB official repository (and then need +`ansible_distribution_version`, see [`tasks/setup.yml`](./tasks/setup.yml)) this +role does not require fact gathering. + +## Role variables + +Available variables are listed below, along with default values (see +[`defaults/main.yml`](./defaults/main.yml)): + +### MariaDB version + +```yaml +mariadb_use_official_repo: false +mariadb_use_official_repo_url: "http://ftp.igh.cnrs.fr/pub/mariadb/repo" +mariadb_use_official_repo_version: "10.4" +``` + +You may deploy the MariaDB Server version that comes with your distribution (Debian/Ubuntu) or +deploy the version packaged by the MariaDB Foundation. +You will find the repositories URL here: + + +By default we deploy the MariaDB Server version that comes with the distribution. + +### MariaDB service activation and restart + +```yaml +mariadb_enabled_on_startup: true +mariadb_can_restart: true +``` + +**Warning:** you may consider setting `mariadb_can_restart` to `false` on +production systems to prevent ansible runs to restart the MariaDB server. + +### General configuration + +To populate the MariaDB Server configuration file, we use almost only raw +variables. This permits more flexibility and a very simple +[`templates/my.cnf.j2`](./templates/my.cnf.j2) file. + +By default, some common and standard options are deployed. + +#### Basic settings + +The following variables are also needed by playbooks so we can not use raw +variables. + +```yaml +mariadb_config_file: /etc/mysql/my.cnf +mariadb_datadir: "/var/lib/mysql" +mariadb_port: "3306" +mariadb_bind_address: "127.0.0.1" +mariadb_unix_socket: "/run/mysqld/mysqld.sock" +``` + +```yaml +mariadb_basic_settings_raw: | + user = mysql + pid-file = /run/mysqld/mysqld.pid + socket = {{ mariadb_unix_socket }} + basedir = /usr + datadir = {{ mariadb_datadir }} + tmpdir = /tmp + lc-messages-dir = /usr/share/mysql + lc_messages = en_US + skip-external-locking + port = {{ mariadb_port }} + bind-address = {{ mariadb_bind_address }} +``` + +#### Fine tuning + +```yaml +mariadb_fine_tuning_raw: | + max_connections = 100 + connect_timeout = 5 + wait_timeout = 600 + max_allowed_packet = 16M + thread_cache_size = 128 + sort_buffer_size = 4M + bulk_insert_buffer_size = 16M + tmp_table_size = 32M + max_heap_table_size = 32M +``` + +#### Query cache + +```yaml +mariadb_query_cache_raw: | + query_cache_size = 16M +``` + +#### Logging + +```yaml +mariadb_logging_raw: | + log_error = /var/log/mysql/error.log +``` + +#### Character sets + +```yaml +mariadb_character_sets_raw: | + character-set-server = utf8mb4 + collation-server = utf8mb4_general_ci +``` + +#### InnoDB + +```yaml +mariadb_innodb_raw: | + # InnoDB is enabled by default with a 10MB datafile in /var/lib/mysql/. + # Read the manual for more InnoDB related options. There are many! +``` + +#### Mariadbdump + +```yaml +mariadb_mysqldump_raw: | + quick + quote-names + max_allowed_packet = 16M +``` + +### Databases management + +```yaml +# Databases. +mariadb_databases: [] +# - name: db1 +# collation: utf8_general_ci +# encoding: utf8 +# replicate: true|false +``` + +See: + +### Users management + +```yaml +# Users. +mariadb_users: [] +# - name: user +# host: 100.64.200.10 +# password: password +# priv: "*.*:USAGE/db1.*:ALL" +# state: present|absent +``` + +See: + +### Replication (optional) + +Replication is only enabled if `mariadb_replication_role` has a value (`master` or +`slave`). + +The replication setup on the slave use the GTID autopositionning +`master_use_gtid=slave_pos`. See: + + +For the moment, we use an `SQL` command but in ansible 2.10, we should be able +to use the +[`mysql_replication`](https://docs.ansible.com/ansible/latest/modules/mysql_replication_module.html) +module (see +[`tasks/replication_slave.yml`](./tasks/replication_slave.yml#L09-L33) and +). + +```yaml +mariadb_replication_role: "" # master|slave +mariadb_replication_master_ip: "" +mariadb_server_id: "1" +mariadb_max_binlog_size: "100M" +mariadb_binlog_format: "MIXED" +mariadb_expire_logs_days: "10" + +# Same keys as `mariadb_users` above. +# priv is set to "*.*:REPLICATION SLAVE" by default +mariadb_replication_user: [] +``` + +### Backups (optional) + +```yaml +# db dumps backup +mariadb_backup_db: false +mariadb_backup_db_cron_min: "50" +mariadb_backup_db_cron_hour: "00" +mariadb_backup_db_dir: "/mnt/backup" +mariadb_backup_db_rotation: "15" + +# name of the database to dump +# (mandatory if mariadb_backup_db is set to true) +mariadb_backup_db_name: [] +# - db1 +# - db2 +``` + +Database dump is done serially and compression (`gzip`) is done at the end to +avoid too long locks. + +## Example Playbook + +```yaml +- hosts: database + roles: + - fauust.mariadb +``` + +## Lincense + +GNU General Public License v3.0 diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..bcac895 --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,110 @@ +--- +# Configure the following to use official MariaDB repository +# see: https://downloads.mariadb.org/mariadb/repositories +mariadb_use_official_repo: false +mariadb_use_official_repo_url: "http://ftp.igh.cnrs.fr/pub/mariadb/repo" +mariadb_use_official_repo_version: "10.4" + +mariadb_enabled_on_startup: true +# The following is set to true by default but you may consider setting it to +# false on production servers to prevent ansible from restarting mariadb server. +mariadb_can_restart: true + +# Whether global conf file should be updated on every run +mariadb_overwrite_global_config_file: true + +# MariaDB configuration file +mariadb_config_file: /etc/mysql/my.cnf +# Basic settings +mariadb_datadir: "/var/lib/mysql" +mariadb_port: "3306" +mariadb_bind_address: "127.0.0.1" +mariadb_unix_socket: "/run/mysqld/mysqld.sock" +mariadb_basic_settings_raw: | + user = mysql + pid-file = /run/mysqld/mysqld.pid + socket = {{ mariadb_unix_socket }} + basedir = /usr + datadir = {{ mariadb_datadir }} + tmpdir = /tmp + lc-messages-dir = /usr/share/mysql + lc_messages = en_US + skip-external-locking + port = {{ mariadb_port }} + bind-address = {{ mariadb_bind_address }} + +# Fine tuning +mariadb_fine_tuning_raw: | + max_connections = 100 + connect_timeout = 5 + wait_timeout = 600 + max_allowed_packet = 16M + thread_cache_size = 128 + sort_buffer_size = 4M + bulk_insert_buffer_size = 16M + tmp_table_size = 32M + max_heap_table_size = 32M + +# Query cache +mariadb_query_cache_raw: | + query_cache_size = 16M + +# Logging +mariadb_logging_raw: | + log_error = /var/log/mysql/error.log + +# Character sets +mariadb_character_sets_raw: | + character-set-server = utf8mb4 + collation-server = utf8mb4_general_ci + +# InnoDB +mariadb_innodb_raw: | + # InnoDB is enabled by default with a 10MB datafile in /var/lib/mysql/. + # Read the manual for more InnoDB related options. There are many! + +# mariadbdump +mariadb_mysqldump_raw: | + quick + quote-names + max_allowed_packet = 16M + +# Databases +mariadb_databases: [] +# - name: db1 +# collation: utf8_general_ci +# encoding: utf8 +# replicate: true|false + +# Users +mariadb_users: [] +# - name: user +# host: 100.64.200.10 +# password: password +# priv: "*.*:USAGE/db1.*:ALL" +# state: present|absent + +# Replication +# replication is only enabled if mariadb_replication_role has values +mariadb_replication_role: "" # master|slave +mariadb_replication_master_ip: "" +mariadb_max_binlog_size: "100M" +mariadb_binlog_format: "MIXED" +mariadb_expire_logs_days: "10" + +# Replication users +# same keys as mariadb_users above +# priv is set to "*.*:REPLICATION SLAVE" by default +mariadb_replication_user: [] + +# Backups +mariadb_backup_db: false +mariadb_backup_db_cron_min: "50" +mariadb_backup_db_cron_hour: "00" +mariadb_backup_db_dir: "/opt/backup" +mariadb_backup_db_rotation: "15" + +# DB to backup +mariadb_backup_db_name: [] +# - db1 +# - db2 diff --git a/files/mariadb_dump_db.sh b/files/mariadb_dump_db.sh new file mode 100755 index 0000000..feb99ec --- /dev/null +++ b/files/mariadb_dump_db.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash + +set -o errexit +set -o pipefail +set -o nounset +set -o posix + +err() { + echo >&2 "[$(date +'%Y-%m-%dT%H:%M:%S%z')] ERROR: $*" +} + +usage() { + cat >&2 <<-EOF +Usage : $0 -d -l + -d dump directory destination (mandatory) + -l list of db to dump (coma separated, mandatory) + -k dumps to keep (in days) + -h help +EOF +} + +typeset VAR_DIR_ARGS="" +typeset VAR_DB_LIST_ARGS="" +typeset VAR_ROTATION_DAYS_ARGS="" + +while getopts "d:l:k:h" OPTION; do + case $OPTION in + d) + VAR_DIR_ARGS="$OPTARG" + ;; + l) + VAR_DB_LIST_ARGS="$OPTARG" + ;; + k) + VAR_ROTATION_DAYS_ARGS="$OPTARG" + ;; + h) + usage + exit 0 + ;; + *) + usage + exit 1 + ;; + esac +done + +[[ $VAR_DIR_ARGS != "" ]] || { + usage + exit 1 +} +[[ $VAR_DB_LIST_ARGS != "" ]] || { + usage + exit 1 +} + +# remove eventual trailing slash +typeset -r VAR_DUMPS_DST_DIR=${VAR_DIR_ARGS%/} + +if [[ ! -d $VAR_DUMPS_DST_DIR ]]; then + mkdir -p "$VAR_DUMPS_DST_DIR" || { + err "mkdir -p $VAR_DUMPS_DST_DIR" + exit 1 + } +fi + +for cmd in mysqldump gzip; do + command -v "$cmd" >/dev/null || { + err "$cmd command not found" + exit 1 + } +done + +for db in ${VAR_DB_LIST_ARGS//,/ }; do + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: start $db dump." + typeset DUMP_FILE=$VAR_DUMPS_DST_DIR/$db.$(date +%F_%H%M%S).sql + mysqldump --single-transaction --quick --routines "$db" >"$DUMP_FILE" + # shellcheck disable=SC2181 + if (($? != 0)); then + err "unable do dump $db" + exit 1 + fi + echo -e "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: done.\n" +done + +for sql in "$VAR_DUMPS_DST_DIR/"*.sql; do + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: compress $sql." + gzip -- "$sql" || { + err "gzip $sql" + exit 1 + } + echo -e "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: done.\n" +done + +if [[ -n $VAR_ROTATION_DAYS_ARGS ]]; then + # rotation + echo "Rotation of old dumps (-${VAR_ROTATION_DAYS_ARGS}d)" + if ! find "$VAR_DUMPS_DST_DIR" -name "*.sql.gz" -type f -mtime +"$((VAR_ROTATION_DAYS_ARGS - 1))" -exec /bin/rm -vf {} \;; then + err "clean old dumps" + exit 1 + fi + echo -e "done.\n" +fi diff --git a/handlers/main.yml b/handlers/main.yml new file mode 100644 index 0000000..e8710e8 --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,8 @@ +--- +- name: restart mariadb + service: + name: mariadb + state: restarted + when: + - mariadb_can_restart + - not ansible_check_mode diff --git a/meta/main.yml b/meta/main.yml new file mode 100644 index 0000000..b136bd4 --- /dev/null +++ b/meta/main.yml @@ -0,0 +1,21 @@ +--- +dependencies: [] + +galaxy_info: + author: fauust + role_name: mariadb + description: MariaDB server for Debian/Ubuntu. + license: "license GPL-3.0-only" + min_ansible_version: 2.8 + platforms: + - name: Debian + versions: + - buster + - sid + - name: Ubuntu + versions: + - bionic + - focal + galaxy_tags: + - mariadb + - mysql diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml new file mode 100644 index 0000000..e1b1085 --- /dev/null +++ b/molecule/default/converge.yml @@ -0,0 +1,14 @@ +--- +- name: Converge + hosts: all + gather_facts: true + vars: + ansible_python_interpreter: /usr/bin/python3 + vars_files: testvars.yml + + pre_tasks: + - name: Update apt cache + apt: update_cache=true cache_valid_time=600 + + roles: + - role: ansible-role-mariadb diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml new file mode 100644 index 0000000..647a451 --- /dev/null +++ b/molecule/default/molecule.yml @@ -0,0 +1,20 @@ +--- +dependency: + name: galaxy +driver: + name: docker +platforms: + - name: instance + image: "fauust/docker-ansible:${MOLECULE_DISTRO:-debian-10}" + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:ro + privileged: true + command: /lib/systemd/systemd +provisioner: + name: ansible + env: + ANSIBLE_GATHERING: explicit + ANSIBLE_FORCE_COLOR: true + ANSIBLE_PYTHON_INTERPRETER: /usr/bin/python3 +verifier: + name: testinfra diff --git a/molecule/default/tests/test_default.py b/molecule/default/tests/test_default.py new file mode 100644 index 0000000..bdce1b0 --- /dev/null +++ b/molecule/default/tests/test_default.py @@ -0,0 +1,69 @@ +import os + +import testinfra.utils.ansible_runner + +testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( + os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all') + + +def test_passwd_file(host): + passwd = host.file("/etc/passwd") + assert passwd.contains("mysql") + assert passwd.user == "root" + assert passwd.group == "root" + + +def test_mariadb_is_installed(host): + package = host.package("mariadb-server") + assert package.is_installed + + +def test_ensure_mariadb_is_listening_on_requiered_port(host): + assert host.socket("tcp://0.0.0.0:3306").is_listening + + +def test_mariadb_enabled_and_running(host): + assert host.service("mariadb").is_running + assert host.service("mariadb").is_enabled + + +def test_ensure_custom_config_is_applied(host): + config = host.file("/etc/mysql/my.cnf") + assert config.contains("datadir") + assert config.user == "root" + assert config.group == "root" + assert config.mode == 0o644 + + +def test_ensure_innodb_is_enabled(host): + assert host.run("mariadb -Bse 'SHOW ENGINES' |\ + grep -qE '^InnoDB.DEFAULT.*YES.YES.YES$'").rc == 0 + + +def test_ensure_system_db_exist(host): + assert host.run("mariadb -Bse 'SHOW DATABASES' |\ + grep -q '^mysql$'").rc == 0 + assert host.run("mariadb -Bse 'SHOW DATABASES' |\ + grep -q '^information_schema$'").rc == 0 + assert host.run("mariadb -Bse 'SHOW DATABASES' |\ + grep -q '^performance_schema$'").rc == 0 + + +def test_ensure_test_db_exist(host): + assert host.run("mariadb -Bse 'SHOW DATABASES' |\ + grep -q '^db1'").rc == 0 + assert host.run("mariadb -Bse 'SHOW DATABASES' |\ + grep -q '^db2'").rc == 0 + + +def test_some_sql_queries(host): + assert host.run("mariadb -e 'CREATE DATABASE db'").rc == 0 + assert host.run("mariadb -e 'CREATE TABLE\ + db.t_innodb(a1 SERIAL, c1 CHAR(8)) ENGINE=InnoDB;\ + INSERT INTO db.t_innodb VALUES (1,\"foo\"),(2,\"bar\")'").rc == 0 + assert host.run("mariadb -e 'CREATE FUNCTION db.f()\ + RETURNS INT DETERMINISTIC RETURN 1'").rc == 0 + assert host.run("mariadb -e 'SHOW TABLES IN db'").rc == 0 + assert host.run("mariadb -e 'SELECT * FROM db.t_innodb;\ + INSERT INTO db.t_innodb VALUES (3,\"foo\"),(4,\"bar\")'").rc == 0 + assert host.run("mariadb -e 'SELECT db.f()'").rc == 0 diff --git a/molecule/default/testvars.yml b/molecule/default/testvars.yml new file mode 100644 index 0000000..4b64303 --- /dev/null +++ b/molecule/default/testvars.yml @@ -0,0 +1,47 @@ +--- +mariadb_server_id: 1 +mariadb_bind_address: 0.0.0.0 +mariadb_replication_role: master + +mariadb_innodb_raw: | + innodb_buffer_pool_size = 512M + innodb_log_file_size = 64M + innodb_file_per_table = 1 + +mariadb_databases: + - name: db1 + collation: latin1_swedish_ci + encoding: latin1 + state: present + - name: db2 + state: present + replicate: true + +mariadb_users: + - name: user1 + host: "%" + password: user1passwd + priv: "db2.*:SELECT" + state: present + - name: user2 + host: 100.64.10.3 + password: user2passwd + priv: "db2.*:ALL" + state: present + +mariadb_replication_user: + - name: ReplicationUser + host: 100.64.10.33 + password: ReplicationPassword + state: present + +# backup +mariadb_backup_db: true +mariadb_backup_db_cron_min: "50" +mariadb_backup_db_cron_hour: "00" +mariadb_backup_db_dir: "/mnt/backup" +mariadb_backup_db_rotation: "15" + +mariadb_backup_db_name: + - db1 + - db2 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..12cf7fb --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +ansible-lint==4.2.0 +ansible==2.9.9 +docker==4.2.0 +flake8==3.8.1 +mitogen==0.2.9 +molecule==3.0.4 +testinfra==5.0.0 +yamllint==1.23.0 diff --git a/tasks/backup.yml b/tasks/backup.yml new file mode 100644 index 0000000..4726ac2 --- /dev/null +++ b/tasks/backup.yml @@ -0,0 +1,36 @@ +--- +- name: Install cron package + apt: + package: + - cron + state: present + +- name: Deploy DB dump script + copy: + src: mariadb_dump_db.sh + dest: /usr/local/bin/mariadb_dump_db.sh + owner: root + group: root + mode: 0744 + validate: "bash -n %s" + +- name: Ensure backup dir exists + file: + path: "{{ mariadb_backup_db_dir }}" + state: directory + owner: root + group: root + mode: 0755 + +- name: Deploy crontab file + cron: + name: "Dump MariaBD databases" + minute: "{{ mariadb_backup_db_cron_min }}" + hour: "{{ mariadb_backup_db_cron_hour }}" + job: "/usr/local/bin/mariadb_dump_db.sh \ + -d \"{{ mariadb_backup_db_dir }}\" \ + -l \"{{ mariadb_backup_db_name|join(',') }}\" \ + -k {{ mariadb_backup_db_rotation }} \ + >{{ mariadb_backup_db_dir }}/mariadb_dump_db.log 2>&1" + cron_file: mariadb_dump_db + user: root diff --git a/tasks/configure.yml b/tasks/configure.yml new file mode 100644 index 0000000..e702749 --- /dev/null +++ b/tasks/configure.yml @@ -0,0 +1,28 @@ +--- +- name: Copy global MariaDB configuration + template: + src: my.cnf.j2 + dest: "{{ mariadb_config_file }}" + owner: root + group: root + mode: 0644 + force: "{{ mariadb_overwrite_global_config_file }}" + notify: restart mariadb + +- name: Create datadir if it does not exist + file: + path: "{{ mariadb_datadir }}" + state: directory + owner: mysql + group: mysql + mode: 0755 + +- name: Ensure MariaDB is started and enabled on boot + service: + name: mariadb + state: started + enabled: "{{ mariadb_enabled_on_startup }}" + +# immediately restart mariadb +# this is necessary for replication setup +- meta: flush_handlers diff --git a/tasks/databases.yml b/tasks/databases.yml new file mode 100644 index 0000000..920e1c4 --- /dev/null +++ b/tasks/databases.yml @@ -0,0 +1,9 @@ +--- +- name: Ensure MariaDB databases are present (or absent) + mysql_db: + name: "{{ item.name }}" + collation: "{{ item.collation | default('utf8_general_ci') }}" + encoding: "{{ item.encoding | default('utf8') }}" + state: "{{ item.state | default('present') }}" + login_unix_socket: "{{ mariadb_unix_socket | default ('/run/mysqld/mysqld.sock') }}" + with_items: "{{ mariadb_databases }}" diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..9ca9cf3 --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,32 @@ +--- +- name: include task setup.yml + include_tasks: setup.yml + +- name: include task configure.yml + include_tasks: configure.yml + +- name: include task databases.yml + include_tasks: databases.yml + when: + - mariadb_databases is defined + - mariadb_replication_role != "slave" + +- name: include task users.yml + include_tasks: users.yml + when: + - mariadb_users is defined + - mariadb_replication_role != "slave" + +- name: include task replication_master.yml + include_tasks: replication_master.yml + when: mariadb_replication_role == "master" + +- name: include task replication_slave.yml + include_tasks: replication_slave.yml + when: + - not ansible_check_mode + - mariadb_replication_role == "slave" + +- name: include task backup.yml + include_tasks: backup.yml + when: mariadb_backup_db diff --git a/tasks/replication_master.yml b/tasks/replication_master.yml new file mode 100644 index 0000000..2fb3ee9 --- /dev/null +++ b/tasks/replication_master.yml @@ -0,0 +1,12 @@ +--- +- name: Ensure replication user exists on master + mysql_user: + name: "{{ item.name }}" + host: "{{ item.host | default('%') }}" + password: "{{ item.password }}" + priv: "{{ item.priv | default('*.*:REPLICATION SLAVE') }}" + state: present + login_unix_socket: "{{ mariadb_unix_socket }}" + with_items: "{{ mariadb_replication_user }}" + when: not ansible_check_mode + no_log: true diff --git a/tasks/replication_slave.yml b/tasks/replication_slave.yml new file mode 100644 index 0000000..6c9ad0d --- /dev/null +++ b/tasks/replication_slave.yml @@ -0,0 +1,54 @@ +--- +- name: Check slave replication status + mysql_replication: + mode: getslave + login_unix_socket: "{{ mariadb_unix_socket }}" + register: slave + no_log: true + +# For the moment, we have to use a sql command. +# In ansible 2.10, we should be able to use mysql_replication module. +# See https://github.com/ansible/ansible/pull/62648 (and below) +- name: Configure replication on the slave + command: | + /usr/bin/mariadb -e "CHANGE MASTER TO master_host='{{ mariadb_replication_master_ip }}', + master_user='{{ item.name }}', master_password='{{ item.password }}', master_use_gtid=slave_pos" + with_items: "{{ mariadb_replication_user }}" + when: + - not slave.Is_Slave + no_log: true + +# # Following (not tested) should work on ansible 2.10 +# - name: Configure replication on the slave +# mysql_replication: +# mode: changemaster +# master_host: "{{ mariadb_replication_master_ip }}" +# master_user: "{{ item.name }}" +# master_password: "{{ item.password }}" +# master_use_gtid: "{{ mariadb_replication_gtid | default('slave_pos') }} +# login_unix_socket: "{{ mariadb_unix_socket }}" +# with_items: "{{ mariadb_replication_user }}" +# when: +# - not slave.Is_Slave +# no_log: true + +- name: Reset slave replication + mysql_replication: + mode: resetslave + login_unix_socket: "{{ mariadb_unix_socket }}" + when: + - not slave.Is_Slave + +- name: Check slave replication status (second time) + mysql_replication: + mode: getslave + login_unix_socket: "{{ mariadb_unix_socket }}" + register: slave2 + no_log: true + +- name: Start slave replication + mysql_replication: + mode: startslave + login_unix_socket: "{{ mariadb_unix_socket }}" + when: + - slave2.Slave_IO_Running == "No" diff --git a/tasks/setup.yml b/tasks/setup.yml new file mode 100644 index 0000000..daa6b8d --- /dev/null +++ b/tasks/setup.yml @@ -0,0 +1,36 @@ +--- +- name: Install mariadb repo necessary packages + apt: + package: + - software-properties-common + - dirmngr + state: present + when: mariadb_use_official_repo + +- name: Install mariadb repository key + apt_key: + keyserver: keyserver.ubuntu.com + id: "0xF1656F24C74CD1D8" + when: mariadb_use_official_repo + +- name: Setup mariadb repository sourcelist entry + apt_repository: + repo: deb {{ mariadb_use_official_repo_url }}/{{ mariadb_use_official_repo_version }}/debian {{ ansible_distribution_release }} main + state: present + when: mariadb_use_official_repo + +- name: Install mariadb + apt: + package: + - mariadb-server + state: present + update_cache: true + +- name: Determine required MariaDB Python libraries + set_fact: + deb_mariadb_python_package: "{% if 'python3' in ansible_python_interpreter|default('') %}python3-pymysql{% else %}python-pymysql{% endif %}" + +- name: Install python mariadb driver + apt: + name: "{{ deb_mariadb_python_package }}" + state: present diff --git a/tasks/users.yml b/tasks/users.yml new file mode 100644 index 0000000..2acc735 --- /dev/null +++ b/tasks/users.yml @@ -0,0 +1,13 @@ +--- +- name: Ensure MariaDB users are present (or absent) + mysql_user: + name: "{{ item.name }}" + host: "{{ item.host | default('localhost') }}" + password: "{{ item.password }}" + priv: "{{ item.priv | default('*.*:USAGE') }}" + state: "{{ item.state | default('present') }}" + append_privs: "{{ item.append_privs | default('no') }}" + encrypted: "{{ item.encrypted | default('no') }}" + login_unix_socket: "{{ mariadb_unix_socket }}" + with_items: "{{ mariadb_users }}" + no_log: true diff --git a/templates/my.cnf.j2 b/templates/my.cnf.j2 new file mode 100644 index 0000000..94cd4c4 --- /dev/null +++ b/templates/my.cnf.j2 @@ -0,0 +1,49 @@ +# {{ ansible_managed }} + +[mariadb] + +# Basic settings +{{ mariadb_basic_settings_raw }} +# Fine tuning +{{ mariadb_fine_tuning_raw }} +# Logging +{{ mariadb_logging_raw }} +# Query cache +{{ mariadb_query_cache_raw }} +# Character sets +{{ mariadb_character_sets_raw }} +# InnoDB +{{ mariadb_innodb_raw }} + +{% if mariadb_replication_role != '' %} +# Replication +server-id = {{ mariadb_server_id }} +log-basename = mariadb + +{% if mariadb_replication_role == 'master' %} +log_bin +expire_logs_days = {{ mariadb_expire_logs_days }} +max_binlog_size = {{ mariadb_max_binlog_size }} +binlog_format = {{mariadb_binlog_format}} +# the following permits to simplify the process of moving a slave to a master +# role by ensuring that replication is not started on master +skip-slave-start + +{% for db in mariadb_databases %} +{% if db.replicate|default(false) %} +binlog_do_db = {{ db.name }} +{% else %} +binlog_ignore_db = {{ db.name }} +{% endif %} +{% endfor %} +{% endif %} + +{% if mariadb_replication_role == 'slave' %} +read_only +relay-log = relay-bin +relay-log-index = relay-bin.index +{% endif %} +{% endif -%} + +[mysqldump] +{{ mariadb_mysqldump_raw }}