Skip to content

Instantly share code, notes, and snippets.

@cauethenorio
Created June 25, 2020 21:37
Show Gist options
  • Save cauethenorio/3b8761c5a22163e606fea5f598a61472 to your computer and use it in GitHub Desktop.
Save cauethenorio/3b8761c5a22163e606fea5f598a61472 to your computer and use it in GitHub Desktop.
Script to setup/resync two MariaDB nodes (main/seconday)
#!/bin/bash
# MariaDB/MySQL Replication Configuration Script for Ubuntu/Debian
# cauelt@gmail.com 2013-12/2014-09
set -o errexit -o nounset -o pipefail
## References
# Percona repositories:
# http://www.percona.com/doc/percona-server/5.5/installation/apt_repo.html
### Variables
# colors
NC="\e[0m" # NO COLOR
YC="\e[93m" # YELLOW
BC="\e[94m" # BLUE
GC="\e[92m" # GREEN
RC="\e[91m" # RED
# slave port used to transfer data from slave
NETCAT_PORT=9182
# apt-get update executed flag
APT_UPDATE_EXECUTED=0
# get OS codebase
OS_CODEBASE=$(lsb_release -c|awk -F ' ' '{print $2}')
# dir to store backup temporarily
TIMESTAMP=$(date +"%Y-%m-%d-%H-%M")
BACKUP_DIR="/tmp/xtrabackup/${TIMESTAMP}/master_data"
SLAVEDATA_DIR="/tmp/xtrabackup/${TIMESTAMP}/slave_data"
### Functions
function show_vars {
echo -e "\n"
msg "Ocorreu um erro na execução do script e agora você vai precisar continuar por sua conta e risco :-)" "" "" "${RC}"
echo
echo "Variáveis de ambiente:"
echo
echo "TIMESTAMP=\""${TIMESTAMP}"\""
echo "BACKUP_DIR=\""${BACKUP_DIR}"\""
echo "SLAVEDATA_DIR=\""${SLAVEDATA_DIR}"\""
if [[ "${TIPO_SERVIDOR:-}" =~ ^[Mm]$ ]]; then
echo "MASTER_USER=\""${MASTER_USER:-}"\""
echo "MASTER_PASS=\""${MASTER_PASS:-}"\""
echo "MYSQL_COMMAND=\""${MYSQL_COMMAND:-}"\""
echo "REPL_USER=\""${REPL_USER:-}"\""
echo "REPL_PASS=\""${REPL_PASS:-}"\""
echo "SLAVE_IP=\""${SLAVE_IP:-}"\""
echo "BKP_COMMAND=\""${BKP_COMMAND:-}"\""
elif [[ "${TIPO_SERVIDOR:-}" =~ ^[Ss]$ ]]; then
echo "LOCAL_USER=\""${LOCAL_USER:-}"\""
echo "LOCAL_PASS=\""${LOCAL_PASS:-}"\""
echo "LOCAL_MYSQL_COMMAND=\""${LOCAL_MYSQL_COMMAND:-}"\""
echo "REPL_USER=\""${REPL_USER:-}"\""
echo "REPL_PASS=\""${REPL_PASS:-}"\""
echo "MASTER_IP=\""${MASTER_IP:-}"\""
echo "REMOTE_MYSQL_COMMAND=\""${REMOTE_MYSQL_COMMAND:-}"\""
echo "DATADIR=\""${DATADIR:-}"\""
echo "SLAVE_SERVER_ID=\""${SLAVE_SERVER_ID:-}"\""
echo "MASTER_LOG_FILE=\""${MASTER_LOG_FILE:-}"\""
echo "MASTER_LOG_POS=\""${MASTER_LOG_POS:-}"\""
fi
echo
echo "Ao terminar o processo não se esqueça de apagar os diretórios temporários:"
echo " ${BACKUP_DIR}"
echo " ${SLAVEDATA_DIR}"
}
function msg {
TSTAMP=$(date +"%H:%M:%S")
echo -e${2:-} "${3:-}${YC}[${TSTAMP}] ${4:-$GC}$1${NC}"
}
function check_connection {
CH_MYSQL_COMMAND=$1
echo -ne "${GC}Testando a conexão com o banco...${NC} "
CONN_ERROR=$(${CH_MYSQL_COMMAND} -e exit 2>&1) && CONN_RESULT=$? || CONN_RESULT=12
if [ $CONN_RESULT -ne 0 ]; then
echo -e " FALHOU!\n${CONN_ERROR}\n"
else
echo -e " OK!"
fi
}
# verifica se pacote está instalado, e instala-o se não estiver
function ensure_package {
PKG_NAME=$1
PKG_OK=$(dpkg-query -W --showformat='${Status}\n' ${PKG_NAME} 2>&1|grep "install ok installed") || "echo"
if [ "" == "$PKG_OK" ]; then
if [ $APT_UPDATE_EXECUTED -eq 0 ]; then
# update index
msg "Atualizando o índice de pacotes..." "" "\n"
apt-get update
APT_UPDATE_EXECUTED=$((APT_UPDATE_EXECUTED+1))
fi
echo -e "Instalando '${PKG_NAME}'..."
apt-get -y install "${PKG_NAME}"
msg "Pacote '${PKG_NAME}' instalado com sucesso!"
else
msg "Pacote '${PKG_NAME}' já instalado."
fi
}
# funcao que checa as permissoes do usuario administrativo do servidor master
function check_replication_user {
CH_MYSQL_COMMAND=$1
REPL_HOST=${2:-}
REPL_USER=${3:-}
REPL_PASS=${4:-}
msg "Verificando a existência do usuário '${REPL_USER}'@'${REPL_HOST}'..."
REPL_USER_EXISTS=$($CH_MYSQL_COMMAND -e "SELECT user FROM mysql.user WHERE user = '${REPL_USER}' AND host = '${REPL_HOST}'"|wc -c)
if [ $REPL_USER_EXISTS -eq 0 ]; then
echo -e "O usuário ${REPL_USER} não existe e precisa ser criado."
echo -e "Pressione ENTER para criar o usuário '${REPL_USER}'@'${REPL_HOST}' com a senha '${REPL_PASS}'..."
read
$CH_MYSQL_COMMAND -e "CREATE USER '${REPL_USER}'@'${REPL_HOST}' IDENTIFIED BY '${REPL_PASS}';"
msg "Usuário '${REPL_USER}' criado com sucesso!"
else
echo -e "O usuário ${REPL_USER} existe."
fi
$CH_MYSQL_COMMAND -e "GRANT REPLICATION SLAVE ON *.* TO '${REPL_USER}'@'${REPL_HOST}'; FLUSH PRIVILEGES;"
msg "Adicionada ao usuário '${REPL_USER}' a permissão para fazer replicação a partir do SLAVE."
}
# termina a execução do script se não for o root executando-o
[ "$(id -u)" = "0" ] || (msg "Erro: Esse script deve ser executado como root" "" "" "${RC}" && exit 1)
trap show_vars EXIT
# para instalar poder adicionar o repo da percona
ensure_package software-properties-common
ensure_package python-software-properties
PERCONA_REPO=$(cat "/etc/apt/sources.list"|grep "deb http://repo.percona.com/apt") || echo
if [ "" == "$PERCONA_REPO" ]; then
# add percona repositories
msg "Adicionando repositorios da Percona..." "" "\n"
apt-key adv --keyserver keys.gnupg.net --recv-keys 1C4CBDCDCD2EFD2A
add-apt-repository "deb http://repo.percona.com/apt ${OS_CODEBASE} main"
apt-get update
APT_UPDATE_EXECUTED=$((APT_UPDATE_EXECUTED+1))
msg "Repositórios adicionados com sucesso!"
else
msg "Repositórios da Percona já adicionados."
fi
# verifica se os pacotes necessários estão instalados
# e instala-os se não estiverem
ensure_package pv
ensure_package percona-xtrabackup
echo -ne "\nEsse script está sendo executado no servidor MASTER ou SLAVE (M/S)? "
read -n 1 TIPO_SERVIDOR
echo -e "\n"
# se esta rodando no master...
if [[ "$TIPO_SERVIDOR" =~ ^[Mm]$ ]]; then
HOUVE_MUDANCAS_MY_CNF=
msg "Verificando espaço disponível..."
MASTER_DATADIR=$(cat /etc/mysql/my.cnf |grep '^datadir'|awk -F ' ' '{print $3}')
MASTER_DATADIR_TAMANHO=$(du -s "${MASTER_DATADIR}"|awk "{print \$1}")
MASTER_ESPACO_LIVRE_TMP=$(df -k "/tmp"|awk '{print $4}'|sed -n 2p)
if [ ! "${MASTER_ESPACO_LIVRE_TMP}" -gt "${MASTER_DATADIR_TAMANHO}" ]; then
msg "Não há espaço disponível para copiar os dados do MySQL!" '' '' "$RC"
echo
msg "Tamanho dos dados do MySQL: " n '' "$RC"
echo "$(du -sh "${MASTER_DATADIR}"|awk "{print \$1}")"
msg "Espaço livre na partição de '/tmp': " n '' "$RC"
echo "$(df -kh "/tmp"|awk '{print $4}'|sed -n 2p)"
trap - EXIT
exit 1
fi
echo "OK!"
msg "Verificando bind-address do servidor..."
# se o servidor só está escutando na porta local...
if cat /etc/mysql/my.cnf | grep -Pxq "bind-address\s*=\s*127\.0\.0\.1"; then
echo
echo "O servidor MySQL instalado está configurado para apenas aceitar"
echo "conexões a partir da interface local (127.0.0.1)."
echo
msg "Pressione ENTER para habilitar conexões a partir de todas as interfaces..."
read
sed -ie 's/^\(bind-address\s*=\s*\)127\.0\.0\.1/\10\.0\.0\.0/' /etc/mysql/my.cnf
HOUVE_MUDANCAS_MY_CNF=1
echo -e "bind-address definido como 0.0.0.0. OK!\n"
else
echo -e "bind-adress OK!\n"
fi
msg "Verificando o server-id do servidor..."
# se o server-id nao esta definido no my.cnf...
if ! cat /etc/mysql/my.cnf | grep -Pxq "server-id[ \t]+=[ \t]*1"; then
msg "A variável 'server-id' não está definida como 1 no servidor MASTER." '' "\n"
msg "Pressione ENTER para fazer essa alteração..."
read
# pega o numero da linha onde a string 'server-id' foi encontrada pela 1a vez
NSID=$(grep -n -m 1 "server-id" /etc/mysql/my.cnf | awk -F ':' '{print $1}')
# comenta o server-id do arquivo
sed -ie 's/^\(server-id[ \t]\+\)/# \1/g' /etc/mysql/my.cnf
# define o server-id como 1
sed -ie "${NSID}i\server-id = 1" /etc/mysql/my.cnf
HOUVE_MUDANCAS_MY_CNF=1
echo -e "O server-id foi definido como 1. OK!\n"
else
echo -e "O server-id já está definido como 1. OK!\n"
fi
if [ "$HOUVE_MUDANCAS_MY_CNF" ]; then
msg "Pressione ENTER para reiniciar o servidor e aplicar as mudanças feitas no my.cnf..."
read
# reinicia o DB...
service mysql restart
echo
fi
while true; do
echo -n "Usuario root do banco [root] : "
read MASTER_USER
[ -z "${MASTER_USER}" ] && MASTER_USER=root
echo -n "Digite a senha desse usuario : "
read -s MASTER_PASS
[ -n "${MASTER_PASS}" ] && PASS_PARAM="--password=${MASTER_PASS}"
echo -e "\n"
# monta os parametros a serem passados para o mysql
MYSQL_COMMAND="mysql --user=${MASTER_USER} ${PASS_PARAM:-}"
check_connection "$MYSQL_COMMAND"
[ $CONN_RESULT -ne 0 ] || break
echo
done
echo
echo -e "Entre com os dados do usuário a ser usado para réplica."
echo -e "Caso esse usuário não exista, ele será criado."
echo
echo -n "Usuario que será usado para réplica : "
read REPL_USER
echo -n "Digite a senha desse usuario : "
read -s REPL_PASS
[ -n "${REPL_PASS}" ] && REPL_PASS_PARAM="--password=${REPL_PASS}"
echo
echo -ne "Digite o IP do servidor SLAVE : "
read SLAVE_IP
echo
check_replication_user "$MYSQL_COMMAND" ${SLAVE_IP:-} ${REPL_USER:-} ${REPL_PASS:-}
BKP_COMMAND="innobackupex --user=${MASTER_USER} ${PASS_PARAM:-}"
msg "Fazendo o backup dos dados..." "" "\n"
mkdir -p $(dirname ${BACKUP_DIR})
$BKP_COMMAND --no-timestamp ${BACKUP_DIR}
msg "Atualizando o backup e adicionado log de transações..." "" "\n"
$BKP_COMMAND --apply-log ${BACKUP_DIR}
msg "Backup atualizado com sucesso!" "" "\n"
echo
NETCAT_CHECK=1
while [ $NETCAT_CHECK -ne 0 ]; do
msg "Checando porta ${NETCAT_PORT} do servidor ${SLAVE_IP}..."
nc -z -w5 ${SLAVE_IP} ${NETCAT_PORT} && NETCAT_CHECK=$? || NETCAT_CHECK=1
msg "Execute esse script no servidor SLAVE para conitnuar."
if [[ "${NETCAT_CHECK}" -ne 0 ]]; then sleep 5; fi
done
sleep 1
msg "Copiando arquivo de configuração 'debian.cnf' para o SLAVE... " n "\n"
cat "/etc/mysql/debian.cnf" | nc ${SLAVE_IP} ${NETCAT_PORT}
echo "OK!"
sleep 1
msg "Copiando arquivo de configuração 'my.cnf' para o SLAVE... " n "\n"
cat "/etc/mysql/my.cnf" | nc ${SLAVE_IP} ${NETCAT_PORT}
echo "OK!"
sleep 1
msg "\nCopiando o backup para o servidor SLAVE..."
tar -c -C ${BACKUP_DIR} . | pv -s $(du -sb ${BACKUP_DIR} | awk '{print $1}') | gzip | nc ${SLAVE_IP} ${NETCAT_PORT}
echo -e "Os dados foram copiados com sucesso!\n"
echo -ne "Pressione ENTER para remover o backup no MASTER \"${BACKUP_DIR}\" e liberar espaço..."
read
rm -Rf "${BACKUP_DIR}"
msg "As operações nesse servidor para a configuração da replicação estão concluídas."
msg "Acompanhe a execução do script no servidor SLAVE."
trap - EXIT
exit
elif [[ "$TIPO_SERVIDOR" =~ ^[Ss]$ ]]; then
msg "Verificando se servidor MySQL está sendo executado... " n
PID_FILE="/var/run/mysqld/mysqld.pid"
if [[ -e "${PID_FILE}" && -s "${PID_FILE}" ]]; then
msg "Parando o MySQL/MariaDB... " '' "\n"
service mysql stop || mysqladmin shutdown -p${LOCAL_PASS} || true
echo "OK!"
else
echo "PARADO. OK!"
fi
echo
DATADIR=$(cat /etc/mysql/my.cnf |grep '^datadir'|awk -F ' ' '{print $3}')
msg "Espaço utilizado pelos dados do MySQL (${DATADIR}): " n
echo $(du -sh "${DATADIR}"|awk "{print \$1}")
msg "Espaço livre na repartição dos dados: " n
echo $(df -kh "${DATADIR}"|awk '{print $4}'|sed -n 2p)
echo
mkdir -p "${BACKUP_DIR}"
mkdir -p "${SLAVEDATA_DIR}"
while true; do
echo -ne "Deseja SALVAR (S) ou REMOVER (R) os dados atuais do MySQL (S/R) ? "
read -n 1 ACAO_DADOS_SLAVE
echo -e "\n"
if [[ "$ACAO_DADOS_SLAVE" =~ ^[Ss]$ ]]; then
msg "Movendo dados existentes de '${DATADIR}' para '${SLAVEDATA_DIR}'... " n "\n"
mv ${DATADIR} ${SLAVEDATA_DIR}; mkdir -p ${DATADIR}
break
elif [[ "$ACAO_DADOS_SLAVE" =~ ^[Rr]$ ]]; then
echo -ne "ATENÇÃO: Confirma a REMOÇÃO de todos os dados do MySQL (S/N) ? "
read -n 1 CONFIRMA_REMOCAO_DADOS_SLAVE
echo
if [[ "$CONFIRMA_REMOCAO_DADOS_SLAVE" =~ ^[Ss]$ ]]; then
msg "Removendo dados existentes em '${DATADIR}'... " n "\n"
rm -Rf "${DATADIR}"; mkdir -p ${DATADIR}
break
fi
fi
done
echo -e "OK!\n"
while true; do
echo -n "Usuario do banco MASTER que será usado para réplica : "
read REPL_USER
echo -n "Digite a senha desse usuario : "
read -s REPL_PASS
[ -n "${REPL_PASS}" ] && PASS_PARAM="--password=${REPL_PASS}"
echo
echo -ne "Digite o IP do servidor MASTER : "
read MASTER_IP
echo
# monta os parametros a serem passados para o mysql
REMOTE_MYSQL_COMMAND="mysql --host=${MASTER_IP} --user=${REPL_USER} ${PASS_PARAM:-}"
check_connection "${REMOTE_MYSQL_COMMAND}"
[ $CONN_RESULT -ne 0 ] || break
echo
done
SLAVE_IP=$(ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1')
msg "Aguardando conexão na porta ${NETCAT_PORT} do servidor SLAVE $(echo ${SLAVE_IP})..."
msg "Continue/inicie a execução desse script no servidor MASTER para continuar."
# wait for first connection
nc -l ${NETCAT_PORT}
cd ${BACKUP_DIR}
msg "Recebendo 'debian.cnf' do servidor MASTER... " n "\n"
nc -l ${NETCAT_PORT} > ${BACKUP_DIR}/debian.cnf.master
echo "OK!"
msg "Recebendo 'my.cnf' do servidor MASTER... " n "\n"
nc -l ${NETCAT_PORT} > ${BACKUP_DIR}/my.cnf.master
echo "OK!"
msg "Iniciando cópia do backup..." '' "\n"
nc -l ${NETCAT_PORT} | gunzip | tar ixvf -
msg "\nDados copiados com sucesso!"
echo
msg "Restaurando 'debian.cnf' copiado do MASTER... " n
cp /etc/mysql/debian.cnf ${SLAVEDATA_DIR}/debian.cnf
mv ${BACKUP_DIR}/debian.cnf.master /etc/mysql/debian.cnf
echo "OK"
# faz backup do my.cnf original do slave
cp /etc/mysql/my.cnf ${SLAVEDATA_DIR}/my.cnf
echo
echo -ne "Deseja restaurar o arquivo de configuração my.cnf do MASTER para o SLAVE (S/N) ? "
read -n 1 RESTAURAR_MY_CNF
echo -e "\n"
if [[ "$RESTAURAR_MY_CNF" =~ ^[Ss]$ ]]; then
msg "Restaurando 'my.cnf' copiado do MASTER... " n
mv ${BACKUP_DIR}/my.cnf.master /etc/mysql/my.cnf
echo -e "OK\n"
echo -e "Um novo 'server-id' deve ser definido para esse servidor SLAVE."
echo -ne "Pressione ENTER para definir como 2 ou digite o número a ser usado... [2] : "
read -n 1 SLAVE_SERVER_ID
[ -z "${SLAVE_SERVER_ID}" ] && SLAVE_SERVER_ID=2
echo
# pega o numero da linha onde a string 'server-id' foi encontrada pela 1a vez
NSID=$(grep -n -m 1 "server-id" /etc/mysql/my.cnf | awk -F ':' '{print $1}')
# comenta o server-id do arquivo
sed -ie 's/^\(server-id[ \t]\+\)/# \1/g' /etc/mysql/my.cnf
# define o server-id como 1
sed -ie "${NSID}i\server-id = ${SLAVE_SERVER_ID}" /etc/mysql/my.cnf
sed -i -e "s/^server-id.\+$/server-id = ${SLAVE_SERVER_ID}/g" /etc/mysql/my.cnf
msg "O 'server-id' foi definido como ${SLAVE_SERVER_ID} com sucesso!"
ORIGINAL_SLAVE_BIND_ADDRESS=$(grep "^bind-address" ${SLAVEDATA_DIR}/my.cnf)
sed -i -e "s/^bind-address.*$/${ORIGINAL_SLAVE_BIND_ADDRESS}/" /etc/mysql/my.cnf
fi
msg "Restaurando backup... " n "\n"
innobackupex --move-back ${BACKUP_DIR}
chown -R mysql:mysql ${DATADIR}
msg "Backup restaurado com sucesso!\n" '' "\n"
msg "O processo do MySQL/MariaDB será iniciado, se você deseja fazer alguma"
msg "configuração adicional no novo arquivo '/etc/mysql/my.cnf' que foi instalado,"
msg "faça essa alteração agora e dê o comando 'exit' quando tiver concluído."
bash
echo
msg "Reiniciando MySQL... " n "\n"
echo
service mysql stop || true
sleep 1
service mysql start
while true; do
echo -n "Usuario root do banco (mesmo do MASTER) [root] : "
read LOCAL_USER
[ -z "${LOCAL_USER}" ] && LOCAL_USER=root
echo -n "Digite a senha desse usuario : "
read -s LOCAL_PASS
[ -n "${LOCAL_PASS}" ] && LOCAL_PASS_PARAM="--password=${LOCAL_PASS}"
echo -e "\n"
# monta os parametros a serem passados para o mysql
LOCAL_MYSQL_COMMAND="mysql --user=${LOCAL_USER} ${LOCAL_PASS_PARAM:-}"
check_connection "$LOCAL_MYSQL_COMMAND"
[ $CONN_RESULT -ne 0 ] || break
echo
done
msg "Configurando a replicação dos dados..." n
echo
MASTER_LOG_FILE=$(cat "${BACKUP_DIR}/xtrabackup_binlog_info"|awk '{print $1}')
MASTER_LOG_POS=$(cat "${BACKUP_DIR}/xtrabackup_binlog_info"|awk '{print $2}')
$LOCAL_MYSQL_COMMAND -e "CHANGE MASTER TO MASTER_HOST='${MASTER_IP}', MASTER_USER='${REPL_USER}', MASTER_PASSWORD='${REPL_PASS}', MASTER_LOG_FILE='${MASTER_LOG_FILE}', MASTER_LOG_POS=${MASTER_LOG_POS};";
$LOCAL_MYSQL_COMMAND -e "START SLAVE;"
msg "Replicação configurada com sucesso!"
echo
msg "Para verificar o andamento da replicação, verifique as variáveis" '' "\n"
echo "'Slave_IO_Running', 'Slave_SQL_Running' e 'Seconds_Behind_Master' abaixo."
echo
$LOCAL_MYSQL_COMMAND -e "SHOW SLAVE STATUS \G;"
trap - EXIT
echo "Ao terminar o processo não se esqueça de apagar os diretórios temporários:"
echo " ${BACKUP_DIR}"
echo " ${SLAVEDATA_DIR}"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment