├── shutdown.sh ├── mha_node ├── preparation │ ├── mha4mysql-node-0.56.tar.gz │ ├── sources.list │ ├── ssh-pass.sh │ └── ssh-init.sh └── Dockerfile ├── mha_manager ├── preparation │ ├── mha4mysql-node-0.56.tar.gz │ ├── mha4mysql-manager-0.56.tar.gz │ ├── sources.list │ ├── ssh-pass.sh │ ├── ssh-init.sh │ └── bootstrap.sh └── Dockerfile ├── employees_share ├── application.cnf ├── change-master.sh └── create-repl-account.sh ├── account.env ├── reset.sh ├── employees_master └── conf.d │ └── mysqld.cnf ├── docker-compose.yml ├── employees_slave_1 └── conf.d │ └── mysqld.cnf ├── start.sh ├── .gitignore ├── employees_db └── employees.sql └── README.md /shutdown.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #set -x 3 | set -eo pipefail 4 | shopt -s nullglob 5 | 6 | docker-compose down 7 | -------------------------------------------------------------------------------- /mha_node/preparation/mha4mysql-node-0.56.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prontera/docker-mysql-mha/HEAD/mha_node/preparation/mha4mysql-node-0.56.tar.gz -------------------------------------------------------------------------------- /mha_manager/preparation/mha4mysql-node-0.56.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prontera/docker-mysql-mha/HEAD/mha_manager/preparation/mha4mysql-node-0.56.tar.gz -------------------------------------------------------------------------------- /mha_manager/preparation/mha4mysql-manager-0.56.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prontera/docker-mysql-mha/HEAD/mha_manager/preparation/mha4mysql-manager-0.56.tar.gz -------------------------------------------------------------------------------- /employees_share/application.cnf: -------------------------------------------------------------------------------- 1 | [server default] 2 | manager_workdir=/var/log/masterha 3 | remote_workdir=/var/log/masterha 4 | master_binlog_dir=/var/log/mysql 5 | ping_interval=2 6 | ssh_user=root 7 | -------------------------------------------------------------------------------- /account.env: -------------------------------------------------------------------------------- 1 | # 定义MHA中MySQL的通用账户密码, root的SSH登录密码 2 | # WARN: 密码定义请遵循相关规则 3 | 4 | # MySQL root账户密码 5 | MYSQL_ROOT_PASSWORD=123456 6 | # MySQL新建的数据库 7 | MYSQL_DATABASE=employees 8 | # SSH到root账户上的密码 9 | SSH_ROOT_PASSWORD=qwert12345 10 | # MySQL repl复制账户名 11 | MYSQL_REPL_NAME=repl 12 | # MySQL repl复制账户密码 13 | MYSQL_REPL_PASSWORD=asnei931 14 | # MySQL mha用户名 15 | MYSQL_MHA_NAME=mha_user 16 | # MySQL mha账户的密码 17 | MYSQL_MHA_PASSWORD=ins924a 18 | -------------------------------------------------------------------------------- /reset.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #set -x 3 | set -eo pipefail 4 | shopt -s nullglob 5 | 6 | echo "$(tput setaf 2)" "Shutting down..." "$(tput sgr0)" 7 | docker-compose down 8 | echo "$(tput setaf 2)" "Deleting libs and logs..." "$(tput sgr0)" 9 | for dir in ./employees_*; do 10 | if [[ "$dir" =~ employees_(master|slave_.*) ]]; then 11 | read -r -p "Are u sure to delete \"$dir/log\" and \"$dir/lib\" ? (y/n): " choice 12 | if [[ "$choice" == "y" ]]; then 13 | rm -rf "./$dir/log" 14 | rm -rf "./$dir/lib" 15 | fi 16 | fi 17 | done 18 | echo "$(tput setaf 2)" "Done!" "$(tput sgr0)" 19 | -------------------------------------------------------------------------------- /mha_node/preparation/sources.list: -------------------------------------------------------------------------------- 1 | deb http://mirrors.ustc.edu.cn/debian/ jessie main contrib non-free 2 | deb-src http://mirrors.ustc.edu.cn/debian/ jessie main contrib non-free 3 | 4 | deb http://mirrors.ustc.edu.cn/debian/ jessie-updates main contrib non-free 5 | deb-src http://mirrors.ustc.edu.cn/debian/ jessie-updates main contrib non-free 6 | 7 | deb http://mirrors.ustc.edu.cn/debian/ jessie-backports main contrib non-free 8 | deb-src http://mirrors.ustc.edu.cn/debian/ jessie-backports main contrib non-free 9 | 10 | deb http://mirrors.ustc.edu.cn/debian-security/ jessie/updates main contrib non-free 11 | deb-src http://mirrors.ustc.edu.cn/debian-security/ jessie/updates main contrib non-free 12 | -------------------------------------------------------------------------------- /mha_manager/preparation/sources.list: -------------------------------------------------------------------------------- 1 | deb http://mirrors.ustc.edu.cn/debian/ jessie main contrib non-free 2 | deb-src http://mirrors.ustc.edu.cn/debian/ jessie main contrib non-free 3 | 4 | deb http://mirrors.ustc.edu.cn/debian/ jessie-updates main contrib non-free 5 | deb-src http://mirrors.ustc.edu.cn/debian/ jessie-updates main contrib non-free 6 | 7 | deb http://mirrors.ustc.edu.cn/debian/ jessie-backports main contrib non-free 8 | deb-src http://mirrors.ustc.edu.cn/debian/ jessie-backports main contrib non-free 9 | 10 | deb http://mirrors.ustc.edu.cn/debian-security/ jessie/updates main contrib non-free 11 | deb-src http://mirrors.ustc.edu.cn/debian-security/ jessie/updates main contrib non-free 12 | -------------------------------------------------------------------------------- /mha_node/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mysql:5.7.17 2 | 3 | COPY ./preparation /preparation 4 | RUN build_deps='ssh sshpass perl libdbi-perl libdbd-mysql-perl make' \ 5 | && pack='/preparation' \ 6 | && mv /etc/apt/sources.list /etc/apt/sources.list.bak \ 7 | && mv $pack/sources.list /etc/apt/sources.list \ 8 | && apt-get clean \ 9 | && apt-get update \ 10 | && apt-get -y --force-yes install $build_deps \ 11 | && apt-get purge -y --auto-remove $buil_deps \ 12 | && sed -n -i 's/UsePAM yes/UsePAM no/gp' /etc/ssh/sshd_config \ 13 | && tar -zxf $pack/mha4mysql-node-0.56.tar.gz -C /opt \ 14 | && cd /opt/mha4mysql-node-0.56 \ 15 | && perl Makefile.PL \ 16 | && make \ 17 | && make install \ 18 | && rm -rf /opt/mha4mysql-node-0.56 19 | 20 | -------------------------------------------------------------------------------- /employees_share/change-master.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # usage: 在从库上执行, 自动执行CHANGE MASTER使其形成主从链路 3 | # author: prontera@github 4 | 5 | #set -x 6 | set -eo pipefail 7 | shopt -s nullglob 8 | 9 | if [[ -z "$MYSQL_ROOT_PASSWORD" ]]; then 10 | echo "$HOSTNAME please set the environment variable MYSQL_ROOT_PASSWORD first!" 11 | exit 1 12 | fi 13 | mysql=( mysql -p"$MYSQL_ROOT_PASSWORD" ) 14 | 15 | for i in {30..0}; do 16 | if echo 'SELECT 1' | "${mysql[@]}" &> /dev/null; then 17 | break 18 | fi 19 | echo "$HOSTNAME MySQL init process in progress..." 20 | sleep 2 21 | done 22 | if [ "$i" = 0 ]; then 23 | echo >&2 "$HOSTNAME MySQL init process failed." 24 | exit 1 25 | fi 26 | 27 | "${mysql[@]}" <<-EOSQL 28 | STOP SLAVE; 29 | CHANGE MASTER TO MASTER_HOST="master", MASTER_USER="${MYSQL_REPL_NAME}", MASTER_PASSWORD="${MYSQL_REPL_PASSWORD:-}", MASTER_AUTO_POSITION=1; 30 | START SLAVE; 31 | EOSQL 32 | 33 | exit 0 34 | -------------------------------------------------------------------------------- /mha_manager/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:jessie 2 | 3 | COPY ./preparation /preparation 4 | RUN build_deps='ssh sshpass supervisor perl libdbi-perl libdbd-mysql-perl libconfig-tiny-perl liblog-dispatch-perl libparallel-forkmanager-perl make' \ 5 | && pack='/preparation' \ 6 | && mv /etc/apt/sources.list /etc/apt/sources.list.bak \ 7 | && mv $pack/sources.list /etc/apt/sources.list \ 8 | && apt-get clean \ 9 | && apt-get update \ 10 | && apt-get -y --force-yes install $build_deps \ 11 | && apt-get purge -y --auto-remove $buil_deps \ 12 | && sed -n -i 's/UsePAM yes/UsePAM no/gp' /etc/ssh/sshd_config \ 13 | && tar -zxf $pack/mha4mysql-node-0.56.tar.gz -C /opt \ 14 | && cd /opt/mha4mysql-node-0.56 \ 15 | && perl Makefile.PL \ 16 | && make \ 17 | && make install \ 18 | && tar -zxf $pack/mha4mysql-manager-0.56.tar.gz -C /opt \ 19 | && cd /opt/mha4mysql-manager-0.56 \ 20 | && perl Makefile.PL \ 21 | && make \ 22 | && make install \ 23 | && rm -rf /opt/mha4mysql-* 24 | -------------------------------------------------------------------------------- /mha_manager/preparation/ssh-pass.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # usage: 容器之间的ssh-copy-id 3 | # author: prontera@github 4 | 5 | #set -x 6 | set -eo pipefail 7 | shopt -s nullglob 8 | 9 | var_pass=SSH_ROOT_PASSWORD 10 | 11 | # 检测调用者是否有传入参数 12 | check_args_empty(){ 13 | if [[ "$#" == 0 ]]; then 14 | echo >&2 "$HOSTNAME option can not be an empty list when processing sshpass command!" 15 | fi 16 | } 17 | 18 | # 检测是否已经初始化ssh 19 | check_ssh_init(){ 20 | if [[ ! -e "$HOME/.ssh/id_rsa.pub" ]]; then 21 | echo >&2 "$HOSTNAME please generate ssh key first before processing sshpass command!" 22 | exit 1 23 | fi 24 | } 25 | 26 | # 多参函数 27 | # 迭代参数中的container id, 使用ssh-copy-id实现无密码登录 28 | ssh-copy(){ 29 | local password=${!var_pass:-} 30 | for ip do 31 | sshpass &> /dev/null -p "$password" ssh-copy-id -o StrictHostKeyChecking=no root@"$ip" \ 32 | && echo "$HOSTNAME copy ssh key to $ip successfully." || echo &>2 "$HOSTNAME fail to copy ssh key to $ip !" 33 | done 34 | } 35 | 36 | check_args_empty "$@" 37 | check_ssh_init 38 | ssh-copy "$@" 39 | exit 0 40 | -------------------------------------------------------------------------------- /mha_node/preparation/ssh-pass.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # usage: 容器之间的ssh-copy-id 3 | # author: prontera@github 4 | 5 | #set -x 6 | set -eo pipefail 7 | shopt -s nullglob 8 | 9 | var_pass=SSH_ROOT_PASSWORD 10 | 11 | # 检测调用者是否有传入参数 12 | check_args_empty(){ 13 | if [[ "$#" == 0 ]]; then 14 | echo >&2 "$HOSTNAME option can not be an empty list when processing sshpass command!" 15 | fi 16 | } 17 | 18 | # 检测是否已经初始化ssh 19 | check_ssh_init(){ 20 | if [[ ! -e "$HOME/.ssh/id_rsa.pub" ]]; then 21 | echo >&2 "$HOSTNAME please generate ssh key first before processing sshpass command!" 22 | exit 1 23 | fi 24 | } 25 | 26 | # 多参函数 27 | # 迭代参数中的container id, 使用ssh-copy-id实现无密码登录 28 | ssh-copy(){ 29 | local password=${!var_pass:-} 30 | for ip do 31 | sshpass &> /dev/null -p "$password" ssh-copy-id -o StrictHostKeyChecking=no root@"$ip" \ 32 | && echo "$HOSTNAME copy ssh key to $ip successfully." || echo &>2 "$HOSTNAME fail to copy ssh key to $ip !" 33 | done 34 | } 35 | 36 | check_args_empty "$@" 37 | check_ssh_init 38 | ssh-copy "$@" 39 | exit 0 40 | -------------------------------------------------------------------------------- /employees_share/create-repl-account.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # usage: 生成MySQL的复制账户 3 | # author: prontera@github 4 | 5 | #set -x 6 | set -eo pipefail 7 | shopt -s nullglob 8 | 9 | if [[ -z "$MYSQL_ROOT_PASSWORD" ]]; then 10 | echo "$HOSTNAME please set the environment variable MYSQL_ROOT_PASSWORD first!" 11 | exit 1 12 | fi 13 | 14 | mysql=( mysql -p"$MYSQL_ROOT_PASSWORD" ) 15 | 16 | for i in {30..0}; do 17 | if echo 'SELECT 1' | "${mysql[@]}" &> /dev/null; then 18 | break 19 | fi 20 | echo "$HOSTNAME MySQL init process in progress..." 21 | sleep 2 22 | done 23 | if [ "$i" = 0 ]; then 24 | echo >&2 "$HOSTNAME MySQL init process failed." 25 | exit 1 26 | fi 27 | 28 | "${mysql[@]}" <<-EOSQL 29 | GRANT REPLICATION SLAVE ON *.* TO "$MYSQL_REPL_NAME"@"172.%" IDENTIFIED BY "${MYSQL_REPL_PASSWORD:-}"; 30 | GRANT ALL PRIVILEGES ON *.* TO "$MYSQL_MHA_NAME"@"172.%" IDENTIFIED BY "${MYSQL_MHA_PASSWORD:-}"; 31 | GRANT REPLICATION SLAVE ON *.* TO "$MYSQL_REPL_NAME"@"192.%" IDENTIFIED BY "${MYSQL_REPL_PASSWORD:-}"; 32 | GRANT ALL PRIVILEGES ON *.* TO "$MYSQL_MHA_NAME"@"192.%" IDENTIFIED BY "${MYSQL_MHA_PASSWORD:-}"; 33 | FLUSH PRIVILEGES; 34 | EOSQL 35 | -------------------------------------------------------------------------------- /mha_node/preparation/ssh-init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # usage: 从环境变量中读取SSH_ROOT_PASSWORD的值, 用于设置容器中的root用户密码 3 | # author: prontera@github 4 | 5 | #set -x 6 | set -eo pipefail 7 | shopt -s nullglob 8 | 9 | var_pass=SSH_ROOT_PASSWORD 10 | 11 | # 无参函数 12 | # 根据环境变量SSH_ROOT_PASSWORD修改当前的root密码 13 | change_password(){ 14 | local password=${!var_pass:-} 15 | # 检测是否存在对应的环境变量 16 | if [[ ! "$password" ]]; then 17 | echo "$(tput setaf 1)" "$HOSTNAME please set the environment variable SSH_ROOT_PASSWORD first!" "$(tput sgr0)" 18 | exit 1 19 | fi 20 | # 修改密码 21 | echo "root:$password" | chpasswd && echo "$HOSTNAME change the password of root successfully." 22 | } 23 | 24 | restart_ssh(){ 25 | service ssh restart > /dev/null && echo "$HOSTNAME SSH service has been restarted." || echo "$HOSTNAME fail to restart SSH service!" 26 | } 27 | 28 | # 无参函数 29 | # 生成ssh密钥对, 如果已经存在则忽略 30 | generate_key(){ 31 | if [[ ! -e "$HOME/.ssh/id_rsa.pub" ]]; then 32 | ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa > /dev/null && echo "$HOSTNAME succeed in generating ssh key." || echo "$HOSTNAME fail to generate ssh key." 33 | fi 34 | } 35 | 36 | change_password 37 | restart_ssh 38 | generate_key 39 | exit 0 40 | -------------------------------------------------------------------------------- /mha_manager/preparation/ssh-init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # usage: 从环境变量中读取SSH_ROOT_PASSWORD的值, 用于设置容器中的root用户密码 3 | # author: prontera@github 4 | 5 | #set -x 6 | set -eo pipefail 7 | shopt -s nullglob 8 | 9 | var_pass=SSH_ROOT_PASSWORD 10 | 11 | # 无参函数 12 | # 根据环境变量SSH_ROOT_PASSWORD修改当前的root密码 13 | change_password(){ 14 | local password=${!var_pass:-} 15 | # 检测是否存在对应的环境变量 16 | if [[ ! "$password" ]]; then 17 | echo "$(tput setaf 1)" "$HOSTNAME please set the environment variable SSH_ROOT_PASSWORD first!" "$(tput sgr0)" 18 | exit 1 19 | fi 20 | # 修改密码 21 | echo "root:$password" | chpasswd && echo "$HOSTNAME change the password of root successfully." 22 | } 23 | 24 | restart_ssh(){ 25 | service ssh restart > /dev/null && echo "$HOSTNAME SSH service has been restarted." || echo "$HOSTNAME fail to restart SSH service!" 26 | } 27 | 28 | # 无参函数 29 | # 生成ssh密钥对, 如果已经存在则忽略 30 | generate_key(){ 31 | if [[ ! -e "$HOME/.ssh/id_rsa.pub" ]]; then 32 | ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa > /dev/null && echo "$HOSTNAME succeed in generating ssh key." || echo "$HOSTNAME fail to generate ssh key." 33 | fi 34 | } 35 | 36 | change_password 37 | restart_ssh 38 | generate_key 39 | exit 0 40 | -------------------------------------------------------------------------------- /employees_master/conf.d/mysqld.cnf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. 2 | # 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; version 2 of the License. 6 | # 7 | # This program is distributed in the hope that it will be useful, 8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | # GNU General Public License for more details. 11 | # 12 | # You should have received a copy of the GNU General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 15 | 16 | # 17 | # The MySQL Server configuration file. 18 | # 19 | # For explanations see 20 | # http://dev.mysql.com/doc/mysql/en/server-system-variables.html 21 | 22 | [mysql] 23 | default-character-set = utf8 24 | 25 | [mysqld] 26 | character_set_server = utf8 27 | collation_server = utf8_general_ci 28 | pid-file = /var/run/mysqld/mysqld.pid 29 | socket = /var/run/mysqld/mysqld.sock 30 | datadir = /var/lib/mysql 31 | log-error = /var/log/mysql/error.log 32 | # By default we only accept connections from localhost 33 | #bind-address = 127.0.0.1 34 | # Disabling symbolic-links is recommended to prevent assorted security risks 35 | symbolic-links=0 36 | # replication 37 | log_bin = /var/log/mysql/mysql-bin 38 | log_bin_index = /var/log/mysql/mysql-bin.index 39 | binlog_format = row 40 | server_id = 101 41 | gtid_mode = ON 42 | enforce_gtid_consistency = ON 43 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | services: 4 | mha_share: 5 | image: debian:jessie 6 | volumes: 7 | - "./employees_share/:/mha_share/" 8 | mha_node: 9 | build: ./mha_node 10 | image: prontera/mha_node 11 | read_only: true 12 | mha_manager: 13 | build: ./mha_manager 14 | image: prontera/mha_manager 15 | read_only: true 16 | master: 17 | image: prontera/mha_node 18 | depends_on: 19 | - mha_node 20 | ports: 21 | - "3406:3306" 22 | volumes: 23 | - "./employees_db/:/docker-entrypoint-initdb.d/:ro" 24 | - "./employees_master/lib/:/var/lib/mysql/" 25 | - "./employees_master/log/:/var/log/mysql/" 26 | - "./employees_master/conf.d/:/etc/mysql/mysql.conf.d/" 27 | volumes_from: 28 | - mha_share 29 | env_file: 30 | - ./account.env 31 | environment: 32 | - MYSQL_USER=prontera 33 | - MYSQL_PASSWORD=123123 34 | slave_1: 35 | image: prontera/mha_node 36 | depends_on: 37 | - master 38 | ports: 39 | - "3407:3306" 40 | volumes: 41 | - "./employees_slave_1/lib/:/var/lib/mysql/" 42 | - "./employees_slave_1/log/:/var/log/mysql/" 43 | - "./employees_slave_1/conf.d/:/etc/mysql/mysql.conf.d/" 44 | volumes_from: 45 | - mha_share 46 | env_file: 47 | - ./account.env 48 | manager: 49 | image: prontera/mha_manager 50 | depends_on: 51 | - mha_manager 52 | - slave_1 53 | volumes_from: 54 | - mha_share 55 | entrypoint: 56 | tailf /dev/null 57 | env_file: 58 | - ./account.env 59 | -------------------------------------------------------------------------------- /employees_slave_1/conf.d/mysqld.cnf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. 2 | # 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; version 2 of the License. 6 | # 7 | # This program is distributed in the hope that it will be useful, 8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | # GNU General Public License for more details. 11 | # 12 | # You should have received a copy of the GNU General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 15 | 16 | # 17 | # The MySQL Server configuration file. 18 | # 19 | # For explanations see 20 | # http://dev.mysql.com/doc/mysql/en/server-system-variables.html 21 | 22 | [mysql] 23 | default-character-set = utf8 24 | 25 | [mysqld] 26 | character_set_server = utf8 27 | collation_server = utf8_general_ci 28 | pid-file = /var/run/mysqld/mysqld.pid 29 | socket = /var/run/mysqld/mysqld.sock 30 | datadir = /var/lib/mysql 31 | log-error = /var/log/mysql/error.log 32 | # By default we only accept connections from localhost 33 | #bind-address = 127.0.0.1 34 | # Disabling symbolic-links is recommended to prevent assorted security risks 35 | symbolic-links=0 36 | # replication 37 | log_bin = /var/log/mysql/mysql-bin 38 | log_bin_index = /var/log/mysql/mysql-bin.index 39 | server_id = 182 40 | # slaves 41 | relay_log = /var/log/mysql/relay-bin 42 | relay_log_index = /var/log/mysql/relay-bin.index 43 | relay_log_info_file = /var/log/mysql/relay-bin.info 44 | enforce_gtid_consistency = ON 45 | gtid_mode = ON 46 | log_slave_updates = ON 47 | read_only = ON 48 | master_info_repository = TABLE 49 | relay_log_info_repository = TABLE 50 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #set -x 3 | set -eo pipefail 4 | shopt -s nullglob 5 | 6 | declare -a container_id; 7 | #declare -a container_ip; 8 | declare -a service_name; 9 | 10 | # 无参函数 11 | # 用于获取当前Docker compose下的所有container id和ip地址, 执行容器内的SSH免密登录脚本 12 | ssh-interconnect(){ 13 | local ssh_init_path=/preparation/ssh-init.sh 14 | local ssh_pass_path=/preparation/ssh-pass.sh 15 | # 将该project下的container name置于数组 16 | for con in $(docker-compose ps | sed -n '3,$p' | sed -n '/Up/p' | awk '{print $1}'); do 17 | # 获取正在运行的container id 18 | cid=$(docker ps | grep "$con" | awk '{print $1}') 19 | container_id+=( "$cid" ) 20 | # 获取容器ip 21 | #container_ip+=("$(docker inspect "$cid" | grep -o -E '\"IPAddress": ".+"' \ 22 | # | grep -o -E '(\d+[.]*)+\"' | sed "s/\"//g")") 23 | # 获取docker compose的service名, 限制一个service对应一个container 24 | service_name+=("$(docker inspect "$cid" | sed -n 's/\"com\.docker\.compose\.service\": \"//gp' \ 25 | | sed -n 's/\",//gp')") 26 | done 27 | 28 | for c_id in ${container_id[*]}; do 29 | echo "$c_id initializing SSH..." 30 | docker exec "$c_id" "$ssh_init_path" 31 | done 32 | 33 | for c_id in ${container_id[*]}; do 34 | for c_name in ${service_name[*]}; do 35 | docker exec "$c_id" "$ssh_pass_path" "$c_name" 36 | done 37 | done 38 | } 39 | 40 | # 将接收到的参数使用ANSI颜色打印到控制台 41 | aprint(){ 42 | echo "$(tput setaf 2)>>> $1 $(tput sgr0)" 43 | } 44 | 45 | aprint "Docker Compose starting..." 46 | docker-compose up -d 47 | 48 | aprint "Setting ssh..." 49 | ssh-interconnect 50 | 51 | aprint "Creating mysql user for replication named 'repl' on master container..." 52 | docker-compose exec master /mha_share/create-repl-account.sh 53 | 54 | aprint "Configuring replication with GTID mode..." 55 | for c_name in ${service_name[*]}; do 56 | if [[ "$c_name" =~ slave_.* ]]; then 57 | echo "configuring $c_name $c_id ..." 58 | docker exec "$c_id" /mha_share/change-master.sh 59 | fi 60 | done 61 | sleep 7 62 | 63 | aprint "Initializing MHA configuration..." 64 | docker-compose exec manager /preparation/bootstrap.sh ${service_name[*]} 65 | 66 | aprint "Done!" 67 | -------------------------------------------------------------------------------- /mha_manager/preparation/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # usage: 在MHA manager上运行, 用于初始化配置文件 3 | # author: prontera@github 4 | 5 | #set -x 6 | set -eo pipefail 7 | shopt -s nullglob 8 | dir_cnf=/mha_share 9 | file_cnf="$dir_cnf"/application.cnf 10 | generated_flag="# generated" 11 | 12 | # 传输需要检测的参数的名称, 如果不存在则抛出异常 13 | check_env(){ 14 | local var="$1" 15 | if [[ -z ${!var:-} ]]; then 16 | echo >&2 "$HOSTNAME the environment variable \"$var\" do not exist!" 17 | fi 18 | } 19 | 20 | append_to_conf(){ 21 | echo "$@" >> "$file_cnf" 22 | } 23 | 24 | init_conf(){ 25 | check_env "MYSQL_REPL_NAME" 26 | check_env "MYSQL_REPL_NAME" 27 | check_env "MYSQL_REPL_NAME" 28 | check_env "MYSQL_REPL_PASSWORD" 29 | check_env "MYSQL_MHA_NAME" 30 | check_env "MYSQL_MHA_PASSWORD" 31 | 32 | append_to_conf "$generated_flag" 33 | append_to_conf "user=${MYSQL_MHA_NAME}" 34 | append_to_conf "password=${MYSQL_MHA_PASSWORD}" 35 | append_to_conf "repl_user=${MYSQL_REPL_NAME}" 36 | append_to_conf "repl_password=${MYSQL_REPL_PASSWORD}" 37 | 38 | i=1 39 | for arg; do 40 | if [[ "$arg" != "manager" ]]; then 41 | append_to_conf "[server$i]" 42 | append_to_conf "hostname=$arg" 43 | append_to_conf "candidate_master=1" 44 | echo "added host \"$arg\" to mha configuration file." 45 | ((i++)) 46 | fi 47 | done 48 | } 49 | 50 | if grep "$generated_flag" "$file_cnf" >& /dev/null; then 51 | echo "\"$file_cnf\" has been modified, skipping..." 52 | else 53 | echo "mha configuration \"$file_cnf\" is not initialized." 54 | init_conf "$@" 55 | fi 56 | 57 | echo "**********************************************" 58 | echo "checking mha ssh..." 59 | masterha_check_ssh --conf="$file_cnf" 60 | echo "**********************************************" 61 | echo "checking mha repl to mysql..." 62 | masterha_check_repl --conf="$file_cnf" 63 | 64 | if [ "$(ps aux | pgrep '/usr/local/bin/masterha_manager' || echo $?)" == 1 ]; then 65 | echo "**********************************************" 66 | echo "starting mha manager with file \"$file_cnf\"..." 67 | nohup masterha_manager --conf="$file_cnf" >>$dir_cnf/mha.log & 68 | sleep 1 69 | else 70 | echo "**********************************************" 71 | echo "mha manager process has been running already..." 72 | fi 73 | exit 0 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 4 | *.iml 5 | 6 | ## Directory-based project format: 7 | .idea/ 8 | # if you remove the above rule, at least ignore the following: 9 | 10 | # MHA Docker example 11 | employees_master/lib/ 12 | employees_master/log/ 13 | employees_slave*/lib/ 14 | employees_slave*/log/ 15 | employees_share/mha.log 16 | 17 | # User-specific stuff: 18 | # .idea/workspace.xml 19 | # .idea/tasks.xml 20 | # .idea/dictionaries 21 | 22 | # Sensitive or high-churn files: 23 | # .idea/dataSources.ids 24 | # .idea/dataSources.xml 25 | # .idea/sqlDataSources.xml 26 | # .idea/dynamic.xml 27 | # .idea/uiDesigner.xml 28 | 29 | # Gradle: 30 | # .idea/gradle.xml 31 | # .idea/libraries 32 | 33 | # Mongo Explorer plugin: 34 | # .idea/mongoSettings.xml 35 | 36 | ## File-based project format: 37 | *.ipr 38 | *.iws 39 | 40 | ## Plugin-specific files: 41 | 42 | # IntelliJ 43 | /out/ 44 | **/target/ 45 | **/generatorConfig.xml 46 | **/rebel.xml 47 | **/rebel-remote.xml 48 | 49 | # mpeltonen/sbt-idea plugin 50 | .idea_modules/ 51 | 52 | # JIRA plugin 53 | atlassian-ide-plugin.xml 54 | 55 | # Crashlytics plugin (for Android Studio and IntelliJ) 56 | com_crashlytics_export_strings.xml 57 | crashlytics.properties 58 | crashlytics-build.properties 59 | ### TortoiseGit template 60 | # Project-level settings 61 | /.tgitconfig 62 | ### CVS template 63 | /CVS/* 64 | */CVS/* 65 | .cvsignore 66 | */.cvsignore 67 | ### MicrosoftOffice template 68 | *.tmp 69 | 70 | # Word temporary 71 | ~$*.doc* 72 | 73 | # Excel temporary 74 | ~$*.xls* 75 | 76 | # Excel Backup File 77 | *.xlk 78 | ### Java template 79 | *.class 80 | 81 | # Mobile Tools for Java (J2ME) 82 | .mtj.tmp/ 83 | 84 | # Package Files # 85 | #*.jar 86 | *.war 87 | *.ear 88 | 89 | # domain machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 90 | hs_err_pid* 91 | ### SublimeText template 92 | # cache files for sublime text 93 | *.tmlanguage.cache 94 | *.tmPreferences.cache 95 | *.stTheme.cache 96 | 97 | # workspace files are user-specific 98 | *.sublime-workspace 99 | 100 | # project files should be checked into the repository, unless a significant 101 | # proportion of contributors will probably not be using SublimeText 102 | # *.sublime-project 103 | 104 | # sftp configuration file 105 | sftp-config.json 106 | ### Eclipse template 107 | *.pydevproject 108 | .metadata 109 | .gradle 110 | bin/ 111 | tmp/ 112 | *.bak 113 | *.swp 114 | *~.nib 115 | local.properties 116 | .settings/ 117 | .loadpath 118 | 119 | # Eclipse Core 120 | .project 121 | 122 | # External tool builders 123 | .externalToolBuilders/ 124 | 125 | # Locally stored "Eclipse launch configurations" 126 | *.launch 127 | 128 | # CDT-specific 129 | .cproject 130 | 131 | # JDT-specific (Eclipse Java Development Tools) 132 | .classpath 133 | 134 | # Java annotation processor (APT) 135 | .factorypath 136 | 137 | # PDT-specific 138 | .buildpath 139 | 140 | # sbteclipse plugin 141 | .target 142 | 143 | # TeXlipse plugin 144 | .texlipse 145 | ### Linux template 146 | *~ 147 | 148 | # KDE directory preferences 149 | .directory 150 | 151 | # Linux trash folder which might appear on any partition or disk 152 | .Trash-* 153 | ### Vim template 154 | [._]*.s[a-w][a-z] 155 | [._]s[a-w][a-z] 156 | *.un~ 157 | Session.vim 158 | .netrwhist 159 | ### SVN template 160 | .svn/ 161 | ### Windows template 162 | # Windows image file caches 163 | Thumbs.db 164 | ehthumbs.db 165 | 166 | # Folder config file 167 | Desktop.ini 168 | 169 | # Recycle Bin used on file shares 170 | $RECYCLE.BIN/ 171 | 172 | # Windows Installer files 173 | *.cab 174 | *.msi 175 | *.msm 176 | *.msp 177 | 178 | # Windows shortcuts 179 | *.lnk 180 | ### Maven template 181 | target/ 182 | pom.xml.tag 183 | pom.xml.releaseBackup 184 | pom.xml.versionsBackup 185 | pom.xml.next 186 | release.properties 187 | dependency-reduced-pom.xml 188 | buildNumber.properties 189 | .mvn/timing.properties 190 | 191 | -------------------------------------------------------------------------------- /employees_db/employees.sql: -------------------------------------------------------------------------------- 1 | -- Sample employee database 2 | -- See changelog table for details 3 | -- Copyright (C) 2007,2008, MySQL AB 4 | -- 5 | -- Original data created by Fusheng Wang and Carlo Zaniolo 6 | -- http://www.cs.aau.dk/TimeCenter/software.htm 7 | -- http://www.cs.aau.dk/TimeCenter/Data/employeeTemporalDataSet.zip 8 | -- 9 | -- Current schema by Giuseppe Maxia 10 | -- Data conversion from XML to relational by Patrick Crews 11 | -- 12 | -- This work is licensed under the 13 | -- Creative Commons Attribution-Share Alike 3.0 Unported License. 14 | -- To view a copy of this license, visit 15 | -- http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to 16 | -- Creative Commons, 171 Second Street, Suite 300, San Francisco, 17 | -- California, 94105, USA. 18 | -- 19 | -- DISCLAIMER 20 | -- To the best of our knowledge, this data is fabricated, and 21 | -- it does not correspond to real people. 22 | -- Any similarity to existing people is purely coincidental. 23 | -- 24 | 25 | DROP DATABASE IF EXISTS employees; 26 | CREATE DATABASE IF NOT EXISTS employees; 27 | USE employees; 28 | 29 | SELECT 'CREATING DATABASE STRUCTURE' as 'INFO'; 30 | 31 | DROP TABLE IF EXISTS dept_emp, 32 | dept_manager, 33 | titles, 34 | salaries, 35 | employees, 36 | departments; 37 | 38 | set default_storage_engine = InnoDB; 39 | -- set default_storage_engine = MyISAM; 40 | -- set default_storage_engine = Falcon; 41 | -- set default_storage_engine = PBXT; 42 | -- set default_storage_engine = Maria; 43 | 44 | select CONCAT('storage engine: ', @@default_storage_engine) as INFO; 45 | 46 | CREATE TABLE employees ( 47 | emp_no INT NOT NULL, 48 | birth_date DATE NOT NULL, 49 | first_name VARCHAR(14) NOT NULL, 50 | last_name VARCHAR(16) NOT NULL, 51 | gender ENUM ('M','F') NOT NULL, 52 | hire_date DATE NOT NULL, 53 | PRIMARY KEY (emp_no) 54 | ); 55 | 56 | CREATE TABLE departments ( 57 | dept_no CHAR(4) NOT NULL, 58 | dept_name VARCHAR(40) NOT NULL, 59 | PRIMARY KEY (dept_no), 60 | UNIQUE KEY (dept_name) 61 | ); 62 | 63 | CREATE TABLE dept_manager ( 64 | dept_no CHAR(4) NOT NULL, 65 | emp_no INT NOT NULL, 66 | from_date DATE NOT NULL, 67 | to_date DATE NOT NULL, 68 | KEY (emp_no), 69 | KEY (dept_no), 70 | FOREIGN KEY (emp_no) REFERENCES employees (emp_no) ON DELETE CASCADE, 71 | FOREIGN KEY (dept_no) REFERENCES departments (dept_no) ON DELETE CASCADE, 72 | PRIMARY KEY (emp_no,dept_no) 73 | ); 74 | 75 | CREATE TABLE dept_emp ( 76 | emp_no INT NOT NULL, 77 | dept_no CHAR(4) NOT NULL, 78 | from_date DATE NOT NULL, 79 | to_date DATE NOT NULL, 80 | KEY (emp_no), 81 | KEY (dept_no), 82 | FOREIGN KEY (emp_no) REFERENCES employees (emp_no) ON DELETE CASCADE, 83 | FOREIGN KEY (dept_no) REFERENCES departments (dept_no) ON DELETE CASCADE, 84 | PRIMARY KEY (emp_no,dept_no) 85 | ); 86 | 87 | CREATE TABLE titles ( 88 | emp_no INT NOT NULL, 89 | title VARCHAR(50) NOT NULL, 90 | from_date DATE NOT NULL, 91 | to_date DATE, 92 | KEY (emp_no), 93 | FOREIGN KEY (emp_no) REFERENCES employees (emp_no) ON DELETE CASCADE, 94 | PRIMARY KEY (emp_no,title, from_date) 95 | ); 96 | 97 | CREATE TABLE salaries ( 98 | emp_no INT NOT NULL, 99 | salary INT NOT NULL, 100 | from_date DATE NOT NULL, 101 | to_date DATE NOT NULL, 102 | KEY (emp_no), 103 | FOREIGN KEY (emp_no) REFERENCES employees (emp_no) ON DELETE CASCADE, 104 | PRIMARY KEY (emp_no, from_date) 105 | ); 106 | 107 | INSERT INTO `departments` VALUES ('d001','Marketing'),('d002','Finance'),('d003','Human Resources'),('d004','Production'),('d005','Development'),('d006','Quality Management'),('d007','Sales'),('d008','Research'),('d009','Customer Service'); 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Docker MySQL MHA 2 | 3 | 基于Docker 1.13.1之上构建的MySQL MHA Docker Compose Project 4 | 5 | 可快速启动GTID模式下的MasterHA集群, 主用于MySQL与Docker的学习研究 6 | 7 | ## 构建环境 8 | 9 | - MacOS 10.12.3 10 | - Docker 1.13.1 11 | - Docker Compose 1.11.1 12 | - Docker image for MySQL 5.7.17 13 | - Docker image for Debian jessie 14 | - mha4mysql-manager-0.56 15 | - mha4mysql-node-0.56 16 | 17 | ## 特性 18 | 19 | - USTC debian sources 20 | - 自动生成MySQL复制账户与MHA工作账户 21 | - 可自定义的MySQL复制与MHA工作账户信息 22 | - SSH key的自动生成 23 | - 容器间的SSH key相互复制与SSH免密登录 24 | - MasterHA的自启动 25 | 26 | ## 构建与启动 27 | 28 | ### docker-compose.yml 定义 29 | 30 | `master` - Docker compose中名为`master`的service, 主库容器默认映射端口3406 31 | 32 | `slave_1` - Docker compose中名为`slave_1`的service, 从库容器默认映射端口3407 33 | 34 | `manager` - Docker compose中名为`manager`的service, 作为MHA manager 35 | 36 | `mha_share` - 容器间的共享数据卷 37 | 38 | ### 目录与文件功能说明 39 | 40 | - account.env 41 | 42 | 保存容器间的公用环境变量, 如MySQL复制账户repl的账号密码 43 | 44 | - employees_db 45 | 46 | `master` 启动时的MySQL初始化脚本 47 | 48 | - employees_master 49 | 50 | `master` 主库容器中的配置文件, 日志与库存放位置 51 | 52 | - employees_slave_1 53 | 54 | `slave_1` 从库容器中的配置文件, 日志与库存放位置 55 | 56 | - employees_share 57 | 58 | 容器间的共享数据卷 59 | 60 | - mha_manager 61 | 62 | 构建Docker镜像`mha_manager`所需的Dockerfile及其依赖文件的存放目录, 在docker-compose.yml有定义 63 | 64 | - mha_node 65 | 66 | 构建Docker镜像`mha_node`所需的Dockerfile及其依赖文件的存放目录, 在docker-compose.yml有定义 67 | 68 | - reset.sh 69 | 70 | 停止project并删除所有数据库的日志与库(配置文件除外) 71 | 72 | - shutdown.sh 73 | 74 | 单纯地停止project 75 | 76 | - start.sh 77 | 78 | 1. 启动project 79 | 2. 在各个容器中生成ssh key 80 | 3. 将ssh key分别复制到其他容器中, 使得容器之间都可以使用SSH免密登录 81 | 4. 使`master`与`slave_1`形成复制链路 82 | 5. 根据自定义的MySQL账户密码生成MHA配置文件 83 | 6. masterha_check_ssh检测容器间的SSH正确性 84 | 7. masterha_check_repl检测复制的健康状况 85 | 8. 启动masterha_manager并将日志写入employees_share中的`mha.log` 86 | 87 | ### 启动 88 | 89 | 1. **首次运行时**请先初始化MySQL, 否则容器将不接受连接 90 | 91 | ```shell 92 | ➜ mha git:(master) docker-compose up -d 93 | Creating network "mha_default" with the default driver 94 | Creating mha_mha_node_1 95 | Creating mha_mha_share_1 96 | Creating mha_mha_manager_1 97 | Creating mha_master_1 98 | Creating mha_slave_1_1 99 | Creating mha_manager_1 100 | ``` 101 | 102 | 待容器可接收宿主机的MySQL连接代表初始化完成 103 | 104 | 2. 预热后运行start.sh脚本以构建MHA集群 105 | 106 | ```shell 107 | ➜ mha git:(master) ./start.sh 108 | >>> Docker Compose starting... 109 | Starting mha_mha_manager_1 110 | Starting mha_mha_share_1 111 | Starting mha_mha_node_1 112 | mha_master_1 is up-to-date 113 | mha_slave_1_1 is up-to-date 114 | mha_manager_1 is up-to-date 115 | >>> Setting ssh... 116 | fd9686976e61 initializing SSH... 117 | fd9686976e61 change the password of root successfully. 118 | fd9686976e61 SSH service has been restarted. 119 | fd9686976e61 succeed in generating ssh key. 120 | ... 121 | fd9686976e61 copy ssh key to manager successfully. 122 | fd9686976e61 copy ssh key to master successfully. 123 | ... 124 | >>> Creating mysql user for replication named 'repl' on master container... 125 | mysql: [Warning] Using a password on the command line interface can be insecure. 126 | >>> Configuring replication with GTID mode... 127 | configuring slave_1 754214d5bdfc ... 128 | mysql: [Warning] Using a password on the command line interface can be insecure. 129 | >>> Initializing MHA configuration... 130 | mha configuration "/mha_share/application.cnf" is not initialized. 131 | added host "master" to mha configuration file. 132 | added host "slave_1" to mha configuration file. 133 | ********************************************** 134 | checking mha ssh... 135 | Wed Feb 15 11:04:08 2017 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping. 136 | ... 137 | Wed Feb 15 11:04:08 2017 - [debug] ok. 138 | Wed Feb 15 11:04:09 2017 - [info] All SSH connection tests passed successfully. 139 | ********************************************** 140 | checking mha repl to mysql... 141 | Wed Feb 15 11:04:09 2017 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping. 142 | ... 143 | MySQL Replication Health is OK. 144 | ********************************************** 145 | starting mha manager with file "/mha_share/application.cnf"... 146 | nohup: redirecting stderr to stdout 147 | >>> Done! 148 | ``` 149 | 150 | > NOTE: 首次运行时如果不预热而直接调用start.sh脚本的话, 则会引起MySQL未完成初始化而造成构建主从复制链路出错, 如果是数据库已经经过初始化则不需要进行预热 151 | 152 | ## 注意事项 153 | 154 | 1. 首次运行时请先运行`docker-compose up -d`进行MySQL的初始化 155 | 2. 一个Docker Compose Service对应一个容器, 以此享用Docker Compose默认构建的容器网络, 即可直接使用service name进行SSH通信 156 | 3. 本项目仅用于学习MySQL MHA集群, 同时练习Docker的使用 157 | 4. 要使用虚拟IP的话可自行搭配Keepalive, LVS等 158 | 159 | ## 参考 160 | 161 | [MHA Quick Start Guide](https://www.percona.com/blog/2016/09/02/mha-quickstart-guide/) 162 | 163 | [MySQL Docker Image](https://hub.docker.com/_/mysql/) 164 | 165 | [Two-way link with Docker Compose](https://medium.com/@tristan.claverie/well-there-is-in-fact-a-simpler-solution-than-creating-a-network-do-nothing-at-all-docker-f38e93326134#.l6uupkacv) 166 | 167 | [How to configure MySQL master/slave replication with MHA automatic failover](http://www.arborisoft.com/how-to-configure-mysql-masterslave-replication-with-mha-automatic-failover/) --------------------------------------------------------------------------------