diff --git a/mysql/README.md b/mysql/README.md index ff4fcbe9..bb87921e 100644 --- a/mysql/README.md +++ b/mysql/README.md @@ -15,11 +15,13 @@ Tasks are extracted in several files, included in `tasks/main.yml` : * `munin.yml` : Munin plugins ; * `log2mail.yml` : log2mail patterns ; * `utils.yml` : useful tools. +* `replication.yml`: install and configure prerequisites for mysql replication, do not forget to set `mysql_bind_address`, `mysql_server_id` and `mysql_log_bin` ## Available variables * `mysql_variant` : install Oracle's MySQL or MariaDB (default: `oracle`) [Debian 8 only]; * `mysql_replace_root_with_mysqladmin`: switch from `root` to `mysqladmin` user or not ; +* `mysql_replication`: setup all prerequisites for replication. * `mysql_thread_cache_size`: number of threads for the cache ; * `mysql_innodb_buffer_pool_size`: amount of RAM dedicated to InnoDB ; * `mysql_bind_address` : (default: `Null`, default evolinux config is then used) ; @@ -30,8 +32,7 @@ Tasks are extracted in several files, included in `tasks/main.yml` : * `mysql_max_heap_table_size`: (default: `Null`, default evolinux config is then used) ; * `mysql_query_cache_limit`: (default: `Null`, default evolinux config is then used) ; * `mysql_query_cache_size`: (default: `Null`, default evolinux config is then used) ; -* `mysql_log_bin`: (default: `Null`, activates binlogs if used) ; -* `mysql_server_id`: (default: `Null`, MySQL version default is then used) ; +* `mysql_server_id`: (default: `Null`, only used with `mysql_replication`, default mysql server id will be used otherwise) ; * `mysql_custom_datadir`: custom datadir. * `mysql_custom_tmpdir`: custom tmpdir. * `general_alert_email`: email address to send various alert messages (default: `root@localhost`). @@ -41,5 +42,9 @@ Tasks are extracted in several files, included in `tasks/main.yml` : * `mysql_force_new_nrpe_password` : change the password for NRPE even if it exists already (default: `False`). * `mysql_install_libclient`: install mysql client libraries (default: `False`). * `mysql_restart_if_needed` : should the restart handler be executed (default: `True`) +* `mysql_log_bin`: (default: `Null`, activates binlogs if used with `mysql_replication`) ; +* `mysql_repl_password`: Password hash for replication user, only creates a user if set. +## Notes +Changing the _datadir_ location can be done multiple times, as long as it is not restored to the default initial location, (because a symlink is created and can't be switched back, yet). -NB : changing the _datadir_ location can be done multiple times, as long as it is not restored to the default initial location, (because a symlink is created and can't be switched back, yet). +When using replication, note that the connections from the client server on the haproxy 8306 and mysql 3306 ports need to be open and the sql servers need to communicate on port 3306. diff --git a/mysql/defaults/main.yml b/mysql/defaults/main.yml index 633619cf..f364de18 100644 --- a/mysql/defaults/main.yml +++ b/mysql/defaults/main.yml @@ -21,7 +21,6 @@ mysql_innodb_buffer_pool_size: '{{ (ansible_memtotal_mb * 0.3) | int }}M' # If these variables are changed to non-Null values, # they will be added in the zzz-evolinux-custom.cnf file. # Otherwise, the value from de the z-evolinux-defaults.cnf file will preveil. -mysql_bind_address: Null mysql_max_connections: Null mysql_max_connect_errors: Null mysql_table_cache: Null @@ -29,8 +28,6 @@ mysql_tmp_table_size: Null mysql_max_heap_table_size: Null mysql_query_cache_limit: Null mysql_query_cache_size: Null -mysql_log_bin: Null -mysql_server_id: Null mysql_cron_optimize: True mysql_cron_optimize_frequency: weekly @@ -44,3 +41,10 @@ mysql_evolinux_defaults_file: z-evolinux-defaults.cnf mysql_evolinux_custom_file: zzz-evolinux-custom.cnf mysql_restart_if_needed: True + +# replication variables: +mysql_replication: false +mysql_log_bin: null +mysql_server_id: null +mysql_bind_address: null +mysql_repl_password: '' \ No newline at end of file diff --git a/mysql/files/dbadmin.sh b/mysql/files/dbadmin.sh new file mode 100644 index 00000000..f5e61ea8 --- /dev/null +++ b/mysql/files/dbadmin.sh @@ -0,0 +1,101 @@ +#!/bin/sh +# +# Manage MySQL accounts and databases. +# +# Note: in the following code: +# - account means user@host +# - user is the user part of account +# + +MYSQL_OPTS="--raw --skip-column-names --skip-line-numbers" + +usage() { + cat <&2 +Usage: $0 [] + +Available commands are: + + list [] + List all accounts and their databases, separated by semi-colon. If user + is specified, list databases for this user only. + + passwd + Change password for specified user. + +EOT +} + +error() { + printf >&2 "Error: $@\n" +} + +get_host() { + user="$1" + host=$(mysql $MYSQL_OPTS --execute "SELECT host FROM mysql.user WHERE user='$user'") + if [ $(echo "$host" |wc -l) -gt 1 ]; then + # TODO: Not perfect! + echo "$host" |grep '%' + else + echo $host + fi +} + +get_dbs() { + account="$1" + echo "$(mysql $MYSQL_OPTS --execute "SHOW GRANTS FOR $account" |perl -ne 'print "$1 " if (/^GRANT (?!USAGE).* ON `(.*)`/)')" +} + +get_accounts() { + echo "$(mysql $MYSQL_OPTS --execute "SELECT user,host FROM mysql.user;" |perl -ne 'print "$1\@$2\n" if (/^([^\s]+)\s+([^\s]+)$/)'|sed "s/^/'/; s/@/'@'/; s/$/'/;")" +} + +list() { + if [ $# -gt 0 ]; then + user="$1" + host=$(get_host $user) + account="'$user'@'$host'" + echo $account:$(get_dbs "$account") + else + for account in $(get_accounts); do + echo $account:$(get_dbs "$account") + done + fi +} + +passwd() { + if [ $# -ne 2 ]; then + usage + exit 1 + fi + + user="$1" + password="$2" + host=$(get_host $user) + + mysql -e "SET PASSWORD FOR '$user'@'$host' = PASSWORD('$password');" +} + + +# +# Argument processing. +# + +if [ $# -lt 1 ]; then + usage + exit 1 +fi + +command="$1" +shift + +case "$command" in + list) + list $@ + ;; + passwd) + passwd $@ + ;; + *) + error "Unknown command: $command." + ;; +esac diff --git a/mysql/files/xinetd/mysqlchk b/mysql/files/xinetd/mysqlchk new file mode 100644 index 00000000..d7c12935 --- /dev/null +++ b/mysql/files/xinetd/mysqlchk @@ -0,0 +1,13 @@ +# Ansible managed +service mysqlchk +{ + socket_type = stream + port = 8306 + protocol = tcp + wait = no + type = UNLISTED + user = root + server = /usr/share/scripts/mysqlchk.sh + log_on_failure += USERID + disable = no +} diff --git a/mysql/files/xinetd/mysqlchk.sh b/mysql/files/xinetd/mysqlchk.sh new file mode 100644 index 00000000..7b5860d2 --- /dev/null +++ b/mysql/files/xinetd/mysqlchk.sh @@ -0,0 +1,54 @@ +#!/bin/sh + +# Ansible managed +# +# http://sysbible.org/x/2008/12/04/having-haproxy-check-mysql-status-through-a-xinetd-script/ +# +# This script checks if a mysql server is healthy running on localhost. It will +# return: +# +# "HTTP/1.x 200 OK\r" (if mysql is running smoothly) +# +# - OR - +# +# "HTTP/1.x 500 Internal Server Error\r" (else) +# +# The purpose of this script is make haproxy capable of monitoring mysql properly +# +# Author: Unai Rodriguez +# +# It is recommended that a low-privileged-mysql user is created to be used by +# this script. Something like this: +# +# mysql> GRANT SELECT on mysql.* TO 'mysqlchkusr'@'localhost' \ +# -> IDENTIFIED BY '257retfg2uysg218' WITH GRANT OPTION; +# mysql> flush privileges; + +TMP_FILE="/tmp/mysqlchk.out" +ERR_FILE="/tmp/mysqlchk.err" + +# +# We perform a simple query that should return a few results :-p +# +/usr/bin/mysql --defaults-file=/etc/mysql/debian.cnf -e "show databases;" > $TMP_FILE 2> $ERR_FILE + +# +# Check the output. If it is not empty then everything is fine and we return +# something. Else, we just do not return anything. +# + +if [ "$(/bin/cat $TMP_FILE)" != "" ]; then + # mysql is fine, return http 200 + /bin/echo -e "HTTP/1.1 200 OK\r\n" + /bin/echo -e "Content-Type: Content-Type: text/plain\r\n" + /bin/echo -e "\r\n" + /bin/echo -e "MySQL is running.\r\n" + /bin/echo -e "\r\n" +else + # mysql is fine, return http 503 + /bin/echo -e "HTTP/1.1 503 Service Unavailable\r\n" + /bin/echo -e "Content-Type: Content-Type: text/plain\r\n" + /bin/echo -e "\r\n" + /bin/echo -e "MySQL is *down*.\r\n" + /bin/echo -e "\r\n" +fi diff --git a/mysql/handlers/main.yml b/mysql/handlers/main.yml index 2ea13151..50755f30 100644 --- a/mysql/handlers/main.yml +++ b/mysql/handlers/main.yml @@ -23,3 +23,8 @@ systemd: name: mysql daemon_reload: yes + +- name: 'restart xinetd' + service: + name: 'xinetd' + state: 'restart' diff --git a/mysql/tasks/main.yml b/mysql/tasks/main.yml index 89ee6866..11435c73 100644 --- a/mysql/tasks/main.yml +++ b/mysql/tasks/main.yml @@ -22,6 +22,9 @@ - include: config_jessie.yml when: ansible_distribution_release == "jessie" +- include: replication.yml + when: mysql_replication + - include: datadir.yml - include: logdir.yml diff --git a/mysql/tasks/replication.yml b/mysql/tasks/replication.yml new file mode 100644 index 00000000..6e5ee039 --- /dev/null +++ b/mysql/tasks/replication.yml @@ -0,0 +1,53 @@ +--- + +- name: 'Copy MySQL configuration for replication' + template: + src: 'replication.cnf.j2' + dest: "{{ mysql_config_directory }}/zzzz-replication.cnf" + with_first_found: + - "templates/mysql/replication.{{ inventory_hostname }}.cnf.j2" + - "templates/mysql/replication.{{ host_group }}.cnf.j2" + - 'templates/mysql/replication.cnf.j2' + - 'replication.cnf.j2' + notify: 'restart mysql' + +- name: 'Create repl user' + mysql_user: + name: 'repl' + host: '%' + encrypted: true + password: "{{ mysql_repl_password }}" + priv: '*.*:REPLICATION SLAVE,REPLICATION CLIENT' + update_password: 'on_create' + state: 'present' + register: create_repl_user + when: mysql_repl_password | length > 0 + +- name: 'Add Nagios check for replication' + template: + src: 'replication_check.cfg.j2' + dest: '/etc/nagios/nrpe.d/replication.cfg' + notify: 'restart nagios-nrpe-server' + +- name: 'Install xinetd' + apt: + name: 'xinetd' + +- name: 'Add xinetd configuration for MySQL HAProxy check' + copy: + src: 'xinetd/mysqlchk' + dest: '/etc/xinetd.d/' + mode: '0644' + notify: 'restart xinetd' + +- name: 'Copy mysqlchk script' + copy: + src: 'xinetd/mysqlchk.sh' + dest: '/usr/share/scripts/' + mode: '0755' + +- name: 'Copy dbadmin script' + copy: + src: 'dbadmin.sh' + dest: '/usr/share/scripts/' + mode: '0755' diff --git a/mysql/templates/evolinux-custom.cnf.j2 b/mysql/templates/evolinux-custom.cnf.j2 index f8ee104e..fd50fb36 100644 --- a/mysql/templates/evolinux-custom.cnf.j2 +++ b/mysql/templates/evolinux-custom.cnf.j2 @@ -29,9 +29,4 @@ query_cache_limit = {{ mysql_query_cache_limit }} {% if mysql_query_cache_limit %} query_cache_size = {{ mysql_query_cache_size }} {% endif %} -{% if mysql_log_bin %} -log_bin = {{ mysql_log_bin }} -{% endif %} -{% if mysql_server_id %} -server_id = {{ mysql_server_id }} -{% endif %} + diff --git a/mysql/templates/replication.cnf.j2 b/mysql/templates/replication.cnf.j2 new file mode 100644 index 00000000..f6da45d9 --- /dev/null +++ b/mysql/templates/replication.cnf.j2 @@ -0,0 +1,7 @@ +# {{ansible_managed}} + +[mysqld] +{% if mysql_log_bin %} +log_bin = {{ mysql_log_bin }} +{% endif %} +server_id = {{ mysql_server_id }} diff --git a/mysql/templates/replication_check.cfg.j2 b/mysql/templates/replication_check.cfg.j2 new file mode 100644 index 00000000..76135811 --- /dev/null +++ b/mysql/templates/replication_check.cfg.j2 @@ -0,0 +1,3 @@ +# ansible managed + +command[check_mysql_slave]=/usr/lib/nagios/plugins/check_mysql --check-slave -H localhost -f ~nagios/.my.cnf -w 1800 -c 3600