Formation HAPAT
Dalibo SCOP
24.09
29 août 2024
Formation | Formation HAPAT |
Titre | Haute disponibilité avec Patroni |
Révision | 24.09 |
ISBN | N/A |
https://dali.bo/hapat_pdf | |
EPUB | https://dali.bo/hapat_epub |
HTML | https://dali.bo/hapat_html |
Slides | https://dali.bo/hapat_slides |
Cette formation est sous licence CC-BY-NC-SA. Vous êtes libre de la redistribuer et/ou modifier aux conditions suivantes :
PostgreSQL® Postgres® et le logo Slonik sont des marques déposées par PostgreSQL Community Association of Canada.
Ce document ne couvre que les versions supportées de PostgreSQL au moment de sa rédaction, soit les versions 12 à 16.
softdog
(moins fiable)À minima, une architecture fiable peut se composer au choix :
N’hésitez pas, c’est le moment !
patroni
par instance PostgreSQLPatroni configure la réplication physique entre les instances pour :
—
Dans postgresql.conf
:
wal_level = replica
(ou logical
)max_wal_senders = X
wal_sender_timeout = 60s
Copie des données du serveur primaire (à chaud !) :
pg_basebackup
rsync
, cp
…
pg_backup_start()
/pg_backup_stop()
!postgresql.conf
& postgresql.auto.conf
standby.signal
(dans PGDATA)
Sur le primaire :
walsender ... streaming 0/3BD48728
Sur le secondaire :
walreceiver streaming 0/3BD48728
pg_ctl promote
pg_promote()
promote_trigger_file
(<=v15)Une promotion déclenche :
standby.signal
.history
VACUUM ANALYZE
conseillé
wal_keep_size
/wal_keep_segments
)restore_command
)rsync
, restauration PITR, plutôt que
pg_basebackup
pg_rewind
Lors d’une élection:
quorum = (nombre de nœuds / 2) + 1
nombre de nœuds - quorum
Nombre de nœuds | Majorité | Tolérance de panne |
---|---|---|
2 | 2 | 0 |
⇒ 3 | 2 | 1 |
4 | 3 | 1 |
⇒ 5 | 3 | 2 |
6 | 4 | 2 |
⇒ 7 | 4 | 3 |
8 | 5 | 3 |
Démo interactive :
etcdctl
(ETCDCTL_API
)fio
dnf install etcd
apt-get install etcd
(Debian 11)apt-get install etcd-server etcd-client
(Debian
12)Trois méthodes de configuration différentes:
etcd
etcd
--config-file
Configurations employées par les principaux services etcd:
etcd.service
/etc/etcd/etcd.conf
(paquet PGDG Red Hat &
dérivées)/etc/default/etcd
(Debian et dérivées)systemctl start etcd
etcdctl
data-dir
get
, put
,
del
(ou l’API)etcdctl txn
)etcdctl auth enable
user
: pour l’authentification
etcdctl user [add|del|get|list]
etcdctl user [grant-role|revoke-role]
role
: conteneur pour les droits, assigné aux
utilisateurs
etcdctl role [add|del|get|list]
etcdctl role [grant-permission|revoke-permission]
--cert-file
--key-file
--client-cert-auth
--trusted-ca-file
--peer-cert-file
--peer-key-file
--peer-client-cert-auth
--peer-trusted-ca-file
etcdctl … snapshot save …
$ETCD_DATA_DIRECTORY/member/snap/db
)etcdctl snapshot restore …
Pour remplacer un membre sans risquer de perdre le quorum :
/debug
/metrics
/health
Nous allons utiliser le simulateur Raftscope.
Les 5 nœuds portent tous le dernier numéro de mandat qu’ils ont connu. Le leader est cerclé de noir.
Le timeout de chaque follower se décrémente en permanence et est réinitialisé quand un message de heart beat du leader est reçu.
Le journal de chaque nœud est visible sur la droite pour suivre la propagation des informations de chaque request.
Il est possible de mettre en pause, d’accélérer ou ralentir.
Les actions se font par clic droit sur chaque nœud :
e1
,
e2
et e3
.e1
, e2
et e3
.e1
,
e2
et e3
.e1
, e2
et e3
.Ces exercices utilisent l’API v3 d’etcd.
But : manipuler la base de données distribuée d’Etcd.
etcdctl put
pour écrire une clef foo
à la valeur bar
.baz
.food
contenant les
clés/valeurs poisson: bar
et vin: blanc
.food
en exigeant une réponse d’un quorum.But : constater le comportement d’Etcd conforme à l’algorithme Raft.
virsh suspend <nom machine>
.Les nœuds portent au départ le numéro de mandat 1, il n’y a pas de leader :
Il faut attendre que l’un des nœuds ait atteint son timeout pour qu’il propose une élection. Ici, c’est le nœud S2 qui déclenche l’élection :
Les petites pastilles sur ce nœud S2 représentent le nombre de votes lui étant favorable. Un vote sur cinq est pour le moment validé : le sien. Les autres nœuds de l’agrégat reçoivent donc la candidature de S2 et y répondent tous favorablement :
Nous voyons le nombre de vote augmenter au fur et à mesure que S2 les reçoit :
Finalement, S2 remporte l’élection, crée le mandat n°2 et envoie son premier message de keep alive :
Les autres se raccrochent à lui et entretiennent chacun leur time out.
Le même phénomène se produit et l’on arrive au mandat 3. Ici, le timeout du nœud S1 expire en premier :
S1 déclenche une élection :
Les autres nœuds accordent leur vote à S1, sauf S2 qui est éteint :
S1 devient leader :
Elles apparaissent sur la droite dans le journal au fur et à mesure que le leader les diffuse. La diffusion se fait en plusieurs étapes. Écrite et émission depuis S1 :
Bonne réception des nœuds S3, S4 et S5 :
S0 ayant reçu une majorité d’acquittement, il valide la valeur auprès des autres nœuds :
Tous les nœuds ont validé la valeur :
Celui-ci redémarre à 2 (en tant que follower) :
Il bascule sur le mandat 3 au premier heart beat reçu, et commence à remplir son journal :
Il rattrape ensuite son journal avec autant d’échange avec le leader que nécessaire :
Le quorum demeure au sein du cluster, il est donc toujours possible de déclencher une élection. Comme précédemment, les trois nœuds restants s’accordent sur un leader. Dans notre exemple, tous les nœuds déclenchent leur propre élection en même temps :
Chaque nœud ayant voté pour lui-même, il n’y a pas de consensus sur le nœud à élire, les nœuds redeviennent followers. L’un d’entre eux voit son timeout atteint, ce qui donne lieu à une seconde élection :
Elle conduit ici à l’élection du nœud S2 :
La tolérance de panne est maintenant nulle.
Le cluster continue de fonctionner : on peut lui soumettre des écritures (requests) :
Elles sont bien répliquées mais ne sont pas exécutées (ou commitées) par le leader :
Pour cela, il faut que les écritures aient été répliquées vers la majorité des nœuds du cluster. Cela signifie que le client reste en attente de confirmation du traitement de sa demande.
L’un des deux nœuds survivants lance une élection :
L’autre le suit :
Mais comme le quorum de 3 nœuds (moitié de 5 plus 1) n’est pas obtenu, l’élection échoue. Les time out continuent d’expirer et de nouvelles élections sont lancées :
Faute de leader, il devient impossible de soumettre des écritures. Tout client s’appuyant dessus reçoit une erreur. Il faut attendre le retour en ligne d’un nœud et l’élection d’un nouveau leader.
e1
,
e2
et e3
.Les paquets etcd sont disponibles dans les dépôts officiels de Debian
12. L’installation consiste simplement à installer les deux paquets
etcd-server
et etcd-client
:
# apt-get install -y etcd-server etcd-client
Afin de respecter la politique des paquets Debian, l’installation du
paquet etcd-server
crée automatiquement une instance etcd
locale. Nous devons la détruire avant de pouvoir construire notre
cluster etcd à trois nœuds.
Sur chaque serveur etcd, exécuter les commandes suivantes :
# systemctl stop etcd.service
# rm -Rf /var/lib/etcd/default
e1
, e2
et e3
.La configuration sous Debian se situe dans le fichier
/etc/default/etcd
où doivent être renseignés les paramètres
etcd sous leur forme de variables d’environnements.
Voici le fichier de configuration commenté du nœud e1
,
veillez à adapter toutes les adresses IP
10.x.y.z
:
# nom du nœud au sein du cluster
ETCD_NAME=e1
# emplacement du répertoire de données
ETCD_DATA_DIR=/var/lib/etcd/acme
# interface et port d'écoute des autres nœuds
ETCD_LISTEN_PEER_URLS=http://10.0.0.11:2380
# interfaces et port d'écoute des clients
ETCD_LISTEN_CLIENT_URLS=http://10.0.0.11:2379,http://127.0.0.1:2379,http://[::1]:2379
# interface et port d'écoute à communiquer aux clients
ETCD_ADVERTISE_CLIENT_URLS=http://10.0.0.11:2379
#######################################
## Initialisation du cluster et du nœud
# création du nœud au sein d'un nouveau cluster
ETCD_INITIAL_CLUSTER_STATE=new
# interface et port à communiquer aux autres nœuds
ETCD_INITIAL_ADVERTISE_PEER_URLS=http://10.0.0.11:2380
# liste des nœuds composant le cluster
ETCD_INITIAL_CLUSTER=e1=http://10.0.0.11:2380,e2=http://10.0.0.12:2380,e3=http://10.0.0.13:2380
Une fois les trois fichiers de configuration établis, nous pouvons démarrer le cluster en exécutant la commande suivante sur tous les serveurs etcd :
# systemctl start etcd
La commande suivante doit désormais fonctionner sur n’importe quel nœud :
$ etcdctl -wjson endpoint status | jq
[
{
"Endpoint": "127.0.0.1:2379",
"Status": {
"header": {
"cluster_id": 11628162814576028000,
"member_id": 13786016400334385000,
"revision": 95,
"raft_term": 24
},
"version": "3.4.23",
"dbSize": 61440,
"leader": 2266733444126347800,
"raftIndex": 112,
"raftTerm": 24,
"raftAppliedIndex": 112,
"dbSizeInUse": 53248
}
}
]
L’état du cluster est visible grace à la commande suivante :
+-----------------------+------------------+---------+---------+-----------+[..]+--------+
| ENDPOINT | ID | VERSION | DB SIZE | IS LEADER |[..]| ERRORS |
+-----------------------+------------------+---------+---------+-----------+[..]+--------+
| http://10.0.0.11:2379 | 9e80988e833ccb43 | 3.5.13 | 20 kB | false |[..]| |
| http://10.0.0.13:2379 | a10d8f7920cc71c7 | 3.5.13 | 29 kB | true |[..]| |
| http://10.0.0.12:2379 | abdc532bc0516b2d | 3.5.13 | 20 kB | false |[..]| |
+-----------------------+------------------+---------+---------+-----------+[..]+--------+
e1
,
e2
et e3
.Les paquets etcd sont disponibles depuis les dépôts PGDG pour les distributions Red Hat 9 & dérivées (comme ici Rocky Linux 9). Il nous faut donc au préalable configurer ce dépôt :
# dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/\
pgdg-redhat-repo-latest.noarch.rpm
Il est désormais possible d’installer le paquet etcd
en
activant le dépôt pgdg-rhel9-extras
:
# dnf --enablerepo=pgdg-rhel9-extras install -y etcd
e1
, e2
et e3
.La configuration sur cette distribution se situe dans le fichier
/etc/etcd/etcd.conf
où sont renseignés les paramètres etcd
sous leur forme de variables d’environnements.
Ci-après les différents paramètres à modifier pour le nœud
e1
, veillez à adapter toutes les adresses
IP 10.x.y.z
:
ETCD_NAME=e1
ETCD_DATA_DIR=/var/lib/etcd/acme
ETCD_LISTEN_PEER_URLS=http://10.0.0.11:2380
ETCD_LISTEN_CLIENT_URLS=http://10.0.0.11:2379,http://127.0.0.1:2379,http://[::1]:2379
ETCD_ADVERTISE_CLIENT_URLS=http://10.0.0.11:2379
ETCD_INITIAL_CLUSTER_STATE=new
ETCD_INITIAL_ADVERTISE_PEER_URLS=http://10.0.0.11:2380
ETCD_INITIAL_CLUSTER=e1=http://10.0.0.11:2380,e2=http://10.0.0.12:2380,e3=http://10.0.0.13:2380
Une fois les trois fichiers de configuration établis, nous pouvons démarrer le cluster en exécutant la commande suivante sur tous les serveurs etcd :
# systemctl start etcd
Afin qu’etcd démarre automatiquement avec le serveur, il est nécessaire d’activer le service :
# systemctl enable etcd
Une fois les trois fichiers de configuration établis, la commande suivante doit fonctionner sur n’importe quel nœud :
$ etcdctl -wjson endpoint status | jq
[
{
"Endpoint": "127.0.0.1:2379",
"Status": {
"header": {
"cluster_id": 11628162814576028000,
"member_id": 13786016400334385000,
"revision": 95,
"raft_term": 24
},
"version": "3.4.23",
"dbSize": 61440,
"leader": 2266733444126347800,
"raftIndex": 112,
"raftTerm": 24,
"raftAppliedIndex": 112,
"dbSizeInUse": 53248
}
}
]
L’état du cluster est visible grace à la commande suivante :
+-----------------------+------------------+---------+---------+-----------+[..]+--------+
| ENDPOINT | ID | VERSION | DB SIZE | IS LEADER |[..]| ERRORS |
+-----------------------+------------------+---------+---------+-----------+[..]+--------+
| http://10.0.0.11:2379 | 9e80988e833ccb43 | 3.5.13 | 20 kB | false |[..]| |
| http://10.0.0.13:2379 | a10d8f7920cc71c7 | 3.5.13 | 29 kB | true |[..]| |
| http://10.0.0.12:2379 | abdc532bc0516b2d | 3.5.13 | 20 kB | false |[..]| |
+-----------------------+------------------+---------+---------+-----------+[..]+--------+
Ces exercices utilisent l’API v3 d’etcd.
But : manipuler la base de données distribuée d’Etcd.
etcdctl put
pour écrire une clef foo
à la valeur bar
.$ etcdctl put foo bar
OK
$ etcdctl get foo
foo
bar
baz
.$ etcdctl put foo baz
OK
food
contenant les
clés/valeurs poisson: bar
et vin: blanc
.$ etcdctl put food/poisson bar
OK
$ etcdctl put food/vin blanc
OK
$ etcdctl get --keys-only --prefix food/
food/poisson
food/vin
food
en exigeant une réponse d’un quorum.$ etcdctl get --consistency="l" --keys-only --prefix food/
/food/poisson
/food/vin
But : constater le comportement d’Etcd conforme à l’algorithme Raft.
virsh suspend <nom machine>
.Le leader peut être identifié avec la commande suivante :
# etcdctl -wtable --endpoints http://10.0.0.11:2379,http://10.0.0.12:2379,http://10.0.0.13:2379 endpoint status
Dans notre correctif, e1
est actuellement
leader. Dans une fenêtre sur e2
et
e3
, laisser défiler le journal :
# journalctl -fu etcd
Depuis une session dans le serveur hôte, interroger e2
ou e3
continuellement à propos de la santé de l’agrégat
avec par exemple :
$ watch -n1 "curl -s -XGET http://10.0.0.12:2379/health | jq"
Depuis le serveur hôte, cette commande interrompt brutalement la
machine virtuelle e1
(mais sans la détruire) :
# virsh destroy e1
Domain 'e1' destroyed
Il est aussi possible de suspendre la machine avec la commande suivante :
# virsh suspend e1
Domain 'e1' suspended
La commande inverse est alors virsh resume e1
.
Notez que la machine est suspendue, donc encore présente,
mais plus du tout exécutée par l’hyperviseur. Cette présence inactive
peut parfois créer des situations déstabilisantes pour les autres
machines virtuelles ayant toujours des connexions TCP en suspend. Aussi,
après son réveil, sauf présence de chrony
ou
ntpsec
, l’horloge de cette machine virtuelle nécessite une
intervention manuelle afin de la resynchroniser.
Dans les traces de e3, nous trouvons :
INFO: 49fc71338f77c1c4 is starting a new election at term 3
INFO: 49fc71338f77c1c4 became candidate at term 4
INFO: 49fc71338f77c1c4 received MsgVoteResp from 49fc71338f77c1c4 at term 4
INFO: 49fc71338f77c1c4 [logterm: 3, index: 9] sent MsgVote request to 97e570ef03022438 at term 4
INFO: 49fc71338f77c1c4 [logterm: 3, index: 9] sent MsgVote request to be3b2f45686f7694 at term 4
INFO: raft.node: 49fc71338f77c1c4 lost leader be3b2f45686f7694 at term 4
L’identifiant 49fc71338f77c1c4
est celui de
e3
lui-même. Ces traces nous indiquent :
e3
pour lui-même ;e1
et e2
.e1
(identifiant
be3b2f45686f7694
) durant le mandat n°4Suivent alors les messages suivants:
INFO: 49fc71338f77c1c4 received MsgVoteResp from 97e570ef03022438 at term 4
INFO: 49fc71338f77c1c4 has received 2 MsgVoteResp votes and 0 vote rejections
INFO: 49fc71338f77c1c4 became leader at term 4
INFO: raft.node: 49fc71338f77c1c4 elected leader 49fc71338f77c1c4 at term 4
Seul e2
(ici 97e570ef03022438
) répond au
vote, mais cette réponse suffit à atteindre le quorum (de 2 sur 3) et
valider l’élection de e3
comme nouveau leader.
En interrogeant l’état de e3, nous confirmons bien qu’il est leader :
$ etcdctl -wtable endpoint status
+----------------+------------------+---------+---------+-----------+-[…]
| ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | […]
+----------------+------------------+---------+---------+-----------+-[…]
| 127.0.0.1:2379 | 49fc71338f77c1c4 | 3.4.23 | 20 kB | true | […]
+----------------+------------------+---------+---------+-----------+-[…]
L’état de l’agrégat tombe en erreur suite à cette perte du quorum :
Dans les traces de e2
, nous constatons qu’il tente en
boucle une élection, envoie des messages mais faute de réponse,
n’obtient jamais d’accord pour l’élection :
15:36:31 INFO: 97e570ef03022438 is starting a new election at term 4
15:36:31 INFO: 97e570ef03022438 became candidate at term 5
15:36:31 INFO: 97e570ef03022438 received MsgVoteResp from 97e570ef03022438 at term 5
15:36:31 INFO: 97e570ef03022438 [logterm: 4, index: 1192] sent MsgVote request to 49fc71338f77c1c4 at term 5
15:36:31 INFO: 97e570ef03022438 [logterm: 4, index: 1192] sent MsgVote request to be3b2f45686f7694 at term 5
15:36:31 INFO: raft.node: 97e570ef03022438 lost leader 49fc71338f77c1c4 at term 5
15:36:33 INFO: 97e570ef03022438 is starting a new election at term 5
15:36:33 INFO: 97e570ef03022438 became candidate at term 6
15:36:33 INFO: 97e570ef03022438 received MsgVoteResp from 97e570ef03022438 at term 6
15:36:33 INFO: 97e570ef03022438 [logterm: 4, index: 1192] sent MsgVote request to 49fc71338f77c1c4 at term 6
15:36:33 INFO: 97e570ef03022438 [logterm: 4, index: 1192] sent MsgVote request to be3b2f45686f7694 at term 6
15:36:34 INFO: 97e570ef03022438 is starting a new election at term 6
15:36:34 INFO: 97e570ef03022438 became candidate at term 7
15:36:34 INFO: 97e570ef03022438 received MsgVoteResp from 97e570ef03022438 at term 7
15:36:34 INFO: 97e570ef03022438 [logterm: 4, index: 1192] sent MsgVote request to 49fc71338f77c1c4 at term 7
15:36:34 INFO: 97e570ef03022438 [logterm: 4, index: 1192] sent MsgVote request to be3b2f45686f7694 at term 7
La situation revient à la normale dès qu’un des deux autres revient
en ligne avec par exemple la commande virsh start e3
.
Patroni permet de :
des serveurs PostgreSQL en haute disponibilité.
pg_rewind
,
pg_basebackup
)patroni
disponiblepip
patronictl reload
bootstrap.dcs
du fichier
de configuration YAMLpatronictl edit-config
$PGDATA/patroni.dynamic.json
à intervalle
régulierPatroni définit trois paramètres globaux au cluster :
name
namespace
scope
etcd
ou etcd3
:
host
protocol
username
, password
cacert
, cert
, key
Le paramétrage de Patroni est :
bootstrap.dcs
Il est possible d’indiquer à Patroni comment construire les instances primaires et secondaires d’un agrégat. Les sections concernées sont :
bootstrap.method
et bootstrap.initdb
postgresql.create_replica_methods
bootstrap.post_bootstrap
et
bootstrap.post_init
bootstrap.dcs.postgresql
(parameters
,
pg_hba
, pg_ident
)postgresql
du YAML dynamique
postgresql.authentication
postgresql.parameters
postgresql.pg_hba
postgresql.pg_ident
postgresql.callbacks
bootstrap.dcs.standby_cluster
Initialisés depuis les sections suivantes :
bootstrap.dcs.slots
bootstrap.dcs.ignore_slots
log
:
type
: format des traces (pain
ou
json
)level
: niveau de logformat
: format des lignesdir
: répertoire où placer les journauxfile_num
: nombre de journaux à conserverfile_size
: taille maximale d’un journal avant
rotationPatroni expose une API REST :
patronictl
restapi
:
connect_address
listen
authentication
(username
,
password
)certfile
, keyfile
,
keyfile_password
, cafile
,
verify_client
)Une configuration spécifique est réservée au CLI
patronictl
:
ctl
insecure
certfile
keyfile
keyfile_password
cacert
watchdog
:
mode
: off
, automatic
ou
required
device
safety_margin
Patroni supporte différents marqueurs dans la section
tags
:
nofailover
clonefrom
noloadbalance
replicatefrom
nosync
failover_priority
group
database
PATRONICTL_CONFIG_FILE
PATRONI_SCOPE
list
: liste les membres d’un agrégat
par ordre alphabétiquetopology
: affiche la topologie d’un
agrégatshow-config
: affiche la configuration
dynamiqueedit-config
: édite la configuration
dynamique de l’agrégat
ALTER SYSTEM
!switchover
: promotion d’un
secondairefailover
: bascule par défaillance du
primairepause
,
resume
history
reinit
reload
restart
L’API REST permet de :
clé=valeur
target_session_attrs
\primary
\replica
Sur les machines créées précédemment :
- installer PostgreSQL sur les 2 nœuds depuis les dépôts PGDG
- installer Patroni sur les 2 nœuds depuis les dépôts PGDG
Avec Debian, ne pas utiliser l’intégration de Patroni dans la
structure de gestion multiinstance proposée par
postgresql-common
afin de se concentrer sur
l’apprentissage.
acme
sur les 2 nœudspatronictl
.curl
.postgres
sur le fichier /dev/watchdog
. Après
quelques secondes, que se passe-t-il ?Créer une table :
Insérer une ligne toutes les secondes, à chaque fois dans une nouvelle connexion au primaire.
Dans une autre fenêtre, afficher les 20 dernières lignes de cette table.
PGDATA
. Relancer Patroni.p1
.shared_buffers
et work_mem
. Si besoin, redémarrer les nœuds.Sur les machines créées précédemment :
- installer PostgreSQL sur les 2 nœuds depuis les dépôts PGDG
Si cette étape a déjà été réalisée, arrêter le service PostgreSQL et supprimer le cluster.
Activation du dépôt PGDG, à exécuter sur nœuds p1
et
p2
:
# apt update
[…]
# apt install postgresql-common
[…]
# /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y
This script will enable the PostgreSQL APT repository on apt.postgresql.org on
your system. The distribution codename used will be bookworm-pgdg.
Using keyring /usr/share/postgresql-common/pgdg/apt.postgresql.org.gpg
Writing /etc/apt/sources.list.d/pgdg.sources ...
Running apt-get update […]
Reading package lists... Done
You can now start installing packages from apt.postgresql.org.
Have a look at https://wiki.postgresql.org/wiki/Apt for more information;
most notably the FAQ at https://wiki.postgresql.org/wiki/Apt/FAQ
Désactiver la création automatique d’une instance durant l’installation :
mkdir -p /etc/postgresql-common/createcluster.d
echo create_main_cluster = false > /etc/postgresql-common/createcluster.d/custom.conf
Installation de PostgreSQL 16 :
# apt install postgresql-16
[…]
Setting up postgresql-16 (16.2-1.pgdg120+2) ...
Les dépôts PGDG fournissent le paquet patroni
:
# apt install patroni
acme
sur les 2 nœudsAvec Debian, ne pas utiliser l’intégration de Patroni dans la
structure de gestion multiinstance proposée par
postgresql-common
afin de se concentrer sur
l’apprentissage.
La configuration de Patroni se déroule dans le fichier
/etc/patroni/config.yml
. Dans le doute, ce fichier est
indiqué par la commande systemctl status patroni
.
Sur les nœuds p1
et p2
, commencer par
générer un fichier de configuration comme base de travail :
Dans ce fichier, éditer les variables suivantes :
scope: "acme"
: le scope, ou nom, du cluster. Ce nom
est utilisé au sein du DCS comme préfixe de toutes les clés ;name: "<hostname>"
: nom de la machine. Cette
valeur doit être différente pour chaque nœud ;log.level: INFO
: dans le cadre de ce TP, il est aussi
possible de positionner le niveau de log à DEBUG
;log.dir: '/var/log/patroni/acme'
: pour les besoins du
TP, afin de bien séparer les journaux de Patroni et PostgeSQL, nous
demandons à Patroni d’écrire lui-même ses journaux dans ce
répertoire ;restapi
: vérifier que l’adresse IP d’écoute est
correcte pour chaque serveur ;bootstrap.dcs.postgresql.use_pg_rewind: false
: il est
préférable de désactiver par défaut l’utilisation de
pg_rewind
. Cette fonctionnalité ne doit être activée que
sur certains environnements.postgresql.datadir: "/var/lib/postgresql/16/main"
:
emplacement du PGDATA
;postgresql.pg_hba:
: vérifier la cohérence des règles
pour les clients et la réplication. Éventuellement, facilitez-vous la
vie pour le reste du TP ;postgresql.bindir: "/usr/lib/postgresql/16/bin"
:
chemin vers les binaires de PostgreSQL ;postgresql.authentication.replication.password
:
positionner le mot de passe à attribuer au rôle
replicator
;postgresql.authentication.superuser.password
:
positionner le mot de passe à attribuer au rôle
postgres
;postgresql.listen
et
postgresql.connect_address
: vérifier que les adresses IP
sont correctes ;Une fois ce modèle complété, la section dédiée au DCS doit encore
être ajoutée. Collecter les adresses IP des nœuds etcd et ajouter à la
configuration la section etcd3
sur le modèle suivant :
Cette configuration est minimale. Libre à vous de modifier la façon dont les instances sont créées (activation des checksums, collation par défaut, etc), ajouter des règles, activer l’authentification etcd, …
Pour ce TP, comme nous plaçons les journaux de Patroni dans des fichiers, nous recommandons de faire de même pour PostgreSQL en ajoutant ces paramètres :
Ce paramétrage a pour seul but de faciliter ce TP. Ce n’est pas une
recommandation pour un serveur en production. Aucune gestion de
rotation, rétention ou externalisation n’est ici en place. Il est tout
aussi possible de se reposer sur journald
.
Voici un exemple de configuration obtenue sur le nœud p1 :
scope: 'acme'
name: p1
etcd3:
hosts:
- 10.0.0.11:2379
- 10.0.0.12:2379
- 10.0.0.13:2379
log:
format: '%(asctime)s %(levelname)s: %(message)s'
level: INFO
max_queue_size: 1000
traceback_level: ERROR
dir: '/var/log/patroni/acme'
restapi:
connect_address: 10.0.0.21:8008
listen: 10.0.0.21:8008
# The bootstrap configuration. Works only when the cluster is not yet initialized.
# If the cluster is already initialized, all changes in the `bootstrap` section are ignored!
bootstrap:
# This section will be written into <dcs>:/<namespace>/<scope>/config after initializing
# new cluster and all other cluster members will use it as a `global configuration`.
# WARNING! If you want to change any of the parameters that were set up
# via `bootstrap.dcs` section, please use `patronictl edit-config`!
dcs:
loop_wait: 10
retry_timeout: 10
ttl: 30
postgresql:
parameters:
hot_standby: 'on'
max_connections: 100
max_locks_per_transaction: 64
max_prepared_transactions: 0
max_replication_slots: 10
max_wal_senders: 10
max_worker_processes: 8
track_commit_timestamp: 'off'
wal_keep_size: 128MB
wal_level: replica
wal_log_hints: 'on'
use_pg_rewind: false
use_slots: true
postgresql:
authentication:
replication:
password: 'pass'
username: replicator
superuser:
password: 'pass'
username: postgres
bin_dir: '/usr/lib/postgresql/16/bin'
connect_address: 10.0.0.21:5432
data_dir: '/var/lib/postgresql/16/main'
listen: 10.0.0.21:5432
parameters:
password_encryption: scram-sha-256
logging_collector: on
log_destination: stderr
pg_hba:
- local all all trust
- host all all all trust
- host replication replicator all scram-sha-256
tags:
clonefrom: true
failover_priority: 1
noloadbalance: false
nosync: false
Assurez-vous que ce fichier de configuration est bien accessible à
l’utilisateur postgres
et créer le répertoire nécessaire
aux journaux applicatifs :
Nous pouvons désormais valider la configuration :
patroni --validate /etc/patroni/config.yml
Le code retour de la commande est 0
si tout est valide.
Sinon, la commande affiche les avertissements appropriés.
Il est maintenant possible de démarrer le service Patroni. Nous
commençons d’abord sur p1
pour créer l’instance :
# systemctl start patroni
Puis nous démarrons le service sur p2
, qui va donc créer
le secondaire :
# systemctl start patroni
Sur p1, nous trouvons les messages suivants dans le journal
/var/log/patroni/acme/patroni.log
:
INFO: Selected new etcd server http://10.20.0.11:2379
INFO: No PostgreSQL configuration items changed, nothing to reload.
INFO: Lock owner: None; I am p1
INFO: trying to bootstrap a new cluster
INFO: postmaster pid=5541
INFO: establishing a new patroni heartbeat connection to postgres
INFO: running post_bootstrap
WARNING: Could not activate Linux watchdog device: Can't open watchdog device: [Errno 2] No such file or directory: '/dev/watchdog'
INFO: initialized a new cluster
INFO: no action. I am (p1), the leader with the lock
Le démon Patroni démarre, choisi un serveur etcd, se saisit du
leader lock et initialise l’instance PostgreSQL locale. Les
sorties standard et d’erreur des commandes exécutées par Patroni n’est
pas capturée vers le journal de ce dernier. Ces commandes sont donc
capturées par journald et associées au service patroni
.
Nous y retrouvons par exemple la sortie de initdb
:
# journalctl -u patroni
[…]
patroni: The files belonging to this database system will be owned by user "postgres".
patroni: This user must also own the server process.
patroni: The database cluster will be initialized with locale "C.UTF-8".
[…]
patroni: Success. You can now start the database server using:
patroni: /usr/lib/postgresql/16/bin/pg_ctl -D /var/lib/postgresql/16/main -l logfile start
Sur p2
, nous trouvons dans le journal
correspondant :
INFO: Selected new etcd server http://10.0.0.13:2379
INFO: No PostgreSQL configuration items changed, nothing to reload.
INFO: Lock owner: p1; I am p2
INFO: trying to bootstrap from leader 'p1'
INFO: replica has been created using basebackup
INFO: bootstrapped from leader 'p1'
INFO: postmaster pid=4551
INFO: Lock owner: p1; I am p2
INFO: establishing a new patroni heartbeat connection to postgres
INFO: no action. I am (p2), a secondary, and following a leader (p1)
Comme sur p1
, le démon Patroni démarre et choisi un
serveur etcd, mais il découvre le leader lock appartient déjà à
p1
. L’instance PostgreSQL locale n’existant pas, Patroni
décide de la créer depuis celle de p1
.
Dans les deux cas, la configuration par défaut des journaux
applicatifs de PostgreSQL les places dans le répertoire
PGDATA/log
, donc ici
/var/lib/postgresql/16/main/log
, équivalent à
~postgres/16/main/log
.
patronictl
.L’utilisation de patronictl
nécessite un fichier de
configuration permettant de déterminer le nom du cluster et idéalement
les nœuds du DCS. Sur les machines p1
et p2
,
nous pouvons utiliser directement le fichier de configuration de Patroni
/etc/patroni/config.yml
. La commande devient :
$ patronictl -c /etc/patroni/config.yml topology
+ Cluster: acme (7349612307776631369) -----+----+-----------+-------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+--------+-----------+---------+-----------+----+-----------+-------+
| p1 | 10.0.0.21 | Leader | running | 1 | | […] |
| + p2 | 10.0.0.22 | Replica | streaming | 1 | 0 | […] |
+--------+-----------+---------+-----------+----+-----------+-------+
Nous constatons que p1
héberge bien l’instance primaire
et p2
la secondaire.
Pour simplifier cette commande, il est possible de positionner dans
votre environnement le variable PATRONICTL_CONFIG_FILE
. Par
exemple :
$ export PATRONICTL_CONFIG_FILE=/etc/patroni/config.yml
$ patronictl topology
[…]
Pour la positionner automatiquement, vous pouvez par exemple créer le
fichier /etc/profile.d/99-patroni.sh
avec le contenu
suivant :
cat <<EOF > /etc/profile.d/99-patroni.sh
PATRONICTL_CONFIG_FILE=/etc/patroni/config.yml
export PATRONICTL_CONFIG_FILE
EOF
chmod +x /etc/profile.d/99-patroni.sh
Répéter les étapes précédentes sur le serveur p3
:
p3
.Après l’ajout du troisième nœud, la topologie est la suivante :
$ patronictl topology
+ Cluster: acme (7349612307776631369) -----+----+-----------+------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+--------+-----------+---------+-----------+----+-----------+------+
| p1 | 10.0.0.21 | Leader | running | 1 | | […] |
| + p2 | 10.0.0.22 | Replica | streaming | 1 | 0 | […] |
| + p3 | 10.0.0.23 | Replica | streaming | 1 | 0 | […] |
+--------+-----------+---------+-----------+----+-----------+------+
Il est conseillé lors des tests de garder une fenêtre répétant l’ordre régulièrement :
curl
.La configuration est visible de l’extérieur :
$ curl -s http://p1:8008/patroni | jq
{
"state": "running",
"postmaster_start_time": "[…]",
"role": "master",
"server_version": 160002,
"xlog": {
"location": 50534136
},
"timeline": 1,
"replication": [
{
"usename": "replicator",
"application_name": "p2",
"client_addr": "10.0.0.22",
"state": "streaming",
"sync_state": "async",
"sync_priority": 0
},
{
"usename": "replicator",
"application_name": "p3",
"client_addr": "10.0.0.23",
"state": "streaming",
"sync_state": "async",
"sync_priority": 0
}
],
"dcs_last_seen": 1711217105,
"tags": {
"clonefrom": true,
"failover_priority": 1
},
"database_system_identifier": "7349612307776631369",
"patroni": {
"version": "3.2.2",
"scope": "acme",
"name": "p1"
}
}
De manière plus précise :
$ curl -s http://p1:8008/cluster |\
jq '.members[] | select ((.role == "leader" ) and ( .state == "running")) | { name }'
Par défaut, les slots de réplications portent le nom des nœuds réplicas.
$ curl -s http://p1:8008/cluster | jq '.members[] | select (.role == "replica") | { name }'
On peut le vérifier en se connectant à PostgreSQL sur le serveur
primaire (ici p1
) :
$ sudo -iu postgres
$ psql -c "SELECT slot_name FROM pg_replication_slots"
slot_name
-----------
p2
p3
Ajoutez la section watchdog dans le fichier de configuration de
Patroni /etc/patroni/config.yml
en positionnant
mode: required
:
Il nous faut recharger la configuration de Patroni :
$ patronictl reload acme
+ Cluster: acme (7349612307776631369) --------+----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+--------+-----------+---------+--------------+----+-----------+
| p1 | 10.0.0.21 | Replica | start failed | | unknown |
| p2 | 10.0.0.22 | Replica | start failed | | unknown |
| p3 | 10.0.0.23 | Leader | running | 6 | |
+--------+-----------+---------+--------------+----+-----------+
Are you sure you want to reload members p1, p2, p3? [y/N]: y
Reload request received for member p1 and will be processed within 10 seconds
Reload request received for member p2 and will be processed within 10 seconds
Reload request received for member p3 and will be processed within 10 seconds
Suite à cette modification, le cluster Patroni se retrouve sans
primaire. Dans les journaux applicatif de p3
, ici
précédemment marqué Leader, nous trouvons :
INFO: Reloading PostgreSQL configuration.
INFO: Lock owner: p3; I am p3
ERROR: Configuration requires watchdog, but watchdog could not be configured.
INFO: Demoting self (immediate)
INFO: Leader key released
INFO: Demoting self because watchdog could not be activated
INFO: Lock owner: None; I am p3
INFO: not healthy enough for leader race
INFO: starting after demotion in progress
INFO: closed patroni connections to postgres
INFO: postmaster pid=561
INFO: establishing a new patroni heartbeat connection to postgres
WARNING: Watchdog device is not usable
INFO: Dropped unknown replication slot 'p1'
INFO: Dropped unknown replication slot 'p2'
INFO: following a different leader because i am not the healthiest node
postgres
sur le fichier /dev/watchdog
. Après
quelques secondes, que se passe-t-il ?Un simple chown
sur le device /dev/watchdog
de chaque VM peut suffire, au moins le temps de ce TP. Néanmoins, il ne
survivrait pas au e de la machine.
Le fichier de service de Patroni propose de systématiquement modifier les droits sur ce device décommentant la ligne suivante :
Une autre solution est de configurer udev
afin qu’il
modifie les droits sur ce fichier automatiquement à chaque
démarrage :
cat <<'EOF' > /etc/udev/rules.d/99-watchdog.rules
# give writes on watchdog device to postgres
SUBSYSTEM=="misc", KERNEL=="watchdog", ACTION=="add", RUN+="/bin/chown postgres /dev/watchdog"
# Or a better solution using ACL:
#SUBSYSTEM=="misc", KERNEL=="watchdog", ACTION=="add", RUN+="/bin/setfacl -m u:postgres:rw- /dev/watchdog"
EOF
Une fois les droits positionnés, l’un des nœuds Patroni devrait finalement réussir à activer le watchdog et ainsi devenir leader et promouvoir son instance PostgreSQL :
INFO: i6300ESB timer activated with 25 second timeout, timing slack 15 seconds
INFO: promoted self to leader by acquiring session lock
INFO: Lock owner: p2; I am p2
INFO: updated leader lock during promote
INFO: Lock owner: p2; I am p2
INFO: no action. I am (p2), the leader with the lock
La connexion extérieure peut se faire depuis la machine hôte des VM.
Il est nécessaire d’y installer un client PostgreSQL, par exemple
postgresql-client
. Pour se connecter à l’instance
p1
, utiliser l’une des commandes suivantes :
$ psql -h p1 -p 5432 -U postgres -d postgres
$ psql -h 10.0.0.21 postgres postgres
En cas de problème, il faut regarder :
pg_hba.conf
.Il est nécessaire de se connecter à l’instance primaire pour réaliser
des écritures en base. La chaîne de connexion permettant d’indiquer
plusieurs nœuds, nous pouvons y préciser tous les nœuds du cluster. Afin
de sélectionner le nœud primaire, il suffit d’ajouter le paramètre
target_session_attrs=primary
ou
target_session_attrs=read-write
.
Par exemple :
psql "host=p1,p2,p3 user=postgres target_session_attrs=primary"
psql "postgresql://postgres@p1,p2,p3/postgres?target_session_attrs=read-write"
Pour une connexion en lecture seule, nous utilisons par exemple :
Créer une table :
Insérer une ligne toutes les secondes, à chaque fois dans une nouvelle connexion au primaire.
Dans une autre fenêtre, afficher les 20 dernières lignes de cette table.
Dans la colonne source
de la table créée, la valeur par
défaut fait appel à la fonction inet_server_addr()
qui
retourne l’adresse IP du serveur PostgreSQL sur lequel nous sommes
connectés.
Lancer une insertion toutes les secondes :
watch -n1 'psql -X -d "host=p1,p2,p3 user=postgres target_session_attrs=primary" \
-c "INSERT INTO insertions SELECT;"'
Lire les vingt dernières lignes de la table :
watch -n1 'psql -X -d "host=p1,p2,p3 port=5432 user=postgres" \
-c "SELECT * FROM insertions ORDER BY d DESC LIMIT 20"'
Bien entendu, nous obtenons toujours le même nœud dans la colonne
source
, ici p1
:
id | d | source
-----+-------------------------------+---------------
…
313 | 2024-03-05 15:40:03.118307+00 | 10.0.0.21/32
312 | 2024-03-05 15:40:02.105116+00 | 10.0.0.21/32
311 | 2024-03-05 15:40:01.090775+00 | 10.0.0.21/32
310 | 2024-03-05 15:40:00.075847+00 | 10.0.0.21/32
309 | 2024-03-05 15:39:59.061759+00 | 10.0.0.21/32
308 | 2024-03-05 15:39:58.048074+00 | 10.0.0.21/32
(20 lignes)
Sur p1 :
# systemctl stop patroni
Après l’arrêt de p1
, p3
prend le rôle de
leader :
$ patronictl -c /etc/patroni/config.yml topology
+ Cluster: acme (7349612307776631369) ---+----+-----------+------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+--------+-----------+---------+---------+----+-----------+------+
| p3 | 10.0.0.23 | Leader | running | 2 | | […] |
| + p1 | 10.0.0.21 | Replica | stopped | | unknown | […] |
| + p2 | 10.0.0.22 | Replica | running | 1 | 0 | […] |
+--------+-----------+---------+---------+----+-----------+------+
Les insertions échouent le temps de la bascule, ici pendant environ 2 secondes, puis continuent depuis l’autre nœud :
id | d | source
-----+-------------------------------+---------------
…
445 | 2024-03-24 15:50:17.840394+00 | 10.0.0.23/32
444 | 2024-03-24 15:50:16.823004+00 | 10.0.0.23/32
431 | 2024-03-24 15:50:14.755045+00 | 10.0.0.21/32
430 | 2024-03-24 15:50:13.740541+00 | 10.0.0.21/32
Le leader Patroni a changé à nouveau :
$ patronictl -c /etc/patroni/config.yml topology
+ Cluster: acme (7349612307776631369) --+----+-----------+------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+--------+-----------+--------+---------+----+-----------+------+
| p2 | 10.0.0.22 | Leader | running | 6 | | […] |
+--------+-----------+--------+---------+----+-----------+------+
Sur e1
et e2
:
Il n’y a plus de quorum etcd garantissant une référence. Le cluster Patroni se met en lecture seule et les insertions tombent en échec puisqu’elles exigent une connexion ouverte en écriture :
psql: error: connection to server at "p1" (10.0.0.21), port 5432 failed: Connection refused
Is the server running on that host and accepting TCP/IP connections?
connection to server at "p2" (10.0.0.22), port 5432 failed: server is in hot standby mode
connection to server at "p3" (10.0.0.23), port 5432 failed: Connection refused
Is the server running on that host and accepting TCP/IP connections?
Les écritures reprennent.
Dans le cadre de cette correction p2
est l’actuel
leader, nous redémarrons donc Patroni sur p1
:
L’instance sur p1
se raccroche en secondaire :
$ patronictl -c /etc/patroni/config.yml topology
+ Cluster: acme (7349612307776631369) -----+----+-----------+------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+--------+-----------+---------+-----------+----+-----------+------+
| p2 | 10.0.0.22 | Leader | running | 7 | | […] |
| + p1 | 10.0.0.21 | Replica | streaming | 7 | 0 | […] |
+--------+-----------+---------+-----------+----+-----------+------+
PGDATA
. Relancer Patroni.Le PGDATA de nos instances se trouve dans
/var/lib/postgresql/16/main
. Supprimons ce
PGDATA
sur p3
, le nœud restant :
Relançons Patroni sur ce nœud :
Nous observons dans les journaux de Patroni et PostgreSQL que
l’instance est recrée et se raccroche à p2
:
LOG: starting PostgreSQL 16.2 (Debian 16.2-1.pgdg120+2) […]
LOG: listening on IPv4 address "10.0.0.23", port 5432
LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
LOG: database system was interrupted while in recovery at log time […]
HINT: If this has occurred more than once some data might be corrupted and you might need to choose an earlier recovery target.
LOG: entering standby mode
LOG: starting backup recovery with redo LSN 0/45405620, checkpoint LSN 0/4FD4D788, on timeline ID 7
LOG: redo starts at 0/45405620
LOG: completed backup recovery with redo LSN 0/45405620 and end LSN 0/509EA568
LOG: consistent recovery state reached at 0/509EA568
LOG: database system is ready to accept read-only connections
LOG: started streaming WAL from primary at 0/50000000 on timeline 7
Le temps de la reconstruction de zéro, il est possible de voir
l’évolution de l’état de p3
de
creating replica
à streaming
:
$ export PATRONICTL_CONFIG_FILE=/etc/patroni/config.yml
$ patronictl topology
+ Cluster: acme (7349612307776631369) ------------+----+-----------+------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+--------+-----------+---------+------------------+----+-----------+------+
| p2 | 10.0.0.22 | Leader | running | 7 | | […] |
| + p1 | 10.0.0.21 | Replica | streaming | 7 | 0 | […] |
| + p3 | 10.0.0.23 | Replica | creating replica | | unknown | […] |
+--------+-----------+---------+------------------+----+-----------+------+
/* Après un certain temps */
$ patronictl topology
+ Cluster: acme (7349612307776631369) -----+----+-----------+------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+--------+-----------+---------+-----------+----+-----------+------+
| p2 | 10.0.0.22 | Leader | running | 7 | | […] |
| + p1 | 10.0.0.21 | Replica | streaming | 7 | 0 | […] |
| + p3 | 10.0.0.23 | Replica | streaming | 7 | 0 | […] |
+--------+-----------+---------+-----------+----+-----------+------+
p1
.$ patronictl failover
Current cluster topology
+ Cluster: acme (7349612307776631369) -----+----+-----------+------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+--------+-----------+---------+-----------+----+-----------+------+
| p1 | 10.0.0.21 | Replica | streaming | 7 | 0 | […] |
+--------+-----------+---------+-----------+----+-----------+------+
| p2 | 10.0.0.22 | Leader | running | 7 | | […] |
+--------+-----------+---------+-----------+----+-----------+------+
| p3 | 10.0.0.23 | Replica | streaming | 7 | 0 | […] |
+--------+-----------+---------+-----------+----+-----------+------+
Candidate ['p1', 'p3'] []: p1
Are you sure you want to failover cluster acme, demoting current leader p2? [y/N]: y
[…] Successfully failed over to "p1"
+ Cluster: acme (7349612307776631369) ---+----+-----------+------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+--------+-----------+---------+---------+----+-----------+------+
| p1 | 10.0.0.21 | Leader | running | 7 | | […] |
+--------+-----------+---------+---------+----+-----------+------+
| p2 | 10.0.0.22 | Replica | stopped | | unknown | […] |
+--------+-----------+---------+---------+----+-----------+------+
| p3 | 10.0.0.23 | Replica | running | 7 | 0 | […] |
+--------+-----------+---------+---------+----+-----------+------+
$ patronictl topology
+ Cluster: acme (7349612307776631369) -----+----+-----------+------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+--------+-----------+---------+-----------+----+-----------+------+
| p1 | 10.0.0.21 | Leader | running | 8 | | […] |
| + p2 | 10.0.0.22 | Replica | streaming | 8 | 0 | […] |
| + p3 | 10.0.0.23 | Replica | streaming | 8 | 0 | […] |
+--------+-----------+---------+-----------+----+-----------+------+
shared_buffers
et work_mem
. Si besoin, redémarrer les nœuds.Pour modifier la configuration, nous devons utiliser
patronictl
, plutôt qu’éditer directement les fichiers de
configuration. Une alternative est de modifier la configuration
statique, dans le fichier YAML “/etc/patroni/config.yml”. Cette méthode
facilite leur maintenance, mais impose que le contenu soit identique sur
tous les nœuds, ce qui est généralement le cas dans un déploiement
industrialisé.
La commande patronictl edit-config
appelle l’éditeur par
défaut, souvent vi
, vim
ou nano
.
Vous pouvez modifier la variable d’environnement EDITOR
pour pointer sur votre éditeur favori.
Éditons la configuration dynamique et ajoutons les deux paramètres :
---
+++
@@ -12,6 +12,8 @@
wal_keep_size: 128MB
wal_level: replica
wal_log_hints: 'on'
+ shared_buffers: 300MB
+ work_mem: 50MB
use_pg_rewind: false
use_slots: true
retry_timeout: 10
Apply these changes? [y/N]: y
Configuration changed
Les nœuds sont à redémarrer à cause de la modification de
shared_buffers
:
$ patronictl topology
+ Cluster: acme (7349612307776631369) -----+----+------+-----------------+------+
| Member | Host | Role | State | TL | Lag… | Pending restart | Tags |
+--------+-----------+---------+-----------+----+------+-----------------+------+
| p1 | 10.0.0.21 | Leader | running | 8 | | * | […] |
| + p2 | 10.0.0.22 | Replica | streaming | 8 | 0 | * | […] |
| + p3 | 10.0.0.23 | Replica | streaming | 8 | 0 | * | […] |
+--------+-----------+---------+-----------+----+------+-----------------+------+
Commandons le redémarrage de PostgreSQL sur les trois nœuds :
$ patronictl restart acme
+ Cluster: acme (7349612307776631369) -----+----+------+-----------------+------+
| Member | Host | Role | State | TL | Lag… | Pending restart | Tags |
+--------+-----------+---------+-----------+----+------+-----------------+------+
| p1 | 10.0.0.21 | Leader | running | 8 | | * | […] |
+--------+-----------+---------+-----------+----+------+-----------------+------+
| p2 | 10.0.0.22 | Replica | streaming | 8 | 0 | * | […] |
+--------+-----------+---------+-----------+----+------+-----------------+------+
| p3 | 10.0.0.23 | Replica | streaming | 8 | 0 | * | […] |
+--------+-----------+---------+-----------+----+------+-----------------+------+
When should the restart take place (e.g. […]) [now]:
Are you sure you want to restart members p1, p2, p3? [y/N]: y
Restart if the PostgreSQL version is less than provided (e.g. 9.5.2) []:
Success: restart on member p1
Success: restart on member p2
Success: restart on member p3
Vérifions que le paramétrage a bien été modifié :
$ for h in p1 p2 p3; do echo -ne $h:; psql -Xtd "host=$h user=postgres" -c "show shared_buffers"; done
p1: 300MB
p2: 300MB
p3: 300MB
Noter que le contenu des modifications est tracé dans un fichier
patroni.dynamic.json
dans le PGDATA
:
$ jq . patroni.dynamic.json
{
"loop_wait": 10,
"postgresql": {
"parameters": {
"hot_standby": "on",
"max_connections": 100,
"max_locks_per_transaction": 64,
"max_prepared_transactions": 0,
"max_replication_slots": 10,
"max_wal_senders": 10,
"max_worker_processes": 8,
"track_commit_timestamp": "off",
"wal_keep_size": "128MB",
"wal_level": "replica",
"wal_log_hints": "on",
"shared_buffers": "300MB",
"work_mem": "50MB"
},
"use_pg_rewind": false,
"use_slots": true
},
"retry_timeout": 10,
"ttl": 30
}
Sur les machines créées précédemment :
- installer PostgreSQL sur les 2 nœuds depuis les dépôts PGDG
Si cette opération a déjà été réalisée précédemment, stopper le service PostgreSQL, le désactiver et supprimer le répertoire de données de l’instance.
Activation du dépôt PGDG, à exécuter sur nœuds p1
et
p2
:
# dnf install https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/\
pgdg-redhat-repo-latest.noarch.rpm
# dnf install -y postgresql16-server
[…]
Installed:
postgresql16-16.2-1PGDG.rhel9.x86_64
postgresql16-libs-16.2-1PGDG.rhel9.x86_64
postgresql16-server-16.2-1PGDG.rhel9.x86_64
Complete!
Les dépôts PGDG fournissent le paquet patroni
:
acme
sur les 2 nœudsLa configuration de Patroni se déroule dans le fichier
/etc/patroni/patroni.yml
.
Sur les nœuds p1
et p2
, commencer par
générer un fichier de configuration comme base de travail :
Dans ce fichier, éditer les variables suivantes :
scope: "acme"
: le scope, ou nom, du cluster. Ce nom
est utilisé au sein du DCS comme préfixe de toutes les clés ;name: "<hostname>"
: nom de la machine. Cette
valeur doit être différente pour chaque nœud ;log.level: INFO
: dans le cadre de ce TP, il est aussi
possible de positionner le niveau de log à DEBUG
;log.dir: '/var/log/patroni/acme'
: pour les besoins du
TP, afin de bien séparer les journaux de Patroni et PostgeSQL, nous
demandons à Patroni d’écrire lui-même ses journaux dans ce
répertoire ;restapi
: vérifier que l’adresse IP d’écoute est
correcte pour chaque serveur ;bootstrap.dcs.postgresql.use_pg_rewind: false
: il est
préférable de désactiver par défaut l’utilisation de
pg_rewind
. Cette fonctionnalité ne doit être activée que
sur certains environnements.postgresql.datadir: "/var/lib/pgsql/16/data"
:
emplacement du PGDATA
;postgresql.pg_hba:
: vérifier la cohérence des règles
pour les clients et la réplication. Éventuellement, facilitez-vous la
vie pour le reste du TP ;postgresql.bindir: "/usr/pgsql-16/bin"
: chemin vers
les binaires de PostgreSQL ;postgresql.authentication.replication.password
:
positionner le mot de passe à attribuer au rôle
replicator
;postgresql.authentication.superuser.password
:
positionner le mot de passe à attribuer au rôle
postgres
;postgresql.listen
et
postgresql.connect_address
: vérifier que les adresses IP
sont correctes ;Une fois ce modèle complété, la section dédiée au DCS doit encore
être ajoutée. Collecter les adresses IP des nœuds etcd et ajouter à la
configuration la section etcd3
sur le modèle suivant :
Cette configuration est minimale. Libre à vous de modifier la façon dont les instances sont créées (activation des checksums, collation par défaut, etc), ajouter des règles, activer l’authentification etcd, …
Pour ce TP, comme nous plaçons les journaux de Patroni dans des fichiers, nous recommandons de faire de même pour PostgreSQL en ajoutant ces paramètres :
Ce paramétrage a pour seul but de faciliter ce TP. Ce n’est pas une
recommandation pour un serveur en production. Aucune gestion de
rotation, rétention ou externalisation n’est ici en place. Il est tout
aussi possible de se reposer sur journald
.
Voici un exemple de configuration obtenue sur le nœud p1 :
scope: 'acme'
name: p1.hapat.vm
etcd3:
hosts:
- 10.0.0.11:2379
- 10.0.0.12:2379
- 10.0.0.13:2379
log:
format: '%(asctime)s %(levelname)s: %(message)s'
level: INFO
max_queue_size: 1000
traceback_level: ERROR
dir: '/var/log/patroni/acme'
restapi:
connect_address: 10.0.0.21:8008
listen: 10.0.0.21:8008
# The bootstrap configuration. Works only when the cluster is not yet initialized.
# If the cluster is already initialized, all changes in the `bootstrap` section are ignored!
bootstrap:
# This section will be written into <dcs>:/<namespace>/<scope>/config after initializing
# new cluster and all other cluster members will use it as a `global configuration`.
# WARNING! If you want to change any of the parameters that were set up
# via `bootstrap.dcs` section, please use `patronictl edit-config`!
dcs:
loop_wait: 10
retry_timeout: 10
ttl: 30
postgresql:
parameters:
hot_standby: 'on'
max_connections: 100
max_locks_per_transaction: 64
max_prepared_transactions: 0
max_replication_slots: 10
max_wal_senders: 10
max_worker_processes: 8
track_commit_timestamp: 'off'
wal_keep_size: 128MB
wal_level: replica
wal_log_hints: 'on'
use_pg_rewind: false
use_slots: true
postgresql:
authentication:
replication:
password: 'pass'
username: replicator
superuser:
password: 'pass'
username: postgres
bin_dir: '/usr/pgsql-16/bin'
connect_address: 10.0.0.21:5432
data_dir: '/var/lib/pgsql/16/data'
listen: 10.0.0.21:5432
parameters:
password_encryption: scram-sha-256
logging_collector: on
log_destination: stderr
pg_hba:
- local all all trust
- host all all all trust
- host replication replicator all scram-sha-256
tags:
clonefrom: true
failover_priority: 1
noloadbalance: false
nosync: false
Assurez-vous que ce fichier de configuration est bien accessible à
l’utilisateur postgres
et créer le répertoire nécessaire
aux journaux applicatifs :
Nous pouvons désormais valider la configuration :
patroni --validate /etc/patroni/patroni.yml
Le code retour de la commande est 0
si tout est valide.
Sinon, la commande affiche les avertissements appropriés.
Il est maintenant possible de démarrer le service Patroni. Nous
commençons d’abord sur p1
pour créer l’instance :
# systemctl start patroni
Puis nous démarrons le service sur p2
, qui va donc créer
le secondaire :
# systemctl start patroni
Sur p1, nous trouvons les messages suivants dans le journal
/var/log/patroni/acme/patroni.log
:
INFO: Selected new etcd server http://10.0.0.11:2379
INFO: No PostgreSQL configuration items changed, nothing to reload.
INFO: Lock owner: None; I am p1.hapat.vm
INFO: trying to bootstrap a new cluster
INFO: postmaster pid=18415
INFO: establishing a new patroni heartbeat connection to postgres
INFO: running post_bootstrap
WARNING: Could not activate Linux watchdog device: Can't open watchdog device: [Errno 13] Permission denied: '/dev/watchdog'
INFO: initialized a new cluster
INFO: no action. I am (p1.hapat.vm), the leader with the lock
Le démon Patroni démarre, choisi un serveur etcd, se saisit du
leader lock et initialise l’instance PostgreSQL locale. Les
sorties standard et d’erreur des commandes exécutées par Patroni n’est
pas capturée vers le journal de ce dernier. Ces commandes sont donc
capturées par journald et associées au service patroni
.
Nous y retrouvons par exemple la sortie de initdb
:
# journalctl -u patroni
[…]
patroni: The files belonging to this database system will be owned by user "postgres".
patroni: This user must also own the server process.
patroni: The database cluster will be initialized with locale "C.UTF-8".
[…]
patroni: Success. You can now start the database server using:
patroni: /usr/pgsql-16/bin/pg_ctl -D /var/lib/pgsql/16/data -l logfile start
Sur p2
, nous trouvons dans le journal
correspondant :
INFO: Selected new etcd server http://10.0.0.13:2379
INFO: No PostgreSQL configuration items changed, nothing to reload.
INFO: Lock owner: p1.hapat.vm; I am p2.hapat.vm
INFO: trying to bootstrap from leader 'p1.hapat.vm'
INFO: replica has been created using basebackup
INFO: bootstrapped from leader 'p1.hapat.vm'
INFO: postmaster pid=18195
INFO: Lock owner: p1.hapat.vm; I am p2.hapat.vm
INFO: establishing a new patroni heartbeat connection to postgres
INFO: no action. I am (p2.hapat.vm), a secondary, and following a leader (p1.hapat.vm)
Comme sur p1
, le démon Patroni démarre et choisi un
serveur etcd, mais il découvre le leader lock appartient déjà à
p1
. L’instance PostgreSQL locale n’existant pas, Patroni
décide de la créer depuis celle de p1
.
Dans les deux cas, la configuration par défaut des journaux
applicatifs de PostgreSQL les places dans le répertoire
PGDATA/log
, donc ici
/var/lib/pgsql/16/data/log/
, équivalent à
~postgres/16/data/log
.
patronictl
.L’utilisation de patronictl
nécessite un fichier de
configuration permettant de déterminer le nom du cluster et idéalement
les nœuds du DCS. Sur les machines p1
et p2
,
nous pouvons utiliser directement le fichier de configuration de Patroni
/etc/patroni/patroni.yml
. La commande devient :
$ patronictl -c /etc/patroni/patroni.yml topology
+ Cluster: acme (7350009258581743592) +-----------+----+-----------+------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+---------------+-----------+---------+-----------+----+-----------+------+
| p1.hapat.vm | 10.0.0.21 | Leader | running | 1 | | […] |
| + p2.hapat.vm | 10.0.0.22 | Replica | streaming | 1 | 0 | […] |
+---------------+-----------+---------+-----------+----+-----------+------+
Nous constatons que p1
héberge bien l’instance primaire
et p2
la secondaire.
Pour simplifier cette commande, il est possible de positionner dans
votre environnement le variable PATRONICTL_CONFIG_FILE
. Par
exemple :
$ export PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml
$ patronictl topology
[…]
Pour la positionner automatiquement, vous pouvez par exemple créer le
fichier /etc/profile.d/99-patroni.sh
avec le contenu
suivant :
cat <<EOF > /etc/profile.d/99-patroni.sh
PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml
export PATRONICTL_CONFIG_FILE
EOF
chmod +x /etc/profile.d/99-patroni.sh
Répéter les étapes précédentes sur le serveur p3
:
p3
.Après l’ajout du troisième nœud, la topologie est la suivante :
$ patronictl topology
+ Cluster: acme (7350009258581743592) +-----------+----+-----------+------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+---------------+-----------+---------+-----------+----+-----------+------+
| p1.hapat.vm | 10.0.0.21 | Leader | running | 1 | | […] |
| + p2.hapat.vm | 10.0.0.22 | Replica | streaming | 1 | 0 | […] |
| + p3.hapat.vm | 10.0.0.23 | Replica | streaming | 1 | 0 | […] |
+---------------+-----------+---------+-----------+----+-----------+------+
Il est conseillé lors des tests de garder une fenêtre répétant l’ordre régulièrement :
curl
.La configuration est visible de l’extérieur :
$ curl -s http://p1:8008/patroni | jq
{
"state": "running",
"postmaster_start_time": "[…]",
"role": "master",
"server_version": 160002,
"xlog": {
"location": 50651960
},
"timeline": 1,
"replication": [
{
"usename": "replicator",
"application_name": "p2.hapat.vm",
"client_addr": "10.0.0.22",
"state": "streaming",
"sync_state": "async",
"sync_priority": 0
},
{
"usename": "replicator",
"application_name": "p3.hapat.vm",
"client_addr": "10.0.0.23",
"state": "streaming",
"sync_state": "async",
"sync_priority": 0
}
],
"dcs_last_seen": 1711308128,
"tags": {
"clonefrom": true,
"failover_priority": 1
},
"database_system_identifier": "7350009258581743592",
"patroni": {
"version": "3.2.2",
"scope": "acme",
"name": "p1.hapat.vm"
}
}
De manière plus précise :
$ curl -s http://p1:8008/cluster |\
jq '.members[] | select ((.role == "leader" ) and ( .state == "running")) | { name }'
Par défaut, les slots de réplications portent le nom des nœuds réplicas.
$ curl -s http://p1:8008/cluster | jq '.members[] | select (.role == "replica") | { name }'
On peut le vérifier en se connectant à PostgreSQL sur le serveur
primaire (ici p1
) :
$ sudo -iu postgres
$ psql -c "SELECT slot_name FROM pg_replication_slots"
slot_name
-----------
p2_hapat_vm
p3_hapat_vm
Ajoutez la section watchdog dans le fichier de configuration de
Patroni /etc/patroni/patroni.yml
en positionnant
mode: required
:
Il nous faut recharger la configuration de Patroni :
$ patronictl reload acme
+ Cluster: acme (7350009258581743592) -------------+----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+-------------+-----------+---------+--------------+----+-----------+
| p1.hapat.vm | 10.0.0.21 | Replica | start failed | | unknown |
| p2.hapat.vm | 10.0.0.22 | Replica | start failed | | unknown |
| p3.hapat.vm | 10.0.0.23 | Leader | running | 6 | |
+-------------+-----------+---------+--------------+----+-----------+
Are you sure you want to reload members p1.hapat.vm, p2.hapat.vm, p3.hapat.vm? [y/N]: y
Reload request received for member p1.hapat.vm and will be processed within 10 seconds
Reload request received for member p2.hapat.vm and will be processed within 10 seconds
Reload request received for member p3.hapat.vm and will be processed within 10 seconds
Suite à cette modification, le cluster Patroni se retrouve sans
primaire. Dans les journaux applicatif de p3
, ici
précédemment marqué Leader, nous trouvons :
INFO: Reloading PostgreSQL configuration.
INFO: Lock owner: p3.hapat.vm; I am p3.hapat.vm
ERROR: Configuration requires watchdog, but watchdog could not be configured.
INFO: Demoting self (immediate)
INFO: Leader key released
INFO: Demoting self because watchdog could not be activated
INFO: Lock owner: None; I am p3.hapat.vm
INFO: not healthy enough for leader race
INFO: starting after demotion in progress
INFO: closed patroni connections to postgres
INFO: postmaster pid=561
INFO: establishing a new patroni heartbeat connection to postgres
WARNING: Watchdog device is not usable
INFO: Dropped unknown replication slot 'p1_hapat_vm'
INFO: Dropped unknown replication slot 'p2_hapat_vm'
INFO: following a different leader because i am not the healthiest node
postgres
sur le fichier /dev/watchdog
. Après
quelques secondes, que se passe-t-il ?Un simple chown
sur le device /dev/watchdog
de chaque VM peut suffire, au moins le temps de ce TP. Néanmoins, il ne
survivrait pas au redémarrage de la machine.
Le fichier de service de Patroni propose de systématiquement modifier les droits sur ce device décommentant la ligne suivante :
Une autre solution est de configurer udev
afin qu’il
modifie les droits sur ce fichier automatiquement à chaque
démarrage :
cat <<'EOF' > /etc/udev/rules.d/99-watchdog.rules
# give writes on watchdog device to postgres
SUBSYSTEM=="misc", KERNEL=="watchdog", ACTION=="add", RUN+="/bin/chown postgres /dev/watchdog"
# Or a better solution using ACL:
#SUBSYSTEM=="misc", KERNEL=="watchdog", ACTION=="add", RUN+="/bin/setfacl -m u:postgres:rw- /dev/watchdog"
EOF
Une fois les droits positionnés, l’un des nœuds Patroni devrait finalement réussir à activer le watchdog et ainsi devenir leader et promouvoir son instance PostgreSQL :
INFO: i6300ESB timer activated with 25 second timeout, timing slack 15 seconds
INFO: promoted self to leader by acquiring session lock
INFO: Lock owner: p2.hapat.vm; I am p2.hapat.vm
INFO: updated leader lock during promote
INFO: Lock owner: p2.hapat.vm; I am p2.hapat.vm
INFO: no action. I am (p2.hapat.vm), the leader with the lock
La connexion extérieure peut se faire depuis la machine hôte des VM.
Il est nécessaire d’y installer un client PostgreSQL, par exemple
postgresql-client
. Pour se connecter à l’instance
p1
, utiliser l’une des commandes suivantes :
$ psql -h p1 -p 5432 -U postgres -d postgres
$ psql -h 10.0.0.21 postgres postgres
En cas de problème, il faut regarder :
pg_hba.conf
.Il est nécessaire de se connecter à l’instance primaire pour réaliser
des écritures en base. La chaîne de connexion permettant d’indiquer
plusieurs nœuds, nous pouvons y préciser tous les nœuds du cluster. Afin
de sélectionner le nœud primaire, il suffit d’ajouter le paramètre
target_session_attrs=primary
ou
target_session_attrs=read-write
.
Par exemple :
psql "host=p1,p2,p3 user=postgres target_session_attrs=primary"
psql "postgresql://postgres@p1,p2,p3/postgres?target_session_attrs=read-write"
Pour une connexion en lecture seule, nous utilisons par exemple :
Créer une table :
Insérer une ligne toutes les secondes, à chaque fois dans une nouvelle connexion au primaire.
Dans une autre fenêtre, afficher les 20 dernières lignes de cette table.
Dans la colonne source
de la table créée, la valeur par
défaut fait appel à la fonction inet_server_addr()
qui
retourne l’adresse IP du serveur PostgreSQL sur lequel nous sommes
connectés.
Lancer une insertion toutes les secondes :
watch -n1 'psql -X -d "host=p1,p2,p3 user=postgres target_session_attrs=primary" \
-c "INSERT INTO insertions SELECT;"'
Lire les vingt dernières lignes de la table :
watch -n1 'psql -X -d "host=p1,p2,p3 port=5432 user=postgres" \
-c "SELECT * FROM insertions ORDER BY d DESC LIMIT 20"'
Bien entendu, nous obtenons toujours le même nœud dans la colonne
source
, ici p1
:
id | d | source
-----+-------------------------------+---------------
…
313 | 2024-03-05 15:40:03.118307+00 | 10.0.0.21/32
312 | 2024-03-05 15:40:02.105116+00 | 10.0.0.21/32
311 | 2024-03-05 15:40:01.090775+00 | 10.0.0.21/32
310 | 2024-03-05 15:40:00.075847+00 | 10.0.0.21/32
309 | 2024-03-05 15:39:59.061759+00 | 10.0.0.21/32
308 | 2024-03-05 15:39:58.048074+00 | 10.0.0.21/32
(20 lignes)
Sur p1 :
# systemctl stop patroni
Après l’arrêt de p1
, p3
prend le rôle de
leader :
$ patronictl -c /etc/patroni/patroni.yml topology
+ Cluster: acme (7350009258581743592) +---------+----+-----------+------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+---------------+-----------+---------+---------+----+-----------+------+
| p3.hapat.vm | 10.0.0.23 | Leader | running | 2 | | […] |
| + p1.hapat.vm | 10.0.0.21 | Replica | stopped | | unknown | […] |
| + p2.hapat.vm | 10.0.0.22 | Replica | running | 1 | 0 | […] |
+---------------+-----------+---------+---------+----+-----------+------+
Les insertions échouent le temps de la bascule, ici pendant environ 2 secondes, puis continuent depuis l’autre nœud :
id | d | source
-----+-------------------------------+---------------
…
445 | 2024-03-24 15:50:17.840394+00 | 10.0.0.23/32
444 | 2024-03-24 15:50:16.823004+00 | 10.0.0.23/32
431 | 2024-03-24 15:50:14.755045+00 | 10.0.0.21/32
430 | 2024-03-24 15:50:13.740541+00 | 10.0.0.21/32
Le leader Patroni a changé à nouveau :
$ patronictl -c /etc/patroni/patroni.yml topology
+ Cluster: acme (7350009258581743592) -------+----+-----------+------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+-------------+-----------+--------+---------+----+-----------+------+
| p2.hapat.vm | 10.0.0.22 | Leader | running | 6 | | […] |
+-------------+-----------+--------+---------+----+-----------+------+
Sur e1
et e2
:
Il n’y a plus de quorum etcd garantissant une référence. Le cluster Patroni se met en lecture seule et les insertions tombent en échec puisqu’elles exigent une connexion ouverte en écriture :
psql: error: connection to server at "p1" (10.0.0.21), port 5432 failed: Connection refused
Is the server running on that host and accepting TCP/IP connections?
connection to server at "p2" (10.0.0.22), port 5432 failed: server is in hot standby mode
connection to server at "p3" (10.0.0.23), port 5432 failed: Connection refused
Is the server running on that host and accepting TCP/IP connections?
Les écritures reprennent.
Dans le cadre de cette correction p2
est l’actuel
leader, nous redémarrons donc Patroni sur p1
:
L’instance sur p1
se raccroche en secondaire :
$ patronictl -c /etc/patroni/patroni.yml topology
+ Cluster: acme (7350009258581743592) +-----------+----+-----------+------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+---------------+-----------+---------+-----------+----+-----------+------+
| p2.hapat.vm | 10.0.0.22 | Leader | running | 7 | | […] |
| + p1.hapat.vm | 10.0.0.21 | Replica | streaming | 7 | 0 | […] |
+---------------+-----------+---------+-----------+----+-----------+------+
PGDATA
. Relancer Patroni.Le PGDATA de nos instances se trouve dans
/var/lib/pgsql/16/data
. Supprimons ce PGDATA
sur p3
, le nœud restant :
Relançons Patroni sur ce nœud :
Nous observons dans les journaux de Patroni et PostgreSQL que
l’instance est recrée et se raccroche à p2
:
LOG: starting PostgreSQL 16.2 on x86_64-pc-linux-gnu, […]
LOG: listening on IPv4 address "10.0.0.23", port 5432
LOG: listening on Unix socket "/run/postgresql/.s.PGSQL.5432"
LOG: listening on Unix socket "/tmp/.s.PGSQL.5432"
LOG: database system was interrupted while in recovery at log time […]
HINT: If this has occurred more than once some data might be corrupted and you might need to choose an earlier recovery target.
LOG: entering standby mode
LOG: starting backup recovery with redo LSN 0/45405620, checkpoint LSN 0/4FD4D788, on timeline ID 7
LOG: redo starts at 0/45405620
LOG: completed backup recovery with redo LSN 0/45405620 and end LSN 0/509EA568
LOG: consistent recovery state reached at 0/509EA568
LOG: database system is ready to accept read-only connections
LOG: started streaming WAL from primary at 0/50000000 on timeline 7
Le temps de la reconstruction de zéro, il est possible de voir
l’évolution de l’état de p3
de
creating replica
à streaming
:
$ export PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml
$ patronictl topology
+ Cluster: acme (7350009258581743592) +------------------+----+-----------+------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+---------------+-----------+---------+------------------+----+-----------+------+
| p2.hapat.vm | 10.0.0.22 | Leader | running | 7 | | […] |
| + p1.hapat.vm | 10.0.0.21 | Replica | streaming | 7 | 0 | […] |
| + p3.hapat.vm | 10.0.0.23 | Replica | creating replica | | unknown | […] |
+---------------+-----------+---------+------------------+----+-----------+------+
/* Après un certain temps */
$ patronictl topology
+ Cluster: acme (7350009258581743592) +-----------+----+-----------+------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+---------------+-----------+---------+-----------+----+-----------+------+
| p2.hapat.vm | 10.0.0.22 | Leader | running | 7 | | […] |
| + p1.hapat.vm | 10.0.0.21 | Replica | streaming | 7 | 0 | […] |
| + p3.hapat.vm | 10.0.0.23 | Replica | streaming | 7 | 0 | […] |
+---------------+-----------+---------+-----------+----+-----------+------+
p1
.$ patronictl failover
Current cluster topology
+ Cluster: acme (7350009258581743592) ----------+----+-----------+------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+-------------+-----------+---------+-----------+----+-----------+------+
| p1.hapat.vm | 10.0.0.21 | Replica | streaming | 7 | 0 | […] |
+-------------+-----------+---------+-----------+----+-----------+------+
| p2.hapat.vm | 10.0.0.22 | Leader | running | 7 | | […] |
+-------------+-----------+---------+-----------+----+-----------+------+
| p3.hapat.vm | 10.0.0.23 | Replica | streaming | 7 | 0 | […] |
+-------------+-----------+---------+-----------+----+-----------+------+
Candidate ['p1', 'p3'] []: p1
Are you sure you want to failover cluster acme, demoting current leader p2? [y/N]: y
[…] Successfully failed over to "p1"
+ Cluster: acme (7350009258581743592) --------+----+-----------+------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+-------------+-----------+---------+---------+----+-----------+------+
| p1.hapat.vm | 10.0.0.21 | Leader | running | 7 | | […] |
+-------------+-----------+---------+---------+----+-----------+------+
| p2.hapat.vm | 10.0.0.22 | Replica | stopped | | unknown | […] |
+-------------+-----------+---------+---------+----+-----------+------+
| p3.hapat.vm | 10.0.0.23 | Replica | running | 7 | 0 | […] |
+-------------+-----------+---------+---------+----+-----------+------+
$ patronictl topology
+ Cluster: acme (7350009258581743592) +-----------+----+-----------+------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+---------------+-----------+---------+-----------+----+-----------+------+
| p1.hapat.vm | 10.0.0.21 | Leader | running | 8 | | […] |
| + p2.hapat.vm | 10.0.0.22 | Replica | streaming | 8 | 0 | […] |
| + p3.hapat.vm | 10.0.0.23 | Replica | streaming | 8 | 0 | […] |
+---------------+-----------+---------+-----------+----+-----------+------+
shared_buffers
et work_mem
. Si besoin, redémarrer les nœuds.Pour modifier la configuration, nous devons utiliser
patronictl
, plutôt qu’éditer directement les fichiers de
configuration. Une alternative est de modifier la configuration
statique, dans le fichier YAML /etc/patroni/patroni.yml
.
Cette méthode facilite leur maintenance, mais impose que le contenu soit
identique sur tous les nœuds, ce qui est généralement le cas dans un
déploiement industrialisé.
La commande patronictl edit-config
appelle l’éditeur par
défaut, souvent vi
, vim
ou nano
.
Vous pouvez modifier la variable d’environnement EDITOR
pour pointer sur votre éditeur favori.
Éditons la configuration dynamique et ajoutons les deux paramètres :
---
+++
@@ -12,6 +12,8 @@
wal_keep_size: 128MB
wal_level: replica
wal_log_hints: 'on'
+ shared_buffers: 300MB
+ work_mem: 50MB
use_pg_rewind: false
use_slots: true
retry_timeout: 10
Apply these changes? [y/N]: y
Configuration changed
Les nœuds sont à redémarrer à cause de la modification de
shared_buffers
:
$ patronictl topology
+ Cluster: acme (7350009258581743592) +-----------+----+------+-----------------+-
| Member | Host | Role | State | TL | Lag… | Pending restart |
+---------------+-----------+---------+-----------+----+------+-----------------+-
| p1.hapat.vm | 10.0.0.21 | Leader | running | 1 | | * |
| + p2.hapat.vm | 10.0.0.22 | Replica | streaming | 1 | 0 | * |
| + p3.hapat.vm | 10.0.0.23 | Replica | streaming | 1 | 0 | * |
+---------------+-----------+---------+-----------+----+------+-----------------+-
Commandons le redémarrage de PostgreSQL sur les trois nœuds :
$ patronictl restart acme
+ Cluster: acme (7350009258581743592) ----------+----+------+-----------------+-
| Member | Host | Role | State | TL | Lag… | Pending restart |
+-------------+-----------+---------+-----------+----+------+-----------------+-
| p1.hapat.vm | 10.0.0.21 | Leader | running | 1 | | * |
+-------------+-----------+---------+-----------+----+------+-----------------+-
| p2.hapat.vm | 10.0.0.22 | Replica | streaming | 1 | 0 | * |
+-------------+-----------+---------+-----------+----+------+-----------------+-
| p3.hapat.vm | 10.0.0.23 | Replica | streaming | 1 | 0 | * |
+-------------+-----------+---------+-----------+----+------+-----------------+-
When should the restart take place (e.g. […]) [now]:
Are you sure you want to restart members p1.hapat.vm, p2.hapat.vm, p3.hapat.vm? [y/N]: y
Restart if the PostgreSQL version is less than provided (e.g. 9.5.2) []:
Success: restart on member p1.hapat.vm
Success: restart on member p2.hapat.vm
Success: restart on member p3.hapat.vm
Vérifions que le paramétrage a bien été modifié :
$ for h in p1 p2 p3; do echo -ne $h:; psql -Xtd "host=$h user=postgres" -c "show shared_buffers"; done
p1: 300MB
p2: 300MB
p3: 300MB
Noter que le contenu des modifications est tracé dans un fichier
patroni.dynamic.json
dans le PGDATA
:
$ jq . patroni.dynamic.json
{
"loop_wait": 10,
"postgresql": {
"parameters": {
"hot_standby": "on",
"max_connections": 100,
"max_locks_per_transaction": 64,
"max_prepared_transactions": 0,
"max_replication_slots": 10,
"max_wal_senders": 10,
"max_worker_processes": 8,
"track_commit_timestamp": "off",
"wal_keep_size": "128MB",
"wal_level": "replica",
"wal_log_hints": "on",
"shared_buffers": "300MB",
"work_mem": "50MB"
},
"use_pg_rewind": false,
"use_slots": true
},
"retry_timeout": 10,
"ttl": 30
}