Esta é a segunda parte do blog “A Guide to Pgpool for PostgreSQL”. A primeira parte sobre balanceamento de carga, pool de sessões, cache de memória e instalação pode ser encontrada aqui.
Muitos usuários procuram o pgpool especificamente para recursos de alta disponibilidade, e ele tem muito a oferecer. Existem poucas instruções para o pgpool HA na web (por exemplo, uma mais longa e uma mais curta), então não faria sentido repeti-las. Tampouco queremos fornecer outro conjunto cego de valores de configuração. Em vez disso, sugiro jogar contra as regras e tentar fazer da maneira errada, então veremos um comportamento interessante. Um dos principais recursos esperados (pelo menos está no topo da página) é a capacidade de reconhecer a usabilidade de um ex master “morto” e reutilizá-lo com pg_rewind. Isso poderia economizar horas de trazer de volta o novo modo de espera com big data (já que pulamos rsync ou pg_basebackup, que efetivamente copia TODOS os arquivos do novo mestre). Estritamente falando, pg_rewind destina-se ao failover planejado (durante a atualização ou migração para um novo hardware). Mas vimos quando isso ajuda muito com o desligamento não planejado, mas ainda assim gracioso e o failover automatizado - por exemplo, o ClusterControl faz uso dele ao executar o failover automático de escravos de replicação. Vamos supor que temos o caso:precisamos que (qualquer) mestre seja acessível o máximo possível. Se por algum motivo (falha de rede, conexões máximas excedidas ou qualquer outra “falha” que impeça o início de novas sessões) não pudermos mais usar um mestre para operações de RW, temos um cluster de failover configurado, com escravos que podem aceitar conexões. Podemos então promover um dos escravos e fazer failover para ele.
Primeiro vamos supor que temos três nós:
- 10.1.10.124:5400 com /pg/10/m (pgpool gira aqui também)
- 10.1.10.147:5401 com /pg/10/m2
- 10.1.10.124:5402 com /pg/10/s2
Esses são efetivamente os mesmos nós da primeira parte, mas o nó de failover é movido para um host e $PGDATA diferentes. Eu fiz isso para ter certeza de que não havia digitado ou esquecido alguma citação extra no comando ssh remoto. Além disso, as informações de depuração parecerão mais simples porque os endereços IP são diferentes. Finalmente, eu não tinha certeza se conseguiria fazer esse caso de uso sem suporte funcionar, então tenho que ver com meus próprios olhos.
Failover
Primeiro definimos failover_command e executamos pgpool reload e tentamos fazer o failover. Aqui e mais adiante, ecoarei algumas informações para /tmp/d no servidor pgpool, para que eu possa seguir -f /tmp/d para ver o fluxo.
[email protected]:~$ grep failover_command /etc/pgpool2/pgpool.conf
failover_command = 'bash /pg/10/fo.sh %D %H %R'
[email protected]:~$ cat /pg/10/fo.sh
rem_cmd="pg_ctl -D $3 promote"
cmd="ssh -T [email protected]$2 $rem_cmd"
echo "$(date) $cmd" >>/tmp/d
$cmd &>>/tmp/d
NB:Você tem $PATH definido em .bashrc no host remoto? ..
Vamos parar o mestre (eu sei que não é assim que o desastre acontece, você espera que pelo menos algum macaco enorme ou robô vermelho brilhante esmague o servidor com um martelo enorme, ou pelo menos os discos rígidos chatos morram, mas estou usando esse gracioso variante para demonstrar o possível uso de pg_rewind, então aqui o failover será o resultado de erro humano ou falha de rede meio segundo durante o health_check_period), então:
/usr/lib/postgresql/10/bin/pg_ctl -D /pg/10/m stop
2018-04-18 13:53:55.469 IST [27433] LOG: received fast shutdown request
waiting for server to shut down....2018-04-18 13:53:55.478 IST [27433] LOG: aborting any active transactions
2018-04-18 13:53:55.479 IST [28855] postgres t FATAL: terminating connection due to administrator command
2018-04-18 13:53:55.483 IST [27433] LOG: worker process: logical replication launcher (PID 27440) exited with exit code 1
2018-04-18 13:53:55.484 IST [27435] LOG: shutting down
2018-04-18 13:53:55.521 IST [27433] LOG: database system is shut down
done
server stopped
Agora verificando a saída do comando de failover:
[email protected]:~$ cat /tmp/d
Wed Apr 18 13:54:05 IST 2018 ssh -T [email protected]
pg_ctl -D /pg/10/f promote
waiting for server to promote.... done
server promoted
E verificando depois de um tempo:
t=# select nid,port,st, role from dblink('host=localhost port=5433','show pool_nodes') as t (nid int,hostname text,port int,st text,lb_weight float,role text,cnt int,cur_node text,del int);
nid | port | st | role
-----+------+------+---------
0 | 5400 | down | standby
1 | 5401 | up | primary
2 | 5402 | up | standby
(3 rows)
Também vemos em logs de cluster ex-failover:
2018-04-13 14:26:20.823 IST [20713] LOG: received promote request
2018-04-13 14:26:20.823 IST [20713] LOG: redo done at 0/951EC20
2018-04-13 14:26:20.823 IST [20713] LOG: last completed transaction was at log time 2018-04-13 10:41:54.355274+01
2018-04-13 14:26:20.872 IST [20713] LOG: selected new timeline ID: 2
2018-04-13 14:26:20.966 IST [20713] LOG: archive recovery complete
2018-04-13 14:26:20.998 IST [20712] LOG: database system is ready to accept connections
Verificando a replicação:
[email protected]:~$ psql -p 5401 t -c "select now() into test"
SELECT 1
[email protected]:~$ psql -p 5402 t -c "select * from test"
now
-------------------------------
2018-04-13 14:33:19.569245+01
(1 row)
O slave /pg/10/s2:5402 mudou para uma nova timeline graças a recovery_target_timeline =last in recovery.conf, então estamos bem. Não precisamos ajustar o recovery.conf para apontar para o novo mestre, porque ele aponta para o ip e a porta do pgpool e eles permanecem os mesmos, não importa quem esteja desempenhando a função de mestre primário.
Verificando o balanceamento de carga:
[email protected]:~$ (for i in $(seq 1 9); do psql -h localhost -p 5433 t -c "select current_setting('port') from ts limit 1" -XAt; done) | sort| uniq -c
6 5401
3 5402
Legal. Os aplicativos por trás do pgpool notarão uma segunda interrupção e continuarão funcionando.
Reutilizando ex-mestre
Agora podemos transformar o ex-master em standby de failover e trazê-lo de volta (sem adicionar um novo nó ao pgpool, pois ele já existe). Se você não tiver wal_log_hints ativado ou somas de verificação de dados (a diferença abrangente entre essas opções está aqui), você deve recriar o cluster no ex-mestre para seguir uma nova linha do tempo:
[email protected]:~$ rm -fr /pg/10/m
[email protected]:~$ pg_basebackup -h localhost -p 5401 -D /pg/10/m/
Mas não se apresse em executar as declarações acima! Se você tomou cuidado com wal_log_hints (requer reinicialização), você pode tentar usar pg_rewind para uma comutação muito mais rápida do ex-mestre para um novo escravo.
Então ATM temos o ex-mestre offline, novo mestre com próxima linha do tempo iniciada. Se o ex-mestre estava offline devido a uma falha temporária na rede e ele volta, precisamos desligá-lo primeiro. No caso acima, sabemos que está inativo, então podemos tentar rebobinar:
[email protected]:~$ pg_rewind -D /pg/10/m2 --source-server="port=5401 host=10.1.10.147"
servers diverged at WAL location 0/40605C0 on timeline 2
rewinding from last common checkpoint at 0/4060550 on timeline 2
Done!
E de novo:
[email protected]:~$ pg_ctl -D /pg/10/m2 start
server started
...blah blah
[email protected]:~$ 2018-04-16 12:08:50.303 IST [24699] LOG: started streaming WAL from primary at 0/B000000 on timeline 2
t=# select nid,port,st,role from dblink('host=localhost port=5433','show pool_nodes') as t (nid int,hostname text,port int,st text,lb_weight float,role text,cnt int,cur_node text,del int);
nid | port | st | role
-----+------+------+---------
0 | 5400 | down | standby
1 | 5401 | up | primary
2 | 5402 | up | standby
(3 rows)
Op. Duh! Apesar do cluster na porta 5400 estar online e seguir uma nova linha do tempo, precisamos dizer ao pgpool para reconhecê-lo:
[email protected]:~$ pcp_attach_node -w -h 127.0.0.1 -U vao -n 0
pcp_attach_node -- Command Successful
Agora todos os três estão ativos (e o pgpool sabe disso) e em sincronia:
[email protected]:~$ sql="select ts.i::timestamp(0), current_setting('data_directory'),case when pg_is_in_recovery() then 'recovering' else 'mastering' end stream from ts order by ts desc"
[email protected]:~$ psql -h 10.1.10.147 -p 5401 t -c "$sql";
i | current_setting | stream
---------------------+-----------------+-----------
2018-04-30 14:34:36 | /pg/10/m2 | mastering
(1 row)
[email protected]:~$ psql -h 10.1.10.124 -p 5402 t -c "$sql";
i | current_setting | stream
---------------------+-----------------+------------
2018-04-30 14:34:36 | /pg/10/s2 | recovering
(1 row)
[email protected]:~$ psql -h 10.1.10.124 -p 5400 t -c "$sql";
i | current_setting | stream
---------------------+-----------------+------------
2018-04-30 14:34:36 | /pg/10/m | recovering
(1 row)
Agora vou tentar usar recovery_1st_stage_command para reutilizar o ex-master:
[email protected]:~# grep 1st /etc/pgpool2/pgpool.conf
recovery_1st_stage_command = 'or_1st.sh'
Mas recovery_1st_stage_command não oferece os argumentos necessários para pg_rewind, que posso ver se adicionar a recovery_1st_stage_command:
echo "online recovery started on $(hostname) $(date --iso-8601) $0 $1 $2 $3 $4"; exit 1;
A saída:
online recovery started on u2 2018-04-30 /pg/10/m2/or_1st.sh /pg/10/m2 10.1.10.124 /pg/10/m 5401
Bem - usar pg_rewind está apenas na lista de tarefas - o que eu esperava? .. Então eu preciso fazer algum hack de macaco para obter o ip e a porta mestres (lembre-se de que ele continuará mudando após o failover).
Baixe o whitepaper hoje PostgreSQL Management &Automation with ClusterControlSaiba o que você precisa saber para implantar, monitorar, gerenciar e dimensionar o PostgreSQLBaixe o whitepaper
Um truque de macaco
Então eu tenho algo assim no recovery_1st_stage_command:
[email protected]:~# cat /pg/10/or_1st.sh
pgpool_host=10.1.10.124
pgpool_port=5433
echo "online recovery started on $(hostname) $(date --iso-8601) $0 $1 $2 $3 $4" | ssh -T $pgpool_host "cat >> /tmp/d"
master_port=$(psql -XAt -h $pgpool_host -p $pgpool_port t -c "select port from dblink('host=$pgpool_host port=$pgpool_port','show pool_nodes') as t (nid int,hostname text,port int,st text,lb_weight float,role text,cnt int,cur_node text,del int) where role='primary'")
master_host=$(psql -XAt -h $pgpool_host -p $pgpool_port t -c "select hostname from dblink('host=$pgpool_host port=$pgpool_port','show pool_nodes') as t (nid int,hostname text,port int,st text,lb_weight float,role text,cnt int,cur_node text,del int) where role='primary'")
failover_host=$(psql -XAt -h $pgpool_host -p $pgpool_port t -c "select hostname from dblink('host=$pgpool_host port=$pgpool_port','show pool_nodes') as t (nid int,hostname text,port int,st text,lb_weight float,role text,cnt int,cur_node text,del int) where role!='primary' order by port limit 1")
src='"port=$master_port host=$master_host"'
rem_cmd="'pg_rewind -D $3 --source-server=\"port=$master_port host=$master_host\"'"
cmd="ssh -T $failover_host $rem_cmd"
echo $cmd | ssh -T $pgpool_host "cat >> /tmp/d"
$cmd
tmp=/tmp/rec_file_tmp
cat > $tmp <<EOF
standby_mode = 'on'
primary_conninfo = 'host=$master_host port=$master_port user=postgres'
trigger_file = '/tmp/tg_file'
recovery_target_timeline = latest
EOF
scp $tmp $failover_host:$3/recovery.conf
rem_cmd="pg_ctl -D $3 start"
cmd="ssh -T $failover_host $rem_cmd"
echo $cmd | ssh -T $pgpool_host "cat >> /tmp/d"
$cmd
echo "OR finished $(date --iso-8601)" | ssh -T $pgpool_host "cat >> /tmp/d"
exit 0;
Agora que confusão! Bem - se você decidir usar um recurso não existente - prepare-se - ele ficará ruim, funcionará pior e você se sentirá permanentemente envergonhado do que fez. Então passo a passo:
- Preciso do IP e da porta do pgpool para me conectar remotamente a ele, tanto para consultar “show pool_nodes” quanto para registrar etapas e executar comandos.
- Estou enviando algumas informações de dbg para /tmp/d sobre ssh, porque o comando será executado no lado mestre, que será alterado após o failover
- Posso usar o resultado de "show pool_nodes" para obter as informações de conexão mestre em execução simplesmente filtrando com a cláusula WHERE
- Precisarei de aspas duplas no argumento para pg_rewind, que precisará ser executado em ssh, então apenas divido o comando para facilitar a leitura, depois o echo e executo
- Preparando recovery.conf com base na saída de “show pool_nodes” (escrevendo, eu me pergunto - por que eu não usei apenas o IP e a porta do pgpool? ..
- Iniciando novo escravo de failover (sei que devo usar a segunda etapa - apenas pulei para evitar obter todos os IPs e portas novamente)
Agora o que resta - tentando usar essa bagunça no pcp:
[email protected]:~# pcp_recovery_node -h 127.0.0.1 -U vao -n 0 -w
pcp_recovery_node -- Command Successful
[email protected]:~# psql -h localhost -p 5433 t -c"select nid,port,st,role from dblink('host=10.1.10.124 port=5433','show pool_nodes') as t (nid int,hostname text,port int,st text,lb_weight float,role text,cnt int,cur_node text,del int)"
nid | port | st | role
-----+------+----+---------
0 | 5400 | up | standby
1 | 5401 | up | primary
2 | 5402 | up | standby
(3 rows)
Verificando o /tmp/d no servidor pgpool:
[email protected]:~# cat /tmp/d
Tue May 1 11:37:59 IST 2018 ssh -T [email protected] /usr/lib/postgresql/10/bin/pg_ctl -D /pg/10/m2 promote
waiting for server to promote.... done
server promoted
online recovery started on u2 2018-05-01 /pg/10/m2/or_1st.sh /pg/10/m2
ssh -T 10.1.10.124 'pg_rewind -D --source-server="port=5401 host=10.1.10.147"'
ssh -T 10.1.10.124 pg_ctl -D start
OR finished 2018-05-01
Agora, obviamente, queremos rolar novamente para ver se funciona em qualquer host:
[email protected]:~$ ssh -T 10.1.10.147 pg_ctl -D /pg/10/m2 stop waiting for server to shut down.... done
server stopped
[email protected]:~$ psql -h localhost -p 5433 t -c"select nid,port,st,role from dblink('host=10.1.10.124 port=5433','show pool_nodes') as t (nid int,hostname text,port int,st text,lb_weight float,role text,cnt int,cur_node text,del int)"
nid | port | st | role
-----+------+------+---------
0 | 5400 | up | primary
1 | 5401 | down | standby
2 | 5402 | up | standby
(3 rows)
[email protected]:~# pcp_recovery_node -h 127.0.0.1 -U vao -n 1 -w
[email protected]:~$ psql -h localhost -p 5433 t -c"select nid,port,st,role from dblink('host=10.1.10.124 port=5433','show pool_nodes') as t (nid int,hostname text,port int,st text,lb_weight float,role text,cnt int,cur_node text,del int)"
nid | port | st | role
-----+------+----+---------
0 | 5400 | up | primary
1 | 5401 | up | standby
2 | 5402 | up | standby
(3 rows)
O log parece semelhante - apenas o IP e as portas foram alterados:
Tue May 1 11:44:01 IST 2018 ssh -T [email protected] /usr/lib/postgresql/10/bin/pg_ctl -D /pg/10/m promote
waiting for server to promote.... done
server promoted
online recovery started on u 2018-05-01 /pg/10/m/or_1st.sh /pg/10/m 10.1.10.147 /pg/10/m2 5400
ssh -T 10.1.10.147 'pg_rewind -D /pg/10/m2 --source-server="port=5400 host=10.1.10.124"'
ssh -T 10.1.10.147 pg_ctl -D /pg/10/m2 start
online recovery started on u 2018-05-01 /pg/10/m/or_1st.sh /pg/10/m
ssh -T 10.1.10.147 'pg_rewind -D --source-server="port=5400 host=10.1.10.124"'
ssh -T 10.1.10.147 pg_ctl -D start
OR finished 2018-05-01
Nesse sandbox, o mestre mudou para 5401 no failover e depois de morar lá por um tempo voltou para 5400. Usar pg_rewind deve torná-lo o mais rápido possível. Anteriormente, a parte assustadora do failover automático era - se você realmente estragasse a configuração e não previsse alguma força maior, você poderia executar o failover automático para o próximo escravo e o próximo e o próximo até que não restasse nenhum escravo livre. E depois disso, você acaba com vários mestres de cérebro dividido e nenhum sobressalente de failover. É um consolo pobre em tal cenário ter ainda mais escravos para failover, mas sem pg_rewind você não teria nem isso. rsync “tradicional” ou pg_basebackup copia ALL $PGDATA para criar um standby e não pode reutilizar o ex master “não muito diferente”.
Em conclusão a esta experiência, gostaria de enfatizar mais uma vez - esta não é uma solução adequada para colagem de cópias cegas. O uso de pg_rewind não é recomendado para pg_pool. Não é utilizável em todos os caixas eletrônicos. Eu queria adicionar um pouco de ar fresco à configuração do pgpool HA, para nubes como eu observar um pouco mais de perto como funciona. Para coryphaeus sorrir para a abordagem ingênua e talvez ver com nossos olhos de nubes.