From 1bc7b80fbec9af20ea3014aa024f6ec2a7bee9ba Mon Sep 17 00:00:00 2001 From: Tymoteusz Blazejczyk Date: Fri, 21 May 2021 13:20:34 +0200 Subject: [PATCH] It fixes #28 Support Galera Replication This patch add support for Galera replication. Features: - it detects if Galera replication was enabled using `mysql` configuration files or provided `mysqld` command line arguments - on default it enables cluster auto bootstrap feature - on default the first cluster node is used for cluster auto bootstrapping based on the wsrep_cluster_address parameter from `mysql` configuration files, `mysqld` command line arguments or by setting the `WSREP_CLUSTER_ADDRESS` environment variable - cluster auto bootstrap feature can be disabled by setting the `WSREP_SKIP_AUTO_BOOTSTRAP` environment variable - use the `WSREP_AUTO_BOOTSTRAP_ADDRESS` environment variable to explicitly choice other node for cluster bootstrapping - cluster node hostnames or IP addresses must be valid to enable cluster auto bootstrapping How to use it. 1. Prepare `mysql` configuration file `galera.cnf`: ```plaintext [galera] wsrep_on = ON wsrep_sst_method = rsync wsrep_provider = /usr/lib/libgalera_smm.so bind-address = 0.0.0.0 binlog_format = row default_storage_engine = InnoDB innodb_doublewrite = 1 innodb_autoinc_lock_mode = 2 innodb_flush_log_at_trx_commit = 2 ``` 2. Make it read-only: ```plaintext chmod 444 galera.cnf ``` 3. Prepare Docker Compose file `docker-compose.yml`: ```yaml services: node: image: mariadb restart: always security_opt: - label=disable environment: WSREP_CLUSTER_ADDRESS: "${WSREP_CLUSTER_ADDRESS:-}" MYSQL_ROOT_PASSWORD: example volumes: - ./galera.cnf:/etc/mysql/conf.d/10-galera.cnf:ro command: - --wsrep-cluster-address=gcomm://db_node_1,db_node_2,db_node_3 deploy: replicas: 3 ``` 4. Start Docker Compose: ```plaintext docker-compose --project-name db up ``` To start N MariaDB instances using environment variable: ```plaintext WSREP_CLUSTER_ADDRESS="gcomm://db_node_1,db_node_2,db_node_3,db_node_4,db_node_5" docker-compose --project-name db up --scale node="$(echo "${WSREP_CLUSTER_ADDRESS}" | tr ',' ' ' | wc -w)" ``` To start N MariaDB instances using `mysql` configuration file: ```plaintext docker-compose --project-name db up --scale node="$(grep -i wsrep_cluster_address .cnf | tr -d ' ' | tr ',' ' ' | wc -w)" ``` --- 10.2/docker-entrypoint.sh | 71 +++++++++++++++++++++++++++++++++++++++ 10.3/docker-entrypoint.sh | 71 +++++++++++++++++++++++++++++++++++++++ 10.4/docker-entrypoint.sh | 71 +++++++++++++++++++++++++++++++++++++++ 10.5/docker-entrypoint.sh | 71 +++++++++++++++++++++++++++++++++++++++ 10.6/docker-entrypoint.sh | 71 +++++++++++++++++++++++++++++++++++++++ docker-entrypoint.sh | 71 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 426 insertions(+) diff --git a/10.2/docker-entrypoint.sh b/10.2/docker-entrypoint.sh index ae837bf1..25df6f3c 100755 --- a/10.2/docker-entrypoint.sh +++ b/10.2/docker-entrypoint.sh @@ -329,6 +329,57 @@ docker_setup_db() { fi } +# usage: docker_hostname_match +# ie: docker_hostname_match node1.cluster.local +# it returns true if provided hostname match with container hostname. Otherwise it returns false +docker_hostname_match() { + for hostname in $(hostname --all-fqdns); do + if [ "$hostname" = "$1" ]; then + return 0 + fi + done + + return 1 +} + +# usage: docker_ip_match +# ie: docker_ip_match 192.168.1.13 +# it returns true if provided IP address match with container IP address. Otherwise it returns false +docker_ip_match() { + for ip in $(hostname --all-ip-addresses); do + if [ "$ip" = "$1" ]; then + return 0 + fi + done + + return 1 +} + +# usage: docker_address_match +# ie: docker_address_match node1 +# it returns true if provided value match with container IP address or container hostname. Otherwise it returns false +docker_address_match() { + local resolved="$(resolveip --silent "$1" 2>/dev/null)" # it converts hostname to ip or vice versa + + docker_ip_match "$resolved" || docker_ip_match "$1" || docker_hostname_match "$resolved" || docker_hostname_match "$1" +} + +# usage: wsrep_enable_new_cluster [arg [arg [...]]] +# ie: wsrep_enable_new_cluster gcomm://node1,node2,node3 "$@" +# it returns true if we need to set the --wsrep-new-cluster argument for the mysqld. Otherwise it returns false +wsrep_enable_new_cluster() { + local address="${WSREP_AUTO_BOOTSTRAP_ADDRESS:-$1}"; shift + local wsrepdir="$(mysql_get_config 'wsrep-data-home-dir' "$@")" + + # it removes URI schemes like gcomm:// + address="${address#[[:graph:]]*://}" + + # it replaces commas ',' with spaces ' ' and converts it to array + address=( ${address//,/ } ) + + [ -n "$address" ] && [ -z "$WSREP_SKIP_AUTO_BOOTSTRAP" ] && [ ! -s "$wsrepdir/gvwstate.dat" ] && docker_address_match "${address[0]}" +} + # check arguments for an option that would cause mysqld to stop # return true if there is one _mysql_want_help() { @@ -388,6 +439,26 @@ _main() { mysql_note "MariaDB init process done. Ready for start up." echo fi + + # check if Galera replication is enabled from configuration files or command line arguments + if [ "$(mysql_get_config wsrep-on "$@")" = "TRUE" ]; then + mysql_note "Galera replication is enabled" + + # determine cluster nodes addresses + if [ -z "${WSREP_CLUSTER_ADDRESS}" ]; then + WSREP_CLUSTER_ADDRESS="$(mysql_get_config wsrep-cluster-address "$@")" + else + set -- "$@" --wsrep-cluster-address="${WSREP_CLUSTER_ADDRESS}" + fi + + mysql_note "Galera cluster addresses ${WSREP_CLUSTER_ADDRESS}" + + # determine if this node is used for cluster bootstrapping. Skip it when cluster was already bootstrapped + if wsrep_enable_new_cluster "${WSREP_CLUSTER_ADDRESS}" "$@"; then + mysql_note "Enabled Galera cluster bootstrapping for this node" + set -- "$@" --wsrep-new-cluster + fi + fi fi exec "$@" } diff --git a/10.3/docker-entrypoint.sh b/10.3/docker-entrypoint.sh index ae837bf1..25df6f3c 100755 --- a/10.3/docker-entrypoint.sh +++ b/10.3/docker-entrypoint.sh @@ -329,6 +329,57 @@ docker_setup_db() { fi } +# usage: docker_hostname_match +# ie: docker_hostname_match node1.cluster.local +# it returns true if provided hostname match with container hostname. Otherwise it returns false +docker_hostname_match() { + for hostname in $(hostname --all-fqdns); do + if [ "$hostname" = "$1" ]; then + return 0 + fi + done + + return 1 +} + +# usage: docker_ip_match +# ie: docker_ip_match 192.168.1.13 +# it returns true if provided IP address match with container IP address. Otherwise it returns false +docker_ip_match() { + for ip in $(hostname --all-ip-addresses); do + if [ "$ip" = "$1" ]; then + return 0 + fi + done + + return 1 +} + +# usage: docker_address_match +# ie: docker_address_match node1 +# it returns true if provided value match with container IP address or container hostname. Otherwise it returns false +docker_address_match() { + local resolved="$(resolveip --silent "$1" 2>/dev/null)" # it converts hostname to ip or vice versa + + docker_ip_match "$resolved" || docker_ip_match "$1" || docker_hostname_match "$resolved" || docker_hostname_match "$1" +} + +# usage: wsrep_enable_new_cluster [arg [arg [...]]] +# ie: wsrep_enable_new_cluster gcomm://node1,node2,node3 "$@" +# it returns true if we need to set the --wsrep-new-cluster argument for the mysqld. Otherwise it returns false +wsrep_enable_new_cluster() { + local address="${WSREP_AUTO_BOOTSTRAP_ADDRESS:-$1}"; shift + local wsrepdir="$(mysql_get_config 'wsrep-data-home-dir' "$@")" + + # it removes URI schemes like gcomm:// + address="${address#[[:graph:]]*://}" + + # it replaces commas ',' with spaces ' ' and converts it to array + address=( ${address//,/ } ) + + [ -n "$address" ] && [ -z "$WSREP_SKIP_AUTO_BOOTSTRAP" ] && [ ! -s "$wsrepdir/gvwstate.dat" ] && docker_address_match "${address[0]}" +} + # check arguments for an option that would cause mysqld to stop # return true if there is one _mysql_want_help() { @@ -388,6 +439,26 @@ _main() { mysql_note "MariaDB init process done. Ready for start up." echo fi + + # check if Galera replication is enabled from configuration files or command line arguments + if [ "$(mysql_get_config wsrep-on "$@")" = "TRUE" ]; then + mysql_note "Galera replication is enabled" + + # determine cluster nodes addresses + if [ -z "${WSREP_CLUSTER_ADDRESS}" ]; then + WSREP_CLUSTER_ADDRESS="$(mysql_get_config wsrep-cluster-address "$@")" + else + set -- "$@" --wsrep-cluster-address="${WSREP_CLUSTER_ADDRESS}" + fi + + mysql_note "Galera cluster addresses ${WSREP_CLUSTER_ADDRESS}" + + # determine if this node is used for cluster bootstrapping. Skip it when cluster was already bootstrapped + if wsrep_enable_new_cluster "${WSREP_CLUSTER_ADDRESS}" "$@"; then + mysql_note "Enabled Galera cluster bootstrapping for this node" + set -- "$@" --wsrep-new-cluster + fi + fi fi exec "$@" } diff --git a/10.4/docker-entrypoint.sh b/10.4/docker-entrypoint.sh index ae837bf1..25df6f3c 100755 --- a/10.4/docker-entrypoint.sh +++ b/10.4/docker-entrypoint.sh @@ -329,6 +329,57 @@ docker_setup_db() { fi } +# usage: docker_hostname_match +# ie: docker_hostname_match node1.cluster.local +# it returns true if provided hostname match with container hostname. Otherwise it returns false +docker_hostname_match() { + for hostname in $(hostname --all-fqdns); do + if [ "$hostname" = "$1" ]; then + return 0 + fi + done + + return 1 +} + +# usage: docker_ip_match +# ie: docker_ip_match 192.168.1.13 +# it returns true if provided IP address match with container IP address. Otherwise it returns false +docker_ip_match() { + for ip in $(hostname --all-ip-addresses); do + if [ "$ip" = "$1" ]; then + return 0 + fi + done + + return 1 +} + +# usage: docker_address_match +# ie: docker_address_match node1 +# it returns true if provided value match with container IP address or container hostname. Otherwise it returns false +docker_address_match() { + local resolved="$(resolveip --silent "$1" 2>/dev/null)" # it converts hostname to ip or vice versa + + docker_ip_match "$resolved" || docker_ip_match "$1" || docker_hostname_match "$resolved" || docker_hostname_match "$1" +} + +# usage: wsrep_enable_new_cluster [arg [arg [...]]] +# ie: wsrep_enable_new_cluster gcomm://node1,node2,node3 "$@" +# it returns true if we need to set the --wsrep-new-cluster argument for the mysqld. Otherwise it returns false +wsrep_enable_new_cluster() { + local address="${WSREP_AUTO_BOOTSTRAP_ADDRESS:-$1}"; shift + local wsrepdir="$(mysql_get_config 'wsrep-data-home-dir' "$@")" + + # it removes URI schemes like gcomm:// + address="${address#[[:graph:]]*://}" + + # it replaces commas ',' with spaces ' ' and converts it to array + address=( ${address//,/ } ) + + [ -n "$address" ] && [ -z "$WSREP_SKIP_AUTO_BOOTSTRAP" ] && [ ! -s "$wsrepdir/gvwstate.dat" ] && docker_address_match "${address[0]}" +} + # check arguments for an option that would cause mysqld to stop # return true if there is one _mysql_want_help() { @@ -388,6 +439,26 @@ _main() { mysql_note "MariaDB init process done. Ready for start up." echo fi + + # check if Galera replication is enabled from configuration files or command line arguments + if [ "$(mysql_get_config wsrep-on "$@")" = "TRUE" ]; then + mysql_note "Galera replication is enabled" + + # determine cluster nodes addresses + if [ -z "${WSREP_CLUSTER_ADDRESS}" ]; then + WSREP_CLUSTER_ADDRESS="$(mysql_get_config wsrep-cluster-address "$@")" + else + set -- "$@" --wsrep-cluster-address="${WSREP_CLUSTER_ADDRESS}" + fi + + mysql_note "Galera cluster addresses ${WSREP_CLUSTER_ADDRESS}" + + # determine if this node is used for cluster bootstrapping. Skip it when cluster was already bootstrapped + if wsrep_enable_new_cluster "${WSREP_CLUSTER_ADDRESS}" "$@"; then + mysql_note "Enabled Galera cluster bootstrapping for this node" + set -- "$@" --wsrep-new-cluster + fi + fi fi exec "$@" } diff --git a/10.5/docker-entrypoint.sh b/10.5/docker-entrypoint.sh index ae837bf1..25df6f3c 100755 --- a/10.5/docker-entrypoint.sh +++ b/10.5/docker-entrypoint.sh @@ -329,6 +329,57 @@ docker_setup_db() { fi } +# usage: docker_hostname_match +# ie: docker_hostname_match node1.cluster.local +# it returns true if provided hostname match with container hostname. Otherwise it returns false +docker_hostname_match() { + for hostname in $(hostname --all-fqdns); do + if [ "$hostname" = "$1" ]; then + return 0 + fi + done + + return 1 +} + +# usage: docker_ip_match +# ie: docker_ip_match 192.168.1.13 +# it returns true if provided IP address match with container IP address. Otherwise it returns false +docker_ip_match() { + for ip in $(hostname --all-ip-addresses); do + if [ "$ip" = "$1" ]; then + return 0 + fi + done + + return 1 +} + +# usage: docker_address_match +# ie: docker_address_match node1 +# it returns true if provided value match with container IP address or container hostname. Otherwise it returns false +docker_address_match() { + local resolved="$(resolveip --silent "$1" 2>/dev/null)" # it converts hostname to ip or vice versa + + docker_ip_match "$resolved" || docker_ip_match "$1" || docker_hostname_match "$resolved" || docker_hostname_match "$1" +} + +# usage: wsrep_enable_new_cluster [arg [arg [...]]] +# ie: wsrep_enable_new_cluster gcomm://node1,node2,node3 "$@" +# it returns true if we need to set the --wsrep-new-cluster argument for the mysqld. Otherwise it returns false +wsrep_enable_new_cluster() { + local address="${WSREP_AUTO_BOOTSTRAP_ADDRESS:-$1}"; shift + local wsrepdir="$(mysql_get_config 'wsrep-data-home-dir' "$@")" + + # it removes URI schemes like gcomm:// + address="${address#[[:graph:]]*://}" + + # it replaces commas ',' with spaces ' ' and converts it to array + address=( ${address//,/ } ) + + [ -n "$address" ] && [ -z "$WSREP_SKIP_AUTO_BOOTSTRAP" ] && [ ! -s "$wsrepdir/gvwstate.dat" ] && docker_address_match "${address[0]}" +} + # check arguments for an option that would cause mysqld to stop # return true if there is one _mysql_want_help() { @@ -388,6 +439,26 @@ _main() { mysql_note "MariaDB init process done. Ready for start up." echo fi + + # check if Galera replication is enabled from configuration files or command line arguments + if [ "$(mysql_get_config wsrep-on "$@")" = "TRUE" ]; then + mysql_note "Galera replication is enabled" + + # determine cluster nodes addresses + if [ -z "${WSREP_CLUSTER_ADDRESS}" ]; then + WSREP_CLUSTER_ADDRESS="$(mysql_get_config wsrep-cluster-address "$@")" + else + set -- "$@" --wsrep-cluster-address="${WSREP_CLUSTER_ADDRESS}" + fi + + mysql_note "Galera cluster addresses ${WSREP_CLUSTER_ADDRESS}" + + # determine if this node is used for cluster bootstrapping. Skip it when cluster was already bootstrapped + if wsrep_enable_new_cluster "${WSREP_CLUSTER_ADDRESS}" "$@"; then + mysql_note "Enabled Galera cluster bootstrapping for this node" + set -- "$@" --wsrep-new-cluster + fi + fi fi exec "$@" } diff --git a/10.6/docker-entrypoint.sh b/10.6/docker-entrypoint.sh index 0cb71cd1..12b1e4ee 100755 --- a/10.6/docker-entrypoint.sh +++ b/10.6/docker-entrypoint.sh @@ -331,6 +331,57 @@ docker_setup_db() { fi } +# usage: docker_hostname_match +# ie: docker_hostname_match node1.cluster.local +# it returns true if provided hostname match with container hostname. Otherwise it returns false +docker_hostname_match() { + for hostname in $(hostname --all-fqdns); do + if [ "$hostname" = "$1" ]; then + return 0 + fi + done + + return 1 +} + +# usage: docker_ip_match +# ie: docker_ip_match 192.168.1.13 +# it returns true if provided IP address match with container IP address. Otherwise it returns false +docker_ip_match() { + for ip in $(hostname --all-ip-addresses); do + if [ "$ip" = "$1" ]; then + return 0 + fi + done + + return 1 +} + +# usage: docker_address_match +# ie: docker_address_match node1 +# it returns true if provided value match with container IP address or container hostname. Otherwise it returns false +docker_address_match() { + local resolved="$(resolveip --silent "$1" 2>/dev/null)" # it converts hostname to ip or vice versa + + docker_ip_match "$resolved" || docker_ip_match "$1" || docker_hostname_match "$resolved" || docker_hostname_match "$1" +} + +# usage: wsrep_enable_new_cluster [arg [arg [...]]] +# ie: wsrep_enable_new_cluster gcomm://node1,node2,node3 "$@" +# it returns true if we need to set the --wsrep-new-cluster argument for the mysqld. Otherwise it returns false +wsrep_enable_new_cluster() { + local address="${WSREP_AUTO_BOOTSTRAP_ADDRESS:-$1}"; shift + local wsrepdir="$(mysql_get_config 'wsrep-data-home-dir' "$@")" + + # it removes URI schemes like gcomm:// + address="${address#[[:graph:]]*://}" + + # it replaces commas ',' with spaces ' ' and converts it to array + address=( ${address//,/ } ) + + [ -n "$address" ] && [ -z "$WSREP_SKIP_AUTO_BOOTSTRAP" ] && [ ! -s "$wsrepdir/gvwstate.dat" ] && docker_address_match "${address[0]}" +} + # check arguments for an option that would cause mysqld to stop # return true if there is one _mysql_want_help() { @@ -390,6 +441,26 @@ _main() { mysql_note "MariaDB init process done. Ready for start up." echo fi + + # check if Galera replication is enabled from configuration files or command line arguments + if [ "$(mysql_get_config wsrep-on "$@")" = "TRUE" ]; then + mysql_note "Galera replication is enabled" + + # determine cluster nodes addresses + if [ -z "${WSREP_CLUSTER_ADDRESS}" ]; then + WSREP_CLUSTER_ADDRESS="$(mysql_get_config wsrep-cluster-address "$@")" + else + set -- "$@" --wsrep-cluster-address="${WSREP_CLUSTER_ADDRESS}" + fi + + mysql_note "Galera cluster addresses ${WSREP_CLUSTER_ADDRESS}" + + # determine if this node is used for cluster bootstrapping. Skip it when cluster was already bootstrapped + if wsrep_enable_new_cluster "${WSREP_CLUSTER_ADDRESS}" "$@"; then + mysql_note "Enabled Galera cluster bootstrapping for this node" + set -- "$@" --wsrep-new-cluster + fi + fi fi exec "$@" } diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index ae837bf1..25df6f3c 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -329,6 +329,57 @@ docker_setup_db() { fi } +# usage: docker_hostname_match +# ie: docker_hostname_match node1.cluster.local +# it returns true if provided hostname match with container hostname. Otherwise it returns false +docker_hostname_match() { + for hostname in $(hostname --all-fqdns); do + if [ "$hostname" = "$1" ]; then + return 0 + fi + done + + return 1 +} + +# usage: docker_ip_match +# ie: docker_ip_match 192.168.1.13 +# it returns true if provided IP address match with container IP address. Otherwise it returns false +docker_ip_match() { + for ip in $(hostname --all-ip-addresses); do + if [ "$ip" = "$1" ]; then + return 0 + fi + done + + return 1 +} + +# usage: docker_address_match +# ie: docker_address_match node1 +# it returns true if provided value match with container IP address or container hostname. Otherwise it returns false +docker_address_match() { + local resolved="$(resolveip --silent "$1" 2>/dev/null)" # it converts hostname to ip or vice versa + + docker_ip_match "$resolved" || docker_ip_match "$1" || docker_hostname_match "$resolved" || docker_hostname_match "$1" +} + +# usage: wsrep_enable_new_cluster [arg [arg [...]]] +# ie: wsrep_enable_new_cluster gcomm://node1,node2,node3 "$@" +# it returns true if we need to set the --wsrep-new-cluster argument for the mysqld. Otherwise it returns false +wsrep_enable_new_cluster() { + local address="${WSREP_AUTO_BOOTSTRAP_ADDRESS:-$1}"; shift + local wsrepdir="$(mysql_get_config 'wsrep-data-home-dir' "$@")" + + # it removes URI schemes like gcomm:// + address="${address#[[:graph:]]*://}" + + # it replaces commas ',' with spaces ' ' and converts it to array + address=( ${address//,/ } ) + + [ -n "$address" ] && [ -z "$WSREP_SKIP_AUTO_BOOTSTRAP" ] && [ ! -s "$wsrepdir/gvwstate.dat" ] && docker_address_match "${address[0]}" +} + # check arguments for an option that would cause mysqld to stop # return true if there is one _mysql_want_help() { @@ -388,6 +439,26 @@ _main() { mysql_note "MariaDB init process done. Ready for start up." echo fi + + # check if Galera replication is enabled from configuration files or command line arguments + if [ "$(mysql_get_config wsrep-on "$@")" = "TRUE" ]; then + mysql_note "Galera replication is enabled" + + # determine cluster nodes addresses + if [ -z "${WSREP_CLUSTER_ADDRESS}" ]; then + WSREP_CLUSTER_ADDRESS="$(mysql_get_config wsrep-cluster-address "$@")" + else + set -- "$@" --wsrep-cluster-address="${WSREP_CLUSTER_ADDRESS}" + fi + + mysql_note "Galera cluster addresses ${WSREP_CLUSTER_ADDRESS}" + + # determine if this node is used for cluster bootstrapping. Skip it when cluster was already bootstrapped + if wsrep_enable_new_cluster "${WSREP_CLUSTER_ADDRESS}" "$@"; then + mysql_note "Enabled Galera cluster bootstrapping for this node" + set -- "$@" --wsrep-new-cluster + fi + fi fi exec "$@" }