Failover automático e transparente do
Postgres com pgpool
Tenho trabalhado muito com o pgpool recentemente e queria documentar
meus experimentos, caso sejam úteis para outros.
Neste post, documentarei passo a passo como instalar postgres/pgpool e como configurar
o pgpool para automatizar o failover. Em seguida, instalarei uma interface
gráfica em cima do pgpool .
Introdução
Este post tem a intenção de ser um
material de aprendizado, ele lista passo a passo como instalar
postgres/pgpool/repmgr. Como tudo pode ser feito na linha de comando, mais
tarde expandirei essa configuração para fazer testes automatizados do watchdog pgpool.
Observe que na minha empresa atual eu implantei postgres e pgpool como imagens
docker, em um docker swarm ou diretamente nos servidores (ou seja, usando docker
run para iniciar os contêineres). Tudo isso está no repositório do github
https://github.com/saule1508/pgcluster .
Se você começar uma jornada de aprendizado
com pgpool e postgres, usar docker pode ser de grande ajuda porque torna a experimentação
e o teste muito fáceis (você pode começar rapidamente do zero).
Darei mais ênfase aos conceitos ou configurações do pgpool que achei
difíceis de entender (às vezes porque são mal documentados). Para postgres e
repmgr e listarei essencialmente todos os passos necessários para configurá-lo,
mas é obviamente muito recomendado ler os respectivos documentos.
O resultado final é uma configuração
composta de 3 servidores Centos 7.6: um banco de dados primário e dois bancos
de dados stand-by, com um pgpool em cada nó tornado de alta disponibilidade com
o modo watch-dog do pgpool. Eu uso repmgr ( https://repmgr.org/ ), não para automatizar o
failover, mas porque ele traz scripts bem testados sobre a replicação do
postgres. NB: Estou usando libvirt para provisionar a VM (eu
provisiono um servidor e depois o clonei), mas você pode usar vagrant, virtual
box, VMWare ou provisionar os convidados centos manualmente. Se você tiver um
host Linux disponível, então sugiro usar libvirt, eu documentei aqui uma
maneira de provisionar um convidado centos a partir da linha de comando em
alguns minutos: Centos guest VM com cloud-init , isso é
simplesmente ótimo.
O resultado final será semelhante a este:
diagrama
interface gráfica
pgpool: alguns fatos
Antes de construir a configuração, aqui estão algumas considerações
sobre o Pgpool. O Pgpool é difícil e aprender porque ele pode fazer muitas
coisas e a documentação é esmagadora e nem sempre clara. Existem 3 funções
principais que são úteis, as outras são mais esotéricas ou por razões
históricas (como cache de resultados de consulta, replicação mestre-mestre,…).
O Pgpool tem uma boa comunidade e, uma vez que o conhecemos, gostamos dele
Cache de conexão
Se você pensar sobre o pool de conexão tradicional no mundo Java, onde o
webcontainer gerencia o pool de conexão e os threads Java pegam emprestado uma
conexão do pool para o momento de uma transação, então você ficará confuso com
o Pgpool. O Pgpool faz cache de conexão. Quando o aplicativo se conecta ao
Pgpool, o Pgpool usa um de seus processos filhos, então cria uma conexão com o
postgres e associa a conexão do postgres ao processo filho.
Quando o aplicativo se desconecta do pgpool, a conexão do postgres é limpa
(para liberar bloqueios, por exemplo) e é mantida aberta, para que permaneça no
cache para a próxima vez.
Se você tiver aplicativos modernos usando
pool de conexão e tiver muitos bancos de dados ou nomes de usuário diferentes,
o cache de conexão do pgpool pode não ser interessante e pode ser desabilitado
(parâmetro connection_cache='off' ) Failover automatizado e (principalmente) transparente
O Postgres tem um forte mecanismo de replicação (hot standby) no qual um
banco de dados primário transmite seus logs de transação (wal na terminologia
do postgres, write-ahead-logs) e um ou mais bancos de dados standby recebem
esses write-ahead-log e os aplicam continuamente para que todas as alterações feitas
no primário sejam replicadas nos standbys. O banco de dados primário é aberto
para leitura e gravação, os bancos de dados standby são abertos somente para
leitura. Mas o postgres não oferece nenhuma maneira automatizada de promover um
banco de dados standby em caso de falha do primário. Além disso, se um banco de
dados standby for promovido manualmente e se tornar o novo banco de dados
read-write, todos os aplicativos conectados ao banco de dados devem agora ser
informados de que devem se conectar a uma nova instância.
O pgpool resolve esses problemas, ele é usado em cima dos bancos de
dados postgres, agindo como um proxy, ele usa os seguintes mecanismos para
detectar uma falha e disparar um failover:
health checks: o pgpool se conecta
(com o usuário health_check_user )
regularmente (param health_check_period )
aos bancos de dados e, portanto, fica ciente quando um primário (ou um standby)
falha. Após um período de nova tentativa ( health_check_retry_interval e health_check_retries ), ele acionará um failover,
o que significa concretamente chamar um script com vários argumentos.
Alternativamente, ele pode usar o parâmetro failover_on_backend_error .
Este parâmetro deve estar desativado se você usar a verificação de integridade.
O que o erro failover_on_backend significa é que quando o Pgpool recebe um erro
em uma das conexões, ele imediatamente acionará um failover. Portanto, não há
nova tentativa. Além disso, se não houver atividade, a falha não será detectada
e o failover não ocorrerá.
O failover transparente é obtido porque os clientes estão se conectando
ao pgpool e não diretamente ao postgres: se um failover acontecer, o pgpool
saberá que o banco de dados primário foi alterado e destruirá e recriará todas
as conexões ao postgres sem nenhuma configuração necessária no lado do cliente.
Claro que não é completamente transparente, qualquer instrução (consulta ou
atualização) que estava sendo executada retornará um erro.
Mas se o pgpool for um proxy entre os aplicativos e o postgres, uma
falha do próprio pgpool resultaria em perda de disponibilidade. Em outras
palavras, o próprio pgpool se tornou o único ponto de falha e, claro, tem um
mecanismo muito bom que o torna HA: o modo watchdog
modo watchdog pgpool
watchdog adiciona alta
disponibilidade coordenando múltiplos nós pgpool (um liderado eleito via
consenso). O líder adquire o VIP (a aquisição do VIP é feita via script usando
ip e arp)
Os nós do pgpool se comunicam via porta tcp 9000 (comando ADD_NODE, etc.)
watchdog usa um mecanismo de heartbeat (opcionalmente, pode usar um mecanismo
de consulta): um “processo lifecheck” envia e recebe dados via UDP (porta 9694)
para verificar a disponibilidade de outros nós
protocolo de gerenciamento pgpool
O pcp expõe sua função através do pcp (protocolo de controle pgpool) na
porta tcp 9898. Isso requer um usuário (escolha postgres) e uma senha (a
combinação deve ser armazenada em um arquivo /home/postgres/.pcppass), para que
o usuário linux postgres possa usar pcp_attach_node, pcp_recovery_node, etc…
1. Crie o primeiro servidor com centos 7, postgres, repmgr e pgpool
A instalação do guest centos 7 é
descrita no link http://saule1508.github.io/libvirt-centos-cloud-image .
Ele precisa de um sistema de arquivos /u01 para o banco de dados (o BD
estará em /u01/pg11/data), um sistema de arquivos /u02 para os logs de gravação
antecipada arquivados (wal) e os backups (/u02/archive e /u02/backup). Eu crio
um usuário postgres com uid 50010 (id arbitrário, não importa, mas é melhor ter
o mesmo id em todos os servidores). O usuário deve ser criado antes que o rpm
seja instalado.
1.2 PostgreSQL
# as user root
MAJORVER=11
MINORVER=2
yum update -y
# install some useful packages
yum install -y epel-release libxslt sudo openssh-server
openssh-clients jq passwd rsync iproute python-setuptools hostname inotify-tools
yum-utils which sudo
vi
firewalld
# create user postgres before
installing postgres rpm, because I want to fix the uid
useradd -u 50010 postgres
# set a password
passwd postgres
# So that postgres can become root
without password, I add it in sudoers
echo "postgres ALL=(ALL)
NOPASSWD:ALL" >
/etc/sudoers.d/postgres
# install postgres release rpm, this
will add a file /etc/yum.repos.d/pgdg-11-centos.repo
yum install -y
https://download.postgresql.org/pub/repos/yum/${MAJORVER}/redhat/rhel-7-x86_64/pgdg-centos${MAJORVER}-${MAJORVER}-${MINORVER}.noarch.rpm
# with the repo added (repo is called
pgdg11), ww can install postgres
# NB: postgres is also available from
Centos base repos, but we want the latest version (11)
yum install -y postgresql${MAJORVER}-${MAJORVER}.${MINORVER} postgresql${MAJORVER}-server-${MAJORVER}.${MINORVER} postgresql${MAJORVER}-contrib-${MAJORVER}.${MINORVER}
# verify
yum list installed postgresql*
Quero armazenar o banco de dados postgres em /u01/pg11/data (local não
padrão), quero ter o wal arquivado em /u02/archive e o backup em /u02/backup
mkdir -p /u01/pg${MAJORVER}/data /u02/archive /u02/backup
chown postgres:postgres /u01/pg${MAJORVER}/data /u02/archive
/u02/backup
chmod 700 /u01/pg${MAJORVER}/data /u02/archive
A variável de ambiente PGDATA é importante: ela aponta para o local do
banco de dados. Precisamos alterá-la do rpm padrão (que é /var/lib/pgsql/11/data)
para o novo local /u01/pg11/data.
export PGDATA=/u01/pg${MAJORVER}/data
# add the binaries in the path of all
users
echo "export PATH=\$PATH:/usr/pgsql-${MAJORVER}/bin" > /etc/profile.d/postgres.sh
# source /etc/profile in bashrc of user
postgres, make sure that PGDATA is defined and also PGVER so that we
# can use PGVER in later scripts
echo "[ -f /etc/profile ] &&
source /etc/profile" >>
/home/postgres/.bashrc
echo "export PGDATA=/u01/pg${MAJORVER}/data" >>
/home/postgres/.bashrc
echo "export PGVER=${MAJORVER}" >>
/home/postgres/.bashrc
Precisamos de um arquivo de unidade
systemd para o postgres, para que ele seja iniciado automaticamente quando
inicializamos o servidor (veja https://www.postgresql.org/docs/11/server-start.html )
cat <<EOF >
/etc/systemd/system/postgresql.service
[Unit]
Description=PostgreSQL database server
Documentation=man:postgres(1)
[Service]
Type=notify
User=postgres
ExecStart=/usr/pgsql-11/bin/postgres -D
/u01/pg11/data
ExecReload=/bin/kill -HUP $MAINPID
KillMode=mixed
KillSignal=SIGINT
TimeoutSec=0
[Install]
WantedBy=multi-user.target
EOF
habilite a unidade, mas não a inicie ainda
sudo systemctl enable postgresql
Agora podemos inicializar o banco de dados, como usuário postgres
su - postgres
# check that PGDATA and the PATH are
correct
echo $PGDATA
echo $PATH
pg_ctl -D ${PGDATA} initdb -o "--auth=trust --encoding=UTF8
--locale='en_US.UTF8'"
Os parâmetros de configuração do postgres estão em $PGDATA/postgres.conf,
prefiro não alterar esse arquivo, mas incluir um arquivo adicional do diretório
config.d e substituir alguns dos parâmetros padrões. Eu adiciono a linha
“include_dir='conf.d'” no final do postgresql.conf e, em seguida, adiciono configurações
personalizadas em conf.d
# as user postgres
mkdir $PGDATA/conf.d
echo "include_dir = 'conf.d'" >> $PGDATA/postgresql.conf
# now let's add some config in this
conf.d directory
cat <<EOF > $PGDATA/conf.d/custom.conf
log_destination = 'syslog,csvlog'
logging_collector = on
# better to put the logs outside PGDATA
so they are not included in the base_backup
log_directory = '/var/log/postgres'
log_filename =
'postgresql-%Y-%m-%d.log'
log_truncate_on_rotation = on
log_rotation_age = 1d
log_rotation_size = 0
# These are relevant when logging to
syslog (if wanted, change log_destination to 'csvlog,syslog')
log_min_duration_statement=-1
log_duration = on
log_line_prefix='%m %c %u - '
log_statement = 'all'
log_connections = on
log_disconnections = on
log_checkpoints = on
log_timezone = 'Europe/Brussels'
# up to 30% of RAM. Too high is not
good.
shared_buffers = 512MB
#checkpoint at least every 15min
checkpoint_timeout = 15min
#if possible, be more restrictive
listen_addresses='*'
#for standby
max_replication_slots = 5
archive_mode = on
archive_command =
'/opt/postgres/scripts/archive.sh %p %f'
# archive_command = '/bin/true'
wal_level = replica
max_wal_senders = 5
hot_standby = on
hot_standby_feedback = on
# for pg_rewind
wal_log_hints=true
EOF
Eu prefiro colocar os logs fora do PGDATA, caso contrário um backup pode
ficar muito grande só por causa dos logs. Então precisamos criar o diretório
sudo mkdir /var/log/postgres
sudo chown postgres:postgres
/var/log/postgres
Como você pode ver, eu uso o script /opt/postgres/scripts/archive.sh
como comando de arquivamento, isso precisa ser criado
sudo mkdir -p
/opt/postgres/scripts
sudo chown -R postgres:postgres
/opt/postgres
conteúdo do script /opt/postgres/scripts/archive.sh
#!/bin/bash
LOGFILE=/var/log/postgres/archive.log
if [ ! -f $LOGFILE ] ; then
touch $LOGFILE
fi
echo "archiving $1 to /u02/archive/$2"
cp $1 /u02/archive/$2
exit $?
certifique-se de que é executável
chmod +x /opt/postgres/scripts/archive.sh
⚠️: Quando o modo de arquivamento
estiver ativado, você deve limpar o diretório onde os wals arquivados são
copiados. Normalmente, por meio de um script de backup que faz um backup e, em
seguida, remove os wals arquivados mais antigos do que o backup
Agora vamos criar um banco de dados e um usuário
sudo systemctl start postgresql
# verify
sudo systemctl status postgresql
psql -U postgres postgres
no prompt do psql, crie um banco de dados (meu banco de dados é chamado
critlib) e saia
create database critlib encoding
'UTF8' LC_COLLATE='en_US.UTF8';
\q
agora reconecte-se ao banco de dados recém-criado e crie dois usuários
(cl_owner e cl_user)
psql -U postgres critlib
create user cl_owner nosuperuser
nocreatedb login password 'cl_owner';
create schema cl_owner authorization
cl_owner;
create user cl_user nosuperuser
nocreatedb login password 'cl_user';
grant usage on schema cl_owner to
cl_user;
alter default privileges in schema
cl_owner grant select,insert,update,delete on tables to cl_user;
alter role cl_user set search_path to
"$user",cl_owner,public;
\q
Vamos abrir a porta 5432 do firewall
# as root
firewall-cmd --add-port 5432/tcp --permanent
systemctl restart firewalld
Antes de clonar o servidor (precisamos de 3 servidores), instalarei os
pacotes repmgr e pgpool. Depois disso, podemos clonar o servidor (é uma VM!),
configurar a replicação de streaming com repmgr e, em seguida, configurar o
pgpool
1.2 repmgr
Repmgr é uma boa ferramenta de código aberto feita pela 2ndquadrant. Não
é obrigatório usá-la e, a princípio, talvez valha a pena configurar a
replicação de streaming usando comandos postgres padrão, para fins de
aprendizado. Mas, de outra forma, vale muito a pena usar repmgr porque ele traz
scripts muito bem testados e documentados. Observe que repmgr também pode ser
usado para automatizar o failover (por meio do processo daemon repmgrd), mas
não queremos isso porque usaremos pgpool.
MAJORVER=11
REPMGRVER=4.2
curl
https://dl.2ndquadrant.com/default/release/get/${MAJORVER}/rpm | bash
yum install -y --enablerepo=2ndquadrant-dl-default-release-pg${MAJORVER} --disablerepo=pgdg${MAJORVER} repmgr${MAJORVER}-${REPMGRVER}
mkdir /var/log/repmgr && chown postgres:postgres
/var/log/repmgr
definir propriedade de /etc/repmgr para postgres
chown -R postgres:postgres /etc/repmgr
1.3 instalar pgpool
Isto é apenas sobre instalar o rpm, a configuração será feita mais
tarde. A configuração tem que ser feita em cada servidor, mas já podemos
instalar o pgpool antes de clonar o servidor. Adicionar o pgpool no Centos é
fácil: instale o rpm de lançamento do pgpool (ele criará um arquivo repo em
/etc/yum.repo.d) e então instale o próprio pgpool via yum.
export PGPOOLMAJOR=4.0
export PGPOOLVER=4.0.3
export PGVER=11
yum install -y http://www.pgpool.net/yum/rpms/${PGPOOLMAJOR}/redhat/rhel-7-x86_64/pgpool-II-release-${PGPOOLMAJOR}-1.noarch.rpm
yum install --disablerepo=pgdg11 --enablerepo=pgpool40 -y pgpool-II-pg11-${PGPOOLVER}
pgpool-II-pg11-extensions-${PGPOOLVER} pgpool-II-pg11-debuginfo-${PGPOOLVER}
``
I prefer to have pgpool running as user
postgres, so I will override the systemd unit file that was installed by the
rpm
```bash
# as user root
mkdir /etc/systemd/system/pgpool.service.d
cat <<EOF >
/etc/systemd/system/pgpool.service.d/override.conf
[Service]
User=postgres
Group=postgres
EOF
Como o pgpool se conecta aos vários servidores postgres via ssh,
adicionei algumas configurações ao cliente ssh para facilitar as conexões ssh
cat <<EOF > /etc/ssh/ssh_config
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
EOF
Como usarei o usuário postgres para iniciar o pgpool, alterarei a
propriedade de /opt/pgpool-II
sudo chown postgres:postgres -R /etc/pgpool-II
Também preciso criar um diretório para o arquivo pid e o arquivo socket
sudo mkdir /var/run/pgpool
sudo chown postgres:postgres
/var/run/pgpool
1.4 Clonar os servidores
Agora que tenho o pg01 pronto, vou cloná-lo para o pg02 e o pg03
Estou usando virsh (KVM) para
gerenciar meus convidados, esses são os comandos que usei para clonar minha VM
(a VM foi criada com este procedimento http://saule1508.github.io/libvirt-centos-cloud-image )
Observação: isso é principalmente para meu próprio registro. Sua maneira
de clonar uma VM pode ser diferente, é claro, ou você pode ter servidores
físicos ou pode usar o Ansible para automatizar o provisionamento...
# on my host
virsh shutdown pg01
sudo mkdir /u01/virt/{pg02,pg03}
sudo chown pierre:pierre
/u01/virt/pg02 /u01/virt/pg03
virt-clone -o pg01 -n pg02 --file /u01/virt/pg02/pg02.qcow2
--file
/u01/virt/pg02/pg02-disk1.qcow2
virt-clone -o pg01 -n pg03 --file
/u01/virt/pg03/pg03.qcow2 --file /u01/virt/pg03/pg03-disk1.qcow2
Agora inicie a VM, entre nela e altere seu endereço IP. Para ter um DNS,
também adiciono uma entrada em /etc/hosts do host KVM
# /etc/hosts of the KVM server, add one
entry per vm so that they can speak to each other via dns
# added
192.168.122.10 pg01.localnet
192.168.122.11 pg02.localnet
192.168.122.13 pg03.localnet
ao fazer isso, é preciso parar e iniciar a rede padrão e reiniciar o
libvirtd
virsh net-destroy default
virsh net-start default
sudo systemctl restart libvirtd
Conecte-se a cada VM via ssh, altere o endereço IP e o nome do host
(hostname-ctl set-hostname)
2. Configurar replicação de streaming com repmgr
Agora tenho 3 centos 7 VM com postgres, repmgr e pgpool instalados. As 3
VM podem resolver os nomes umas das outras via DNS, mas alternativamente
poderíamos colocar 3 entradas no arquivo /etc/hosts em cada VM
Hospedar |
Propriedade Intelectual |
pg01.localnet |
192.168.122.10 |
pg02.localnet |
192.168.122.11 |
pg03.localnet |
192.168.122.12 |
Usaremos o repmgr para configurar as 3 instâncias do postgres em uma
configuração primária - standby, onde o pg01 será o primário. O pg01 fará o
streaming de seus logs de gravação antecipada (wal) para o pg02 e o pg03.
Primeiro, queremos configurar chaves ssh para que cada servidor possa se
conectar com o usuário postgres. Em cada servidor, gere um par de chaves ssh
para o usuário postgres e transfira a chave pública para ambos os outros
servidores.
# on pg01 as user postgres. Keep the
default (no passphrase)
ss-keygen -t rsa
ssh-copy-id postgres@pg01.localnet
ssh-copy-id postgres@pg02.localnet
ssh-copy-id postgres@pg03.localnet
Do pg01 postgres o usuário deve ser capaz de se conectar ao pg02 e pg03,
mas também ao pg01 (ele mesmo) via ssh sem uma senha. Isso ocorre porque o
pgpool - em execução nesses servidores - se conectará via ssh a qualquer
servidor postgres para executar alguma operação (promoção de espera,
acompanhamento de espera, recuperação de nó)
faça o mesmo no servidor pg02 e no servidor pg03
2.1 banco de dados primário em pg01
Para a replicação de streaming, usaremos o usuário repmgr com senha
rep123. Vamos criá-lo.
crie o usuário repmgr
# on pg01 as user postgres
psql <<-EOF
create user repmgr with superuser login password 'rep123' ;
alter user repmgr set search_path to repmgr,"\$user",public;
\q
EOF
crie um banco de dados chamado repmgr
# on pg01 as user postgres
psql --command "create database repmgr with owner=repmgr
ENCODING='UTF8' LC_COLLATE='en_US.UTF8';"
Queremos nos conectar ao repmgr sem senha, é para isso que serve o
arquivo oculto pgpass.
# on pg01 as user postgres
echo "*:*:repmgr:repmgr:rep123" >
/home/postgres/.pgpass
echo "*:*:replication:repmgr:rep123" >>
/home/postgres/.pgpass
chmod 600 /home/postgres/.pgpass
scp /home/postgres/.pgpass
pg02.localnet:/home/postgres/.pgpass
scp /home/postgres/.pgpass
pg03.localnet:/home/postgres/.pgpass
Adicionar entradas em $PGDATA/pg_hba.conf para repmgr
# on pg01 as user postgres
cat <<EOF >> $PGDATA/pg_hba.conf
# replication manager
local
replication repmgr trust
host
replication repmgr 127.0.0.1/32 trust
host
replication repmgr 0.0.0.0/0 md5
local
repmgr repmgr trust
host
repmgr repmgr 127.0.0.1/32 trust
host
repmgr repmgr 127.0.0.1/32 trust
host
repmgr repmgr 0.0.0.0/0 md5
host
all all 0.0.0.0/0 md5
EOF
sudo systemctl restart postgresql
crie o arquivo de configuração repmgr
# on pg01 as user postgres
cat <<EOF > /etc/repmgr/11/repmgr.conf
node_id=1
node_name=pg01.localnet
conninfo='host=pg01.localnet
dbname=repmgr user=repmgr password=rep123 connect_timeout=2'
data_directory='/u01/pg11/data'
use_replication_slots=yes
#
event_notification_command='/opt/postgres/scripts/repmgrd_event.sh %n
"%e" %s "%t" "%d" %p %c %a'
reconnect_attempts=10
reconnect_interval=1
restore_command = 'cp /u02/archive/%f
%p'
log_facility=STDERR
failover=manual
monitor_interval_secs=5
pg_bindir='/usr/pgsql-11/bin'
service_start_command = 'sudo systemctl
start postgresql'
service_stop_command = 'sudo systemctl
stop postgresql'
service_restart_command = 'sudo
systemctl restart postgresql'
service_reload_command = 'pg_ctl
reload'
promote_command='repmgr -f
/etc/repmgr/11/repmgr.conf standby promote'
follow_command='repmgr -f
/etc/repmgr/11/repmgr.conf standby follow -W --upstream-node-id=%n'
EOF
Agora registre este banco de dados como sendo o banco de dados primário
# on pg01 as user postgres
repmgr -f /etc/repmgr/11/repmgr.conf -v master register
repmgr mantém informações sobre a topologia do cluster em uma tabela
chamada nodes
psql -U repmgr repmgr -c "select * from
nodes;"
Agora podemos habilitar a unidade systemd postgresql, para que o
postgres seja iniciado na próxima inicialização
# on pg01
sudo systemctl enable postgresql
2.2. Banco de dados standby em pg02
Agora podemos ir para o primeiro standby, pg02, e configurá-lo como um
standby
# on pg02 as user postgres
cat <<EOF > /etc/repmgr/11/repmgr.conf
node_id=2
node_name=pg02.localnet
conninfo='host=pg02.localnet
dbname=repmgr user=repmgr password=rep123 connect_timeout=2'
data_directory='/u01/pg11/data'
use_replication_slots=yes
#
event_notification_command='/opt/postgres/scripts/repmgrd_event.sh %n
"%e" %s "%t" "%d" %p %c %a'
reconnect_attempts=10
reconnect_interval=1
restore_command = 'cp /u02/archive/%f
%p'
log_facility=STDERR
failover=manual
monitor_interval_secs=5
pg_bindir='/usr/pgsql-11/bin'
service_start_command = 'sudo systemctl
start postgresql'
service_stop_command = 'sudo systemctl
stop postgresql'
service_restart_command = 'sudo
systemctl restart postgresql'
service_reload_command = 'pg_ctl
reload'
promote_command='repmgr -f
/etc/repmgr/11/repmgr.conf standby promote'
follow_command='repmgr -f
/etc/repmgr/11/repmgr.conf standby follow -W --upstream-node-id=%n'
EOF
vamos limpar o banco de dados e o wal arquivado
# make sure postgres is not running !
sudo systemctl stop postgresql
# on pg02 as user postgres
rm -rf /u01/pg11/data/*
rm -rf /u02/archive/*
E agora vamos configurar este servidor como um standby
repmgr -h pg01.localnet -U repmgr -d repmgr -D /u01/pg11/data -f
/etc/repmgr/11/repmgr.conf standby clone
sudo systemctl start postgresql
repmgr -f /etc/repmgr/11/repmgr.conf standby
register --force
se não funcionar, verifique se você consegue se conectar a pg01.localnet
(talvez você tenha esquecido o firewall?): psql -h pg01.localnet -U repmgr
repmgr deve funcionar
podemos habilitar a unidade postgresql para que o postgres inicie
automaticamente na próxima reinicialização
sudo systemctl enable postgresql
A tabela de nós agora deve ter dois registros
psql -U repmgr -c "select * from nodes;"
2.3. Banco de dados standby em pg03
Vamos fazer o mesmo no terceiro standby, pg03, e configurá-lo como um
standby
# on pg03 as user postgres
cat <<EOF > /etc/repmgr/11/repmgr.conf
node_id=3
node_name=pg03.localnet
conninfo='host=pg03.localnet
dbname=repmgr user=repmgr password=rep123 connect_timeout=2'
data_directory='/u01/pg11/data'
use_replication_slots=yes
#
event_notification_command='/opt/postgres/scripts/repmgrd_event.sh %n
"%e" %s "%t" "%d" %p %c %a'
reconnect_attempts=10
reconnect_interval=1
restore_command = 'cp /u02/archive/%f
%p'
log_facility=STDERR
failover=manual
monitor_interval_secs=5
pg_bindir='/usr/pgsql-11/bin'
service_start_command = 'sudo systemctl
start postgresql'
service_stop_command = 'sudo systemctl
stop postgresql'
service_restart_command = 'sudo systemctl
restart postgresql'
service_reload_command = 'pg_ctl
reload'
promote_command='repmgr -f
/etc/repmgr/11/repmgr.conf standby promote'
follow_command='repmgr -f
/etc/repmgr/11/repmgr.conf standby follow -W --upstream-node-id=%n'
EOF
vamos limpar o banco de dados e o wal arquivado
# make sure postgres is not running !
sudo systemctl stop postgresql
# on pg03 as user postgres
rm -rf /u01/pg11/data/*
rm -rf /u02/archive/*
E agora vamos configurar este servidor como um standby
repmgr -h pg01.localnet -U repmgr -d repmgr -D /u01/pg11/data -f
/etc/repmgr/11/repmgr.conf standby clone
sudo systemctl start postgresql
repmgr -f /etc/repmgr/11/repmgr.conf standby
register --force
podemos habilitar a unidade postgresql para que o postgres inicie
automaticamente na próxima reinicialização
sudo systemctl enable postgresql
Verifique os nós das tabelas repmgr
psql -U repmgr repmgr -c "select * from
nodes;"
2.4. Teste de replicação de streaming
# on pg01 as user postgres
# check the nodes table, it contains
meta-data for repmgr
psql -U repmgr repmgr -c "select * from
nodes;"
# create a test table
psql -U repmgr repmgr -c "create table
test(c1 int, message varchar(120)); insert into test values(1,'this is a
test');"
# check on pg02 if I can see the table
and if it is read-only
psql -h pg02.localnet -U repmgr repmgr <<EOF
select * from test;
drop table test;
EOF
# it will say: ERROR: cannot execute DROP TABLE in a read-only
transaction
# check on pg03 if I can see the table
and if it is read-only
psql -h pg03.localnet -U repmgr repmgr <<EOF
select * from test;
drop table test;
EOF
# drop the table on pg01
psql -U repmgr repmgr -c "drop table
test;"
Experimente um pouco com operações básicas: failover, switch-over,
seguir um novo mestre, reingressar em um primário com falha, etc. O repmgr tem
scripts para tudo isso.
3. pgpool
Agora é hora de configurar o pgpool em nossos 3 servidores. O pgpool
pode ser usado como um cache de conexão, mas - mais importante - ele é usado
para automatizar o failover do postgres e torná-lo (quase) transparente para os
aplicativos clientes.
Lembre-se de que o postgres se torna HA (Alta Disponibilidade) por meio
da replicação de streaming e que o pgpool se torna HA por meio de um IP Virtual
(VIP) gerenciado pelo modo watchdog.
A linha a seguir deve ser adicionada à configuração do postgres (não
tenho certeza do porquê e se ainda é necessária)
echo "pgpool.pg_ctl='/usr/pgsql-11/bin/pg_ctl'" >> $PGDATA/conf.d/custom.conf
3.1. Arquivos pgool_hba e pool_passwd
semelhante ao mecanismo de autenticação baseado em host do postgres
(pg_hba), o pgpool tem um arquivo pool_hba.conf.
cat <<EOF > /etc/pgpool-II/pool_hba.conf
local
all all trust
# IPv4 local connections:
host
all all 0.0.0.0/0 md5
EOF
scp /etc/pgpool-II/pool_hba.conf
postgres@pg02.localnet:/etc/pgpool-II/
scp /etc/pgpool-II/pool_hba.conf
postgres@pg03.localnet:/etc/pgpool-II/
Como uso md5, preciso ter o arquivo
pool_passwd contendo a senha com hash md5 http://www.pgpool.net/docs/latest/en/html/runtime-config-connection.html#GUC-POOL-PASSWD
# first dump the info into a temp file
psql -c "select rolname,rolpassword from
pg_authid;" > /tmp/users.tmp
touch /etc/pgpool-II/pool_passwd
# then go through the file to
remove/add the entry in pool_passwd file
cat /tmp/users.tmp | awk 'BEGIN
{FS="|"}{print $1" "$2}' | grep md5 | while read f1 f2
do
echo "setting
passwd of $f1 in /etc/pgpool-II/pool_passwd"
# delete the line
if exits
sed -i -e "/^${f1}:/d"
/etc/pgpool-II/pool_passwd
echo $f1:$f2 >>
/etc/pgpool-II/pool_passwd
done
scp /etc/pgpool-II/pool_passwd
pg02:/etc/pgpool-II/pool_passwd
scp /etc/pgpool-II/pool_passwd
pg03:/etc/pgpool-II/pool_passwd
⚠️ Toda vez que um novo usuário do
postgres é adicionado ou sua senha é alterada, este pequeno procedimento deve
ser executado novamente
3.2. Configurando o arquivo pcp.conf
pcp (protocolo de controle pgpool) é
uma interface administrativa para pgpool, ele permite que você interaja com
pgpool via porta 9898 (padrão). O arquivo pcp.conf armazena um nome de usuário
e uma senha md5 para autenticação, o arquivo .pcppass permite que um usuário
use comandos pcp sem senha. Note que este usuário/senha não está relacionado a
um usuário postgres, é apenas um usuário pcp que pode falar com pgpool via
protocolo pcp. http://www.pgpool.net/docs/latest/en/html/configuring-pcp-conf.html
Vou usar o usuário postgres com senha secreta
# user postgres on pg01
echo "postgres:$(pg_md5 secret)" >>
/etc/pgpool-II/pcp.conf
scp /etc/pgpool-II/pcp.conf
pg02:/etc/pgpool-II/pcp.conf
scp /etc/pgpool-II/pcp.conf
pg03:/etc/pgpool-II/pcp.conf
echo "*:*:postgres:secret" >
/home/postgres/.pcppass
chown postgres:postgres
/home/postgres/.pcppass
chmod 600 /home/postgres/.pcppass
scp /home/postgres/.pcppass
pg02:/home/postgres/.pcppass
scp /home/postgres/.pcppass
pg03:/home/postgres/.pcppass
por causa deste .pcppass poderemos usar o pcp sem senha quando logado
como usuário unix postgres.
No modo watchdog, o pgpool precisará executar os comandos ip e arping
com o usuário postgres, então definimos o sticky bit nesses dois utilitários.
chmod 4755 /usr/sbin/ip /usr/sbin/arping
3.3. Arquivo de configuração do pgpool: pgpool.conf
Os 3 servidores usados na configuração são pg01.localnet,
pg02.localnet e pg03.localnet. Também precisamos de um VIP (IP virtual, também
chamado delegate_ip no contexto do pgpool). No meu caso, o VIP será
192.168.122.50.
Esta é a configuração que eu uso para o primeiro servidor, explicarei
abaixo. Tome cuidado porque, como eu faço um redirecionamento cat para a
configuração, tenho que escapar os cifrões na configuração. Preste atenção ao
if_up_cmd abaixo, por exemplo, escapei o $, mas no arquivo ele não deve ser
escapado.
# as user postgres on pg01
CONFIG_FILE=/etc/pgpool-II/pgpool.conf
cat <<EOF > $CONFIG_FILE
listen_addresses = '*'
port = 9999
socket_dir = '/var/run/pgpool'
pcp_listen_addresses = '*'
pcp_port = 9898
pcp_socket_dir = '/var/run/pgpool'
listen_backlog_multiplier = 2
serialize_accept = off
enable_pool_hba = on
pool_passwd = 'pool_passwd'
authentication_timeout = 60
ssl = off
num_init_children = 100
max_pool = 5
# - Life time -
child_life_time = 300
child_max_connections = 0
connection_life_time = 600
client_idle_limit = 0
log_destination='stderr'
debug_level = 0
pid_file_name =
'/var/run/pgpool/pgpool.pid'
logdir = '/tmp'
connection_cache = on
reset_query_list = 'ABORT; DISCARD ALL'
#reset_query_list = 'ABORT; RESET ALL;
SET SESSION AUTHORIZATION DEFAULT'
replication_mode = off
load_balance_mode = off
master_slave_mode = on
master_slave_sub_mode = 'stream'
backend_hostname0 = 'pg01.localnet'
backend_port0 = 5432
backend_weight0 = 1
backend_data_directory0 =
'/u01/pg11/data'
backend_flag0 = 'ALLOW_TO_FAILOVER'
backend_hostname1 = 'pg02.localnet'
backend_port1 = 5432
backend_weight1 = 1
backend_data_directory1 =
'/u01/pg11/data'
backend_flag1 = 'ALLOW_TO_FAILOVER'
backend_hostname2 = 'pg03.localnet'
backend_port2 = 5432
backend_weight2 = 1
backend_data_directory2 =
'/u01/pg11/data'
backend_flag2 = 'ALLOW_TO_FAILOVER'
# this is about checking the postgres
streaming replication
sr_check_period = 10
sr_check_user = 'repmgr'
sr_check_password = 'rep123'
sr_check_database = 'repmgr'
delay_threshold = 10000000
# this is about automatic failover
failover_command =
'/opt/pgpool/scripts/failover.sh %d %h
%P %m %H %R'
# not used, just echo something
failback_command = 'echo failback %d %h
%p %D %m %H %M %P'
failover_on_backend_error = 'off'
search_primary_node_timeout = 300
# Mandatory in a 3 nodes set-up
follow_master_command =
'/opt/pgpool/scripts/follow_master.sh %d %h %m %p %H %M %P'
# grace period before triggering a
failover
health_check_period = 40
health_check_timeout = 10
health_check_user = 'hcuser'
health_check_password = 'hcuser'
health_check_database = 'postgres'
health_check_max_retries = 3
health_check_retry_delay = 1
connect_timeout = 10000
#------------------------------------------------------------------------------
# ONLINE RECOVERY
#------------------------------------------------------------------------------
recovery_user = 'repmgr'
recovery_password = 'rep123'
recovery_1st_stage_command =
'pgpool_recovery.sh'
recovery_2nd_stage_command = 'echo
recovery_2nd_stage_command'
recovery_timeout = 90
client_idle_limit_in_recovery = 0
#------------------------------------------------------------------------------
# WATCHDOG
#------------------------------------------------------------------------------
use_watchdog = on
# trusted_servers =
'www.google.com,pg02.localnet,pg03.localnet' (not needed with a 3 nodes cluster
?)
ping_path = '/bin'
wd_hostname = pg01.localnet
wd_port = 9000
wd_priority = 1
wd_authkey = ''
wd_ipc_socket_dir = '/var/run/pgpool'
delegate_IP = '192.168.122.99'
if_cmd_path = '/opt/pgpool/scripts'
if_up_cmd = 'ip_w.sh addr add \$_IP_\$/24 dev eth0 label
eth0:0'
if_down_cmd = 'ip_w.sh addr del \$_IP_\$/24 dev eth0'
arping_path = '/opt/pgpool/scripts'
arping_cmd = 'arping_w.sh -U \$_IP_\$ -I eth0 -w 1'
# - Behaivor on escalation Setting -
heartbeat_destination0 =
'pg01.localnet'
heartbeat_destination_port0 = 9694
heartbeat_destination1 =
'pg02.localnet'
heartbeat_destination_port1 = 9694
heartbeat_destination2 =
'pg03.localnet'
heartbeat_destination_port2 = 9694
other_pgpool_hostname0 =
'pg02.localnet'
other_pgpool_port0 = 9999
other_wd_port0 = 9000
other_pgpool_hostname1 =
'pg03.localnet'
other_pgpool_port1 = 9999
other_wd_port1 = 9000
EOF
Vamos copiar o arquivo para pg02.localnet e pg03.localnet e fazer as
adaptações necessárias nas seções
scp /etc/pgpool-II/pgpool.conf
pg02.localnet:/etc/pgpool-II/pgpool.conf
em pg02.localnet, mudanças
wd_hostname = pg02.localnet
other_pgpool_hostname0 =
'pg01.localnet'
other_pgpool_port0 = 9999
other_wd_port0 = 9000
other_pgpool_hostname1 =
'pg03.localnet'
other_pgpool_port1 = 9999
other_wd_port1 = 9000
E o mesmo para pg03.localnet
⚠️ quando há um problema com o watchdog
do pgpool, o motivo geralmente é que a configuração não está correta, por
exemplo, alguém esqueceu de adaptar a seção other_pgpool_hostname em cada nó.
3.4. Alguns parâmetros do pgpool explicados:
parâmetros relacionados à replicação de
streaming.
Prefiro definir load_balance como 'off', mas pode ser um recurso útil em
alguns casos. Ao definir master_slave_mode como on e sub mode como stream,
estamos dizendo ao pgpool que usamos o mecanismo de replicação de streaming
padrão do postgres.
load_balance_mode = off
master_slave_mode = on
master_slave_sub_mode = 'stream'
A seção backend descreve os 3 postgres
backend_hostname0 = 'pg01.localnet'
backend_port0 = 5432
backend_weight0 = 1
backend_data_directory0 = '/u01/pg11/data'
backend_flag0 = 'ALLOW_TO_FAILOVER'
etc... for pg02 and pg03
num_init_children e max_pool estão
relacionados à funcionalidade de cache de conexão do pgpool.
Quando o pgpool inicia, ele cria processos clientes “num_init_children”
que estarão todos em um estado “aguardando uma conexão”. Quando um aplicativo
cliente se conecta ao pgpool, o pgpool usa um dos processos filhos que está
livre e estabelece uma conexão com o postgres. Quando o cliente se desconecta,
o pgpool mantém a conexão com o postgres aberta (cache de conexão) para que da
próxima vez que um cliente se conectar ao pgpool, a conexão com o postgres
possa ser reutilizada e evitemos o custo de criar uma nova conexão. Podemos
desabilitar esse cache de conexão se preferirmos ter o pool no nível do
aplicativo.
parâmetros relacionados ao failover
automático
failover_command é o script
que será executado quando o pgpool detectar (por meio do mecanismo de
verificação de integridade no meu caso) que o banco de dados primário está
inativo. Não sei o que o failback_command deve
fazer, eu simplesmente não o uso. O follow_master_command é
executado em todos os nós de espera após o failover_command ter sido executado
e um novo banco de dados primário ter sido detectado. Mostrarei os scripts
correspondentes mais adiante neste documento.
failover_command =
'/opt/pgpool/scripts/failover.sh %d %h
%P %m %H %R'
failback_command = 'echo failback %d %h
%p %D %m %H %M %P'
search_primary_node_timeout = 300
follow_master_command =
'/opt/pgpool/scripts/follow_master.sh %d %h %m %p %H %M %P'
A primeira razão pela qual as pessoas precisam do pgpool é porque o
pgpool pode automatizar o failover e torná-lo transparente para os aplicativos
clientes. Existem basicamente dois mecanismos para disparar um failover. Ou via
failover_on_backend_error ou via health checks. Eu prefiro definir
failover_on_backend_error como off e usar o mecanismo de health check para
disparar o failover. O mecanismo de health check significa que o pgpool se
conectará regularmente ao postgres, se uma conexão falhar (para um primário ou
para um standby), então após o número de tentativas ele disparará o failover.
failover_on_backend_error = 'off'
health_check_period = 40
health_check_timeout = 10
health_check_user = 'hcuser'
health_check_password = 'hcuser'
health_check_database = 'postgres'
health_check_max_retries = 3
health_check_retry_delay = 1
Se você definir failover_on_backend_error como 'on', então o pgpool
acionará o failover quando um dos processos filhos detectar que o postgres foi
encerrado. Nesse caso, não há nova tentativa. Observe também que, nesse caso,
enquanto nenhum aplicativo se conectar ao pgpool ou executar uma consulta no
banco de dados, nenhum failover será acionado.
Não confunda a verificação de integridade com a verificação de replicação
de streaming (a verificação de replicação de streaming verifica o atraso na
replicação, se o atraso for muito grande, um standby não será usado para
balanceamento de carga de leitura e não será considerado para promoção). Por
alguma razão estranha, o pgpool usa dois usuários diferentes para isso.
parâmetros relacionados ao watch-dog
O mecanismo watch-dog é uma funcionalidade muito legal do pgpool:
múltiplas instâncias do pgpool (3 no nosso caso) estão em uma configuração
ativa-passiva, na inicialização o cluster elege um líder e o líder adquire o
VIP (delegate_ip). Os 3 nós estão monitorando um ao outro e se o primário
falhar, então um novo líder será eleito e o VIP será movido para este novo
líder.
Somente o nó líder pgpool executará os comandos failover, follow_master,
etc.
O modo watchdog deve ser habilitado via use_watchdog ('on')
Os nós monitoram uns aos outros por meio do mecanismo de pulsação,
enviando um pacote na porta UDP 9694 para os outros nós
heartbeat_destination0 =
'pg01.localnet'
heartbeat_destination_port0 = 9694
heartbeat_destination1 =
'pg02.localnet'
heartbeat_destination_port1 = 9694
heartbeat_destination2 =
'pg03.localnet'
heartbeat_destination_port2 = 9694
O VIP é adquirido por meio de um script que deve ser instalado no host.
if_cmd_path = '/opt/pgpool/scripts'
if_up_cmd = 'ip_w.sh addr add $_IP_$/24
dev eth0 label eth0:0'
if_down_cmd = 'ip_w.sh addr del
$_IP_$/24 dev eth0'
arping_path = '/opt/pgpool/scripts'
arping_cmd = 'arping_w.sh -U $_IP_$ -I
eth0 -w 1'
No comando acima, o pgpool
substituirá $ IP $ pelo valor do parâmetro
delegate_ip
delegate_ip = 192.168.122.99
cada nó deve estar ciente dos outros, então em pg01
wd_hostname = pg01
wd_port = 9000
wd_priority = 1
wd_authkey = ''
wd_ipc_socket_dir = '/var/run/pgpool'
other_pgpool_hostname0 =
'pg02.localnet'
other_pgpool_port0 = 9999
other_wd_port0 = 9000
other_pgpool_hostname1 =
'pg03.localnet'
other_pgpool_port1 = 9999
other_wd_port1 = 9000
destino do log
Eu uso o log_destination padrão = 'stderr' e olho os logs via journalctl
sudo journalctl --unit pgpool
3.5. preparação do pgpool
precisamos criar o usuário postgres para a verificação de integridade
# on pg01 as user postgres
psql -c "create user hcuser with login
password 'hcuser';"
Precisamos abrir algumas portas de firewall, para pgpool (9999 e 9898
para pcp) e para a funcionalidade watch-dog
# on all 3 servers
# port for postgres (was already done)
sudo firewall-cmd --add-port 9898/tcp --permanent
# ports for pgpool and for pcp
sudo firewall-cmd --add-port 9898/tcp --permanent
sudo firewall-cmd --add-port 9999/tcp --permanent
sudo firewall-cmd --add-port 9000/tcp --permanent
sudo firewall-cmd --add-port 9694/udp --permanent
sudo systemctl restart firewalld
Diretório para arquivo pid
# on all servers
sudo chown postgres:postgres
/var/run/pgpool
sudo mkdir /var/log/pgpool
sudo chown postgres:postgres
/var/log/pgpool
Os scripts pgpool farão login em /var/log/pgpool.
Se você quiser também logs do próprio pgpool, então use as
log_destination = 'stderr,syslog' e defina syslog_facility para LOCAL1, e
configure syslog. Adicione a /etc/rsyslog.conf
local1.* /var/log/pgpool/pgpool.log
então reinicie o daemon
sudo systemctl restart rsyslog
Nota: não tenho certeza se isso está funcionando, não testei.
3.5. Scripts do Pgpool
sudo mkdir -p
/opt/pgpool/scripts
sudo chown postgres:postgres
/opt/pgpool/scripts
script de failover
Copie e cole o seguinte em /opt/pgpoo/scripts/failover.sh
#!/bin/bash
LOGFILE=/var/log/pgpool/failover.log
if [ ! -f $LOGFILE ] ; then
> $LOGFILE
fi
# we need this, otherwise it is not set
PGVER=${PGVER:-11}
#
#failover_command =
'/scripts/failover.sh %d %h %P %m %H %R'
# Executes this command at failover
# Special values:
# %d = node id
# %h =
host name
# %p = port number
# %D = database cluster path
# %m = new master node id
# %H = hostname of the new master node
# %M = old master node id
# %P = old primary node id
#
FALLING_NODE=$1 # %d
FALLING_HOST=$2 # %h
OLD_PRIMARY_ID=$3 # %P
NEW_PRIMARY_ID=$4 # %m
NEW_PRIMARY_HOST=$5 # %H
NEW_MASTER_PGDATA=$6 # %R
(
date
echo "FALLING_NODE: $FALLING_NODE"
echo "FALLING_HOST: $FALLING_HOST"
echo "OLD_PRIMARY_ID: $OLD_PRIMARY_ID"
echo "NEW_PRIMARY_ID: $NEW_PRIMARY_ID"
echo "NEW_PRIMARY_HOST: $NEW_PRIMARY_HOST"
echo "NEW_MASTER_PGDATA: $NEW_MASTER_PGDATA"
ssh_options="ssh -p 22 -n
-T -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
set -x
if [ $FALLING_NODE = $OLD_PRIMARY_ID ] ; then
$ssh_options postgres@${NEW_PRIMARY_HOST} "/usr/pgsql-${PGVER}/bin/repmgr
--log-to-file -f /etc/repmgr/${PGVER}/repmgr.conf standby promote -v "
fi
exit 0;
torná-lo executável
seguir_mestre
Copie o seguinte em /opt/pgpool/scripts/follow_master.sh
#!/bin/bash
LOGFILE=/var/log/pgpool/follow_master.log
if [ ! -f $LOGFILE ] ; then
> $LOGFILE
fi
PGVER=${PGVER:-11}
echo "executing follow_master.sh at `date`" | tee -a $LOGFILE
NODEID=$1
HOSTNAME=$2
NEW_MASTER_ID=$3
PORT_NUMBER=$4
NEW_MASTER_HOST=$5
OLD_MASTER_ID=$6
OLD_PRIMARY_ID=$7
PGDATA=${PGDATA:-/u01/pg${PGVER}/data}
(
echo NODEID=${NODEID}
echo HOSTNAME=${HOSTNAME}
echo NEW_MASTER_ID=${NEW_MASTER_ID}
echo PORT_NUMBER=${PORT_NUMBER}
echo NEW_MASTER_HOST=${NEW_MASTER_HOST}
echo OLD_MASTER_ID=${OLD_MASTER_ID}
echo OLD_PRIMARY_ID=${OLD_PRIMARY_ID}
echo PGDATA=${PGDATA}
if [ $NODEID -eq $OLD_PRIMARY_ID ] ; then
echo "Do nothing as
this is the failed master. We could prevent failed master to restart here, so
that we can investigate the issue" | tee -a $LOGFILE
else
ssh_options="ssh -p 22 -n
-T -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
set -x
# if
this node is not currently standby then it might be an old master that went
back up after a failover occured
# if
this is the case we cannot do the follow master command on this node, we should
leave it alone
in_reco=$( $ssh_options postgres@${HOSTNAME} 'psql -t -c
"select pg_is_in_recovery();"' | head -1 | awk '{print $1}' )
echo "pg_is_in_recovery
on $HOSTNAME is $in_reco " | tee -a $LOGFILE
if [ "a${in_reco}" != "at" ] ; then
echo "node $HOSTNAME is not in
recovery, probably a degenerated master, skip it" | tee -a $LOGFILE
exit
0
fi
$ssh_options postgres@${HOSTNAME} "/usr/pgsql-${PGVER}/bin/repmgr
--log-to-file -f /etc/repmgr/${PGVER}/repmgr.conf -h ${NEW_MASTER_HOST} -D ${PGDATA} -U repmgr -d
repmgr standby follow -v "
#
TODO: we should check if the standby follow worked or not, if not we should
then do a standby clone command
echo "Sleep
10"
sleep
10
echo "Attach node ${NODEID}"
pcp_attach_node -h localhost -p 9898 -w ${NODEID}
fi
) 2>&1 | tee -a $LOGFILE
script pgpool_recovery
o script pgpool_recovery.sh deve ser instalado em $PGDATA, deve ser de
propriedade do postgres e ter permissão de execução. Este script será chamado
via extensão pgpool_recovery do postgres, então precisamos primeiro instalar
esta extensão.
# on the primary db only
psql -c "create extension
pgpool_recovery;" -d template1
psql -c "create extension
pgpool_adm;"
pgpool_adm é outra extensão que você pode querer instalar
crie o arquivo $PGDATA/pgpool_recovery.sh (no meu caso $PGDATA é
/u01/pg11/data). Eu também instalo esse script em /opt/pgpool/scripts. para
manter uma cópia.
#!/bin/bash
# This script erase an existing replica
and re-base it based on
# the current primary node. Parameters
are position-based and include:
#
# 1 - Path to primary database
directory.
# 2 - Host name of new node.
# 3 - Path to replica database
directory
#
# Be sure to set up public SSH keys and
authorized_keys files.
# this script must be in PGDATA
PGVER=${PGVER:-11}
ARCHIVE_DIR=/u02/archive
LOGFILE=/var/log/pgool/pgpool_recovery.log
if [ ! -f $LOGFILE ] ; then
touch $LOGFILE
fi
log_info(){
echo $( date +"%Y-%m-%d
%H:%M:%S.%6N" ) - INFO - $1 | tee -a $LOGFILE
}
log_error(){
echo $( date +"%Y-%m-%d
%H:%M:%S.%6N" ) - ERROR - $1 | tee -a $LOGFILE
}
log_info "executing pgpool_recovery at `date` on `hostname`"
PATH=$PATH:/usr/pgsql-${PGVER}/bin
if [ $# -lt 3 ]; then
echo "Create a
replica PostgreSQL from the primary within pgpool."
echo
echo "Usage: $0 PRIMARY_PATH
HOST_NAME COPY_PATH"
echo
exit 1
fi
# to do is hostname -i always OK ? Find
other way to extract the host. maybe from repmgr.conf ?
primary_host=$(hostname -i) # not working on
SCM
replica_host=$2
replica_path=$3
log_info "primary_host: ${primary_host}"
log_info "replica_host: ${replica_host}"
log_info "replica_path: ${replica_path}"
ssh_copy="ssh -p 22 postgres@$replica_host -T -n -o
UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
log_info "Stopping postgres on ${replica_host}"
$ssh_copy "sudo systemctl stop
postgresql"
log_info sleeping 10
sleep 10
log_info "delete database directory on ${replica_host}"
$ssh_copy "rm -Rf $replica_path/* $ARCHIVE_DIR/*"
log_info "let us use repmgr on the replica
host to force it to sync again"
$ssh_copy "/usr/pgsql-${PGVER}/bin/repmgr -h ${primary_host} --username=repmgr
-d repmgr -D ${replica_path} -f /etc/repmgr/${PGVER}/repmgr.conf
standby clone -v"
log_info "Start database on ${replica_host} "
$ssh_copy "sudo systemctl start
postgresql"
log_info sleeping 20
sleep 20
log_info "Register standby database"
$ssh_copy "/usr/pgsql-${PGVER}/bin/repmgr -f
/etc/repmgr/${PGVER}/repmgr.conf
standby register -F -v"
Não se esqueça de instalá-lo em $PGDATA e definir a permissão de
execução
chmod 774
/opt/pgpool/scripts/pgpool_recovery.sh
cp /opt/pgpool/scripts/pgpool_recovery.sh
$PGDATA/pgpool_recovery.sh
scp
/opt/pgpool/scripts/pgpool_recovery.sh pg02.localnet:$PGDATA/
scp
/opt/pgpool/scripts/pgpool_recovery.sh pg03.localnet:$PGDATA/
para o modo watchdog, precisamos de dois scripts adicionais
scripts ip
Crie o arquivo /opt/pgool/scripts/ip_w.sh
#!/bin/bash
echo "Exec ip with params $@ at `date`"
sudo /usr/sbin/ip $@
exit $?
Crie o arquivo /opt/pgpool/scripts/arping_w.sh
#!/bin/bash
echo "Exec arping with params $@ at `date`"
sudo /usr/sbin/arping $@
exit $?
Agora defina o modo de execução em todos os scripts e copie-os para pg02
e pg03
chmod 774 /opt/pgpool/scripts/*
scp /opt/pgpool/scripts/*
pg02.localnet:/opt/pgpool/scripts/
scp /opt/pgpool/scripts/*
pg03.localnet:/opt/pgpool/scripts/
4. Teste a configuração
Antes de iniciar o pgpool nos nós pg01.localnet, pg02.localnet e
pg03.localnet, vamos fazer as verificações finais
verificar replicação de streaming
# on pg01
repmgr -f /etc/repmgr/11/repmgr.conf cluster
show
habilitar serviços postgressql e
pgpool em todos os servidores
sudo systemctl enable postgresql
sudo systemctl enable pgpool
verifique o firewall em todos os
servidores
sudo firewall-cmd --list-all
verifique a conectividade ssh entre
todos os servidores
lembre-se de que o pg01 também se conectará a si mesmo via ssh, e o
pg02/3 também
inicie o pgpool em pg01.localnet, pg02.localnet e pg03.localnet
sudo systemctl start pgpool
assim que houver quorum (dois nós), o pgpool elegerá um líder e
adquirirá o VIP
Como o pgpool está registrando no stderr, podemos ver os logs com journalctl
sudo journalctl --unit pgpool -f
Dê uma boa olhada nos logs, eles são um pouco prolixos, mas muito
relevantes. Depois de iniciar o pgpool em pg02.localnet e pg03.localnet, você
deve ver no log que um master foi eleito e que o VIP foi adquirido. O VIP
estará no nó master, no meu caso pg01.localnet (ele começou primeiro)
obter informações sobre o cluster pgpool
pcp_watchdog_info -h 192.168.122.99 -p 9898 -w -v
o sinalizador -w é para evitar solicitação de senha, ele requer ter a
senha pcp para postgres em /home/postgres/.pcppass (a senha md5 é definida em
/etc/pgpool-II/pcp.conf)
# result of ip addr
[postgres@pg01 pgpool-II]$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu
65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0:
<BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP
group default qlen 1000
link/ether 52:54:00:b5:58:ec brd ff:ff:ff:ff:ff:ff
inet 192.168.122.10/24 brd 192.168.122.255 scope global eth0
valid_lft forever preferred_lft forever
inet 192.168.122.99/24 scope global secondary eth0:0
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:feb5:58ec/64 scope link
valid_lft forever preferred_lft forever
a informação importante é que o IP 192.168.122.99 foi configurado no
eth0 com o rótulo eth0:0
4.1. failover pgpool
antes de testar o failover, certifique-se de que todos os nós podem se
conectar uns aos outros via ssh sem um prompt de senha
# on pg01, pg02 and pg03
ssh -p 22 postgres@pg01.localnet -T -n -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "repmgr -f
/etc/repmgr/${PGVER}/repmgr.conf
--help"
ssh -p 22 postgres@pg02.localnet -T -n -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "repmgr -f
/etc/repmgr/${PGVER}/repmgr.conf
--help"
ssh -p 22 postgres@pg03.localnet -T -n -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "repmgr -f
/etc/repmgr/${PGVER}/repmgr.conf
--help"
para testar um failover do pgpool precisamos parar o pgpool no master e
ver se um novo master é eleito e se o VIP é movido para o novo master. Siga os
logs durante essa operação porque ele fornece todas as informações relevantes.
sudo systemctl stop pgpool
Verifique se o VIP foi movido.
reiniciar pgpool
sudo systemctl start pgpool
4.2. failover postgres
Vamos ver o estado do pgpool
psql -h 192.168.122.99 -p 9999 -U repmgr -c "show
pool_nodes;"
vamos ver também as estatísticas de replicação no primário e no standby
# pg01 is the primary
psql -h pg01.localnet -p 5432 -U repmgr -c "select * from
pg_stat_replication;"
psql -h pg02.localnet -p 5432 -U repmgr -c "select * from
pg_stat_wal_receiver;"
psql -h pg03.localnet -p 5432 -U repmgr -c "select * from
pg_stat_wal_receiver;"
como pg01.localnet é o primário, vou interrompê-lo e o pgpool deve
promover o pg02 para primário (script de failover) e o pg03 deve agora seguir o
novo primário.
# on pg01
sudo systemctl stop postgresql
Verifique novamente o status do pgpool
psql -h 192.168.122.99 -p 9999 -U repmgr -c "show
pool_nodes;"
verifique os arquivos de log failover.log e follow_master.log em
/var/log/pgool no nó mestre pgpool
verifique com select * from pg_stat_wal_receiver; que o segundo stand-by
segue o novo primário e verifique com select * from pg_stat_replication; que o
primário transmite para o standby
4.3. recuperar nó com falha
Existem diferentes maneiras de rebasear o primário com falha como um
standby
usando repmgr
assumindo que o novo primário é pg02.localnet, executando o seguinte
para que pg01.localnet se junte novamente ao cluster como um standby. Observe
que nem sempre funcionará
repmgr node rejoin -d 'host=pg02.localnet
dbname=repmgr user=repmgr password=rep123 connect_timeout=2'
e então reconecte o nó ao pgpool
pcp_attach_node -h 192.168.122.99 -p 9898 -w 0
Mas a maneira mais segura é reconstruí-lo, usando pcp_recovery_node. O
pgpool usará os parâmetros recovery_user e recovery_password para conectar-se
ao banco de dados e executar “select pg_recovery(…)”. A função pgpool_recovery
foi criada quando instalamos a extensão pgpool.
No nosso caso tivemos
recovery_user = 'repmgr'
recovery_password = 'rep123'
pcp_recovery_node -h 192.168.122.99 -p 9898 -w 0
e então reconecte o nó
pcp_attach_node -h 192.168.122.99 -p 9898 -w 0
4.4. Recuperação automática de um mestre ou standby com falha
Na minha empresa, eles queriam que um master com falha (ou um standby
com falha) que voltasse à vida fosse automaticamente colocado de volta no
cluster como um standby. Por exemplo, supondo este cenário:
pg01 é o primário
o servidor pg01 está desligado
pg02 se torna o primário, pg03 segue pg02
o servidor pg01 está ligado
postgres no pg01 retorna, está em modo de leitura e gravação, porém como está
separado do pgpool estamos protegidos de um split-brain
queremos que o pg01 seja recuperado automaticamente e se torne um
standby do pg02 e se junte novamente ao cluster pgpool
Outro cenário é quando um servidor em espera é reinicializado: o pgpool
desanexará o banco de dados em espera, mas quando o servidor retornar, o pgpool
não o reconectará.
Minha solução foi agendar um script via cron. O script é o seguinte.
Tome cuidado para que ele seja adaptado no pg02 e no pg03, a variável
PG_NODE_ID é 0 no pg01, 1 no pg02 e 2 no pg03. Adapte também a var DELEGATE_IP
(192.168.122.99 para mim)
# content of
/opt/postgres/scripts/recover_failed_node.sh (can be scheduled via cron)
#!/bin/bash
# this script will re-attach a failed
standby database
# or recover a failed primary database
# it requires that pgpool is available
and that the database on this node is running
# this script might be called when the
postgres container is starting but then it must do so
# when both pgpool and the database is
running. Since the db is started with supervisor, this would
# require to lauch the script in the
background after the start of postgres
# the script can also be started
manually or via cron
# Created by argbash-init v2.6.1
#
ARG_OPTIONAL_BOOLEAN([auto-recover-standby],[],[reattach a standby to pgpool if
possible],[on])
#
ARG_OPTIONAL_BOOLEAN([auto-recover-primary],[],[recover the degenerated
master],[off])
#
ARG_OPTIONAL_SINGLE([lock-timeout-minutes],[],[minutes after which a lock will
be ignored (optional)],[120])
# ARG_HELP([<The general help
message of my script>])
# ARGBASH_GO()
# needed because of Argbash -->
m4_ignore([
### START OF CODE GENERATED BY Argbash
v2.6.1 one line above ###
# Argbash is a bash code generator used
to get arguments parsing right.
# Argbash is FREE SOFTWARE, see
https://argbash.io for more info
die()
{
local _ret=$2
test -n "$_ret" || _ret=1
test "$_PRINT_HELP" = yes && print_help >&2
echo "$1" >&2
exit ${_ret}
}
begins_with_short_option()
{
local first_option
all_short_options
all_short_options='h'
first_option="${1:0:1}"
test "$all_short_options" = "${all_short_options/$first_option/}" && return 1 || return 0
}
# THE DEFAULTS INITIALIZATION -
OPTIONALS
_arg_auto_recover_standby="on"
_arg_auto_recover_primary="off"
_arg_lock_timeout_minutes="120"
print_help ()
{
printf '%s\n' "<The
general help message of my script>"
printf 'Usage: %s
[--(no-)auto-recover-standby] [--(no-)auto-recover-primary]
[--lock-timeout-minutes <arg>] [-h|--help]\n' "$0"
printf '\t%s\n' "--auto-recover-standby,--no-auto-recover-standby:
reattach a standby to pgpool if possible (on by default)"
printf '\t%s\n' "--auto-recover-primary,--no-auto-recover-primary:
recover the degenerated master (off by default)"
printf '\t%s\n' "--lock-timeout-minutes:
minutes after which a lock will be ignored (optional) (default: '120')"
printf '\t%s\n' "-h,--help:
Prints help"
}
parse_commandline ()
{
while test $# -gt 0
do
_key="$1"
case "$_key" in
--no-auto-recover-standby|--auto-recover-standby)
_arg_auto_recover_standby="on"
test "${1:0:5}" = "--no-" && _arg_auto_recover_standby="off"
;;
--no-auto-recover-primary|--auto-recover-primary)
_arg_auto_recover_primary="on"
test "${1:0:5}" = "--no-" && _arg_auto_recover_primary="off"
;;
--lock-timeout-minutes)
test $# -lt 2 && die "Missing value
for the optional argument '$_key'." 1
_arg_lock_timeout_minutes="$2"
shift
;;
--lock-timeout-minutes=*)
_arg_lock_timeout_minutes="${_key##--lock-timeout-minutes=}"
;;
-h|--help)
print_help
exit 0
;;
-h*)
print_help
exit 0
;;
*)
_PRINT_HELP=yes die "FATAL ERROR:
Got an unexpected argument '$1'" 1
;;
esac
shift
done
}
parse_commandline "$@"
# OTHER STUFF GENERATED BY Argbash
### END OF CODE GENERATED BY Argbash
(sortof) ### ])
# [ <-- needed because of Argbash
printf "'%s' is %s\\n" 'auto-recover-standby' "$_arg_auto_recover_standby"
printf "'%s' is %s\\n" 'auto-recover-primary' "$_arg_auto_recover_primary"
printf "'%s' is %s\\n" 'lock-timeout-minutes' "$_arg_lock_timeout_minutes"
PIDFILE=/home/postgres/recover_failed_node.pid
trap cleanup EXIT
DELEGATE_IP=192.168.122.99
### adapt on each node ###
PGP_NODE_ID=0
PGP_STATUS_WAITING=1
PGP_STATUS_UP=2
PGP_STATUS_DOWN=3
LOGFILENAME="${PROGNAME%.*}.log"
LOGFILE=/var/log/postgres/${LOGFILENAME}
if [ ! -f $LOGFILE ] ; then
touch $LOGFILE
fi
log_info(){
echo $( date +"%Y-%m-%d
%H:%M:%S.%6N" ) - INFO - $1 | tee -a $LOGFILE
}
log_error(){
echo $( date +"%Y-%m-%d
%H:%M:%S.%6N" ) - ERROR - $1 | tee -a $LOGFILE
}
cleanup(){
#
remove pid file but only if it is mine, dont remove if another process was
running
if [ -f $PIDFILE ] ; then
MYPID=$$
STOREDPID=$(cat $PIDFILE)
if [ "${MYPID}" == "${STOREDPID}" ] ; then
rm -f $PIDFILE
fi
fi
if [ -z $INSERTED_ID ] ; then
return
fi
log_info
"delete
from recover_failed with id $INSERTED_ID"
psql -U repmgr -h $DELEGATE_IP -p 9999 repmgr -t -c "delete from
recover_failed_lock where id=${INSERTED_ID};"
}
# test if there is lock in the
recover_failed_lock table
# return 0 if there is no lock, 1 if
there is one
is_recovery_locked(){
#
create table if not exists
psql -U repmgr -h $DELEGATE_IP -p 9999 repmgr -c "create table
if not exists recover_failed_lock(id serial,ts timestamp with time zone default
current_timestamp,node varchar(10) not null,message varchar(120));"
#clean-up
old records
psql -U repmgr -h $DELEGATE_IP -p 9999 repmgr -c "delete from
recover_failed_lock where ts < current_timestamp - INTERVAL '1 day';"
#
check if there is already an operation in progress
str=$(psql -U repmgr -h $DELEGATE_IP -p 9999 repmgr -c "select
ts,node from recover_failed_lock where ts > current_timestamp - INTERVAL '${_arg_lock_timeout_minutes} min';")
if [ $? -ne 0 ] ; then
log_error
"psql
error when selecting from recover_failed_lock table"
exit
1
fi
echo $str | grep "(0
rows)"
if [ $? -eq 0 ] ; then
return 0
fi
log_info
"there
is a lock record in recover_failed_lock : $str"
return
1
}
# take a lock on the the recovery
operation
# by inserting a record in table
recover_failed_lock
# fails if there is already a recovery
running (if an old record still exist)
# exit -1 : error
#
return 0: cannot acquire a lock because an operation is already in
progress
lock_recovery(){
MSG=$1
#
create table if not exists
psql -U repmgr -h $DELEGATE_IP -p 9999 repmgr -c "create table
if not exists recover_failed_lock(id serial,ts timestamp with time zone default
current_timestamp,node varchar(10) not null,message varchar(120));"
if [ $? -ne 0 ] ; then
log_error
"Cannot
create table recover_failed_lock table"
exit
1
fi
is_recovery_locked
if [ $? -eq 1 ] ; then
return 0
fi
str=$(psql -U repmgr -h $DELEGATE_IP -p 9999 repmgr -t -c "insert into
recover_failed_lock (node,message) values ('${NODE_NAME}','${MSG}') returning id;")
if [ $? -ne 0 ] ; then
log_info
"cannot
insert into recover_failed_lock"
exit
1
fi
INSERTED_ID=$(echo $str | awk '{print $1}')
log_info "inserted
lock in recover_failed_log with id $INSERTED_ID"
return $INSERTED_ID
}
pg_is_in_recovery(){
psql -t -c "select
pg_is_in_recovery();" | head -1 | awk '{print $1}'
}
check_is_streaming_from(){
PRIMARY=$1
#
first check if is_pg_in_recovery is t
in_reco=$( pg_is_in_recovery )
if [ "a${in_reco}" != "at" ] ; then
return 0
fi
psql
-t -c "select * from
pg_stat_wal_receiver;" > /tmp/stat_wal_receiver.tmp
#
check that status is streamin
status=$( cat
/tmp/stat_wal_receiver.tmp | head -1 | cut -f2 -d"|" | sed -e "s/ //g" )
if [ "a${status}" != "astreaming" ] ; then
log_info
"status
is not streaming"
return
0
fi
#check
that is recovering from primary
conninfo=$( cat
/tmp/stat_wal_receiver.tmp | head -1 | cut -f14 -d"|" )
echo $conninfo | grep "host=${PRIMARY}"
if [ $? -eq 1 ] ; then
log_info
"not
streaming from $PRIMARY"
return
0
fi
return 1
}
# arg: 1 message
recover_failed_master(){
#
try to acquire a lock
MSG=$1
lock_recovery "$MSG"
LOCK_ID=$?
if [ ${LOCK_ID} -eq 0 ] ; then
log_info
"cannot
acquire a lock, probably an old operation is in progress ?"
return
99
fi
log_info
"acquired
lock $LOCK_ID"
#echo
"First try node rejoin"
#echo
"todo"
log_info "Do
pcp_recovery_node of $PGP_NODE_ID"
pcp_recovery_node -h $DELEGATE_IP -p 9898 -w $PGP_NODE_ID
ret=$?
cleanup
return $ret
}
recover_standby(){
#dont
do it if there is a lock on recover_failed_lock
is_recovery_locked
if [ $? -eq 1 ] ; then
return 0
fi
if [ "$_arg_auto_recover_standby" == "on" ] ; then
log_info
"attach
node back since it is in recovery streaming from $PRIMARY_NODE_ID"
pcp_attach_node -h $DELEGATE_IP -p 9898 -w ${PGP_NODE_ID}
if [ $? -eq 0 ] ; then
log_info
"OK
attached node $node back since it is
in recovery and streaming from $PRIMARY_NODE_ID"
exit
0
fi
log_error
"attach
node failed for node $node"
exit
1
else
log_info
"auto_recover_standby
is off, do nothing"
exit
0
fi
}
if [ -f $PIDFILE ] ; then
PID=$(cat $PIDFILE)
ps -p $PID > /dev/null
2>&1
if [ $? -eq 0 ] ; then
log_info
"script
already running with PID $PID"
exit
0
else
log_info
"PID
file is there but script is not running, clean-up $PIDFILE"
rm -f $PIDFILE
fi
fi
echo $$ > $PIDFILE
if [ $? -ne 0 ] ; then
log_error
"Could
not create PID file"
exit
1
fi
log_info "Create $PIDFILE with value $$"
str=$( pcp_node_info -h $DELEGATE_IP -p 9898 -w $PGP_NODE_ID )
if [ $? -ne 0 ] ; then
log_error
"pgpool
cannot be accessed"
rm -f $PIDFILE
exit
1
fi
read node port status weight status_name
role date_status time_status <<< $str
if [ $status -ne $PGP_STATUS_DOWN ] ; then
log_info
"pgpool
status for node $node is $status_name and role $role, nothing to
do"
rm -f $PIDFILE
exit
0
fi
log_info "Node $node is down (role is $role)"
# status down, the node is detached
# get the primary from pool_nodes
psql -h $DELEGATE_IP -p 9999 -U repmgr -c "show pool_nodes;" >
/tmp/pool_nodes.log
if [ $? -ne 0 ] ; then
log_error
"cannot
connect to postgres via pgpool"
rm -f $PIDFILE
exit
1
fi
PRIMARY_NODE_ID=$( cat
/tmp/pool_nodes.log | grep
primary
| grep -v down | cut -f1 -d"|" | sed -e "s/ //g")
PRIMARY_HOST=$( cat
/tmp/pool_nodes.log | grep
primary
| grep -v down | cut -f2 -d"|" | sed -e "s/ //g")
log_info "Primary node is $PRIMARY_HOST"
# check if this node is a failed master
(degenerated master)
# if yes then pcp_recovery_node or node
rejoin is needed
if [ $role == "primary" ] ; then
#
this should never happen !!
log_info "This
node is a primary and it is down: recovery needed"
#
sanity check
if [ $PRIMARY_NODE_ID -ne $PGP_NODE_ID ] ; then
log_error
"Unpextected
state, this node $PGP_NODE_ID is a primary
according to pcp_node_info but pool_nodes said $PRIMARY_NODE_ID is master"
rm -f $PIDFILE
exit
1
fi
if [ "$_arg_auto_recover_primary" == "on" ] ; then
recover_failed_master
"primary
node reported as down in pgpool"
ret=$?
rm -f $PIDFILE
exit $ret
else
log_info
"auto_recover_primary
is off, do nothing"
rm -f $PIDFILE
exit
0
fi
fi
log_info "This node is a standby and it is
down: check if it can be re-attached"
log_info "Check if the DB is running, if
not do not start it but exit with error"
pg_ctl status
if [ $? -ne 0 ] ; then
log_error
"the
DB is not running"
rm -f $PIDFILE
exit
1
fi
check_is_streaming_from $PRIMARY_HOST
res=$?
if [ $res -eq 1 ] ; then
recover_standby
ret=$?
rm -f $PIDFILE
exit $ret
fi
if [ "$_arg_auto_recover_primary" == "on" ] ; then
log_info
"node
is standby in pgpool but it is not streaming from the primary, probably a
degenerated master. Lets do pcp_recovery_node"
recover_failed_master "standby node not streaming from the
primary"
ret=$?
rm -f $PIDFILE
exit $ret
else
log_info
"node
is supposed to be a standby but it is not streaming from the primary, however
auto_recovery_primary is off so do nothing"
fi
rm -f $PIDFILE
exit 0
interface gráfica pgpool
Desenvolvi uma aplicação baseada na web em cima do pgpool e postgres
para visualizar o cluster (estado do watchdog e estado da replicação). Por
enquanto, a maneira mais fácil de usá-lo é clonar o repositório do github,
construir uma imagem docker contendo o aplicativo e então executar a imagem
docker em um dos nós ou em outro servidor (ele precisa de acesso aos servidores
posgres via ssh)
Vou passar pelo processo de construir
a imagem do docker e iniciá-la no meu host. Observe que, dependendo de como
você instala o docker (do docker-ce ou do centos), pode ser necessário usar o
sudo para executar comandos do docker como usuário não root. Observe que ele
requer uma versão do docker que suporte construção em vários estágios, então vá
para docker-ce https://docs.docker.com/install/linux/docker-ce/centos/
# clone the github project
git clone
git@github.com:saule1508/pgcluster.git
# get into the directory containing the
app
cd pgcluster/manager
# get into the build directory
cd build
#
NB: the user must be in the group docker, otherwise you need to add a
sudo in front of the docker commands
#
in the script build.bash
./build.bash
No final do script, uma imagem docker deve ser criada, ela deve dizer
“Gerente marcado com sucesso:"no meu caso é manager:0.8.0
O contêiner precisa de uma variável de ambiente PG_BACKEND_NODE_LIST que
é uma lista csv dos backends. Ele também precisa da senha do usuário repmgr.
Então, neste caso:
PG_BACKEND_NODE_LIST: 0:pg01.localnet:5432:1:/u01/pg11/data:ALLOW_TO_FAILOVER,1:pg02.localnet:5432:1:/u01/pg11/data:ALLOW_TO_FAILOVER,2:pg03.localnet:5432:1:/u01/pg11/data:ALLOW_TO_FAILOVER
REPMGRPWD: rep123
o contêiner docker também deve ser capaz de se conectar aos 3 nós do
postgres via ssh, sem uma senha: para tornar isso possível, precisaremos copiar
a chave pública armazenada na imagem para os 3 servidores (a chave pública deve
ser adicionada a /root/.ssh/authorized_keys nos backends, podemos usar
ssh-copy-id para isso)
inicie o contêiner para copiar a chave pública
docker run -ti --net host --entrypoint
bash manager:0.8.0
# now we are inside the container,
check that you can ping the backends
ping pg01.localnet
ping pg02.localnet
ping pg03.localnet
# copy the public key
ssh-copy-id postgres@pg01.localnet
ssh-copy-id postgres@pg02.localnet
ssh-copy-id postgres@pg03.localnet
# exit the container
exit
A ferramenta executa um script chamado /opt/postgres/scripts/checks.sh
no backend, então devemos instalá-lo nos 3 servidores
#!/bin/bash
repmgr node check | grep -v "^Node" | while read line
do
ck=$(echo $line | sed -e "s/\t//" -e "s/ /_/g" | cut -f1 -d":")
res=$(echo $line | cut -f2- -d":")
echo
repmgr,$ck,$res
done
df -k ${PGDATA} | grep -v "^Filesystem" | awk '{print
"disk,"$NF","$5","$2","$3}'
df -k /backup | grep -v "^Filesystem" | awk '{print
"disk,"$NF","$5","$2","$3}'
df -k /archive | grep -v "^Filesystem" | awk '{print
"disk,"$NF","$5","$2","$3}'
E agora podemos executar o container. Ele vai expor a porta 8080, então
precisamos abrir essa porta no firewall
firewall-cmd --add-port 8080/tcp
--permanent
systemctl restart firewalld
systemctl restart docker
docker run -d --net host \
-v
/var/run/docker.sock:/var/run/docker.sock \
-e PG_BACKEND_NODE_LIST=0:pg01.localnet:5432:1:/u01/pg11/data:ALLOW_TO_FAILOVER,1:pg02.localnet:5432:1:/u01/pg11/data:ALLOW_TO_FAILOVER,2:pg03.localnet:5432:1:/u01/pg11/data:ALLOW_TO_FAILOVER
\
-e REPMGRPWD:rep123 \
-e DBHOST=192.168.122.99 \
-e SCRIPTSDIR=/opt/postgres/scripts
-e SSHPORT=22 -e BACKUPDIR=/u02/backup \
--name pgpoolmanager
manager:0.8.0
Nenhum comentário:
Postar um comentário