r58 Handout
r58 Handout
DALIBO
L'expertise PostgreSQL
25.03
Table des matières
Sur ce document . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Chers lectrices & lecteurs, . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
À propos de DALIBO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Remerciements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Forme de ce manuel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Licence Creative Commons CC‑BY‑NC‑SA . . . . . . . . . . . . . . . . . . . . . . . . . 2
Marques déposées . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Versions de PostgreSQL couvertes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
0.1 Au menu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
0.2 Architecture générale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
0.3 Définitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
0.3.1 Mécanismes mis en œuvre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
0.3.2 Bascule automatique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
0.3.3 Définition : split‑brain . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
0.3.4 Leader lock de Patroni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
0.3.5 Heartbeat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
0.3.6 Bootstrap de nœud . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
0.3.7 Répartition sur deux sites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
0.3.8 Répartition sur trois sites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
0.4 Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
0.4.1 Sur Entreprise Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
0.4.2 Sur Debian et dérivés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
0.4.3 Installation manuelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
0.5 Configurations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
0.5.1 Paramètres globaux du cluster . . . . . . . . . . . . . . . . . . . . . . . . . . 19
0.5.2 Configuration du DCS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
0.5.3 Configuration de Patroni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
0.5.4 Création des instances . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
0.5.5 Configuration de PostgreSQL . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
0.5.6 Agrégat de secours . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
0.5.7 Slots de réplication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
0.5.8 Réplication synchrone et asynchrone . . . . . . . . . . . . . . . . . . . . . . . 35
0.5.9 Journaux applicatifs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
0.5.10 API REST de Patroni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
0.5.11 API REST pour le CLI patronictl . . . . . . . . . . . . . . . . . . . . . . . . . . 41
0.5.12 Configuration du watchdog . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
0.5.13 Marqueurs d’instance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
0.5.14 Agrégat multinœud Citus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
0.5.15 Variables d’environnement . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
0.6 CLI patronictl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
0.6.1 Consultation d’état . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
iii
DALIBO Formations
Sur ce document
Nos formations PostgreSQL sont issues de nombreuses années d’études, d’expérience de terrain et
de passion pour les logiciels libres. Pour Dalibo, l’utilisation de PostgreSQL n’est pas une marque
d’opportunisme commercial, mais l’expression d’un engagement de longue date. Le choix de l’Open
Source est aussi le choix de l’implication dans la communauté du logiciel.
Au‑delà du contenu technique en lui‑même, notre intention est de transmettre les valeurs qui animent
et unissent les développeurs de PostgreSQL depuis toujours : partage, ouverture, transparence, créati‑
vité, dynamisme… Le but premier de nos formations est de vous aider à mieux exploiter toute la puis‑
sance de PostgreSQL mais nous espérons également qu’elles vous inciteront à devenir un membre
actif de la communauté en partageant à votre tour le savoir‑faire que vous aurez acquis avec nous.
Nous mettons un point d’honneur à maintenir nos manuels à jour, avec des informations précises et
des exemples détaillés. Toutefois malgré nos efforts et nos multiples relectures, il est probable que ce
document contienne des oublis, des coquilles, des imprécisions ou des erreurs. Si vous constatez un
souci, n’hésitez pas à le signaler via l’adresse [email protected] !
À propos de DALIBO
Remerciements
Ce manuel de formation est une aventure collective qui se transmet au sein de notre société depuis
des années. Nous remercions chaleureusement ici toutes les personnes qui ont contribué directement
ou indirectement à cet ouvrage, notamment :
Alexandre Anriot, Jean‑Paul Argudo, Carole Arnaud, Alexandre Baron, David Bidoc, Sharon Bonan,
Franck Boudehen, Arnaud Bruniquel, Pierrick Chovelon, Damien Clochard, Christophe Courtois, Marc
Cousin, Gilles Darold, Ronan Dunklau, Vik Fearing, Stefan Fercot, Dimitri Fontaine, Pierre Giraud, Nico‑
las Gollet, Florent Jardin, Virginie Jourdan, Luc Lamarle, Denis Laxalde, Guillaume Lelarge, Alain Le‑
sage, Benoit Lobréau, Jean‑Louis Louër, Thibaut Madelaine, Adrien Nayrat, Alexandre Pereira, Flavie
Perette, Robin Portigliatti, Thomas Reiss, Maël Rimbault, Jehan‑Guillaume de Rorthais, Julien Rou‑
haud, Stéphane Schildknecht, Julien Tachoires, Nicolas Thauvin, Be Hai Tran, Christophe Truffier, Ar‑
naud de Vathaire, Cédric Villemain, Thibaud Walkowiak, Frédéric Yhuel.
Forme de ce manuel
Les versions PDF, EPUB ou HTML de ce document sont structurées autour des slides de nos formations.
Le texte suivant chaque slide contient le cours et de nombreux détails qui ne peuvent être données à
l’oral.
Cette formation est sous licence CC‑BY‑NC‑SA2 . Vous êtes libre de la redistribuer et/ou modifier aux
conditions suivantes :
– Paternité
– Pas d’utilisation commerciale
– Partage des conditions initiales à l’identique
Vous n’avez pas le droit d’utiliser cette création à des fins commerciales.
Si vous modifiez, transformez ou adaptez cette création, vous n’avez le droit de distribuer la création
qui en résulte que sous un contrat identique à celui‑ci.
Vous devez citer le nom de l’auteur original de la manière indiquée par l’auteur de l’œuvre ou le ti‑
tulaire des droits qui vous confère cette autorisation (mais pas d’une manière qui suggérerait qu’ils
vous soutiennent ou approuvent votre utilisation de l’œuvre). À chaque réutilisation ou distribution
de cette création, vous devez faire apparaître clairement au public les conditions contractuelles de
sa mise à disposition. La meilleure manière de les indiquer est un lien vers cette page web. Chacune
de ces conditions peut être levée si vous obtenez l’autorisation du titulaire des droits sur cette œuvre.
Rien dans ce contrat ne diminue ou ne restreint le droit moral de l’auteur ou des auteurs.
Le texte complet de la licence est disponible sur http://creativecommons.org/licenses/by‑nc‑sa/2.0
/fr/legalcode
2
http://creativecommons.org/licenses/by‑nc‑sa/2.0/fr/legalcode
Cela inclut les diapositives, les manuels eux‑mêmes et les travaux pratiques. Cette formation peut
également contenir quelques images et schémas dont la redistribution est soumise à des licences
différentes qui sont alors précisées.
Marques déposées
PostgreSQL® Postgres® et le logo Slonik sont des marques déposées3 par PostgreSQL Community As‑
sociation of Canada.
Ce document ne couvre que les versions supportées de PostgreSQL au moment de sa rédaction, soit
les versions 13 à 17.
Sur les versions précédentes susceptibles d’être encore rencontrées en production, seuls quelques
points très importants sont évoqués, en plus éventuellement de quelques éléments historiques.
Sauf précision contraire, le système d’exploitation utilisé est Linux.
Patroni : Mise en œuvre
Figure 1: PostgreSQL
3
https://www.postgresql.org/about/policies/trademarks/
0.1 AU MENU
ʦ – Architecture générale
– Patroni
– Proxy, VIP et poolers de connexions
Sur les bases de la réplication physique de PostgreSQL, Patroni fournit un modèle de gestion
d’instances, avec bascule automatique et configuration distribuée.
Les tâches nécessaires pour la bascule ou la promotion, l’ajout d’un nouveau nœud, la resynchro‑
nisation suite à une défaillance, deviennent la responsabilité de Patroni qui veille à ce que ces ac‑
tions soient effectuées de manière fiable, en évitant toujours la multiplicité de nœuds primaires (split‑
brain).
Dans ce module, nous décrivons l’architecture de cette solution de haute disponibilité de service et
sa mise en œuvre.
Patroni
L’application des modifications de la configuration de PostgreSQL est effectuée par Patroni qui se
charge de la répercuter sur tous les nœuds.
Le démarrage et l’arrêt du service PostgreSQL sur chaque nœud ne doivent plus être gé‑
Á rés par le système et doivent être désactivés. Toutes les actions de maintenances (arrêt,
démarrage, rechargement de configuration, promotion) doivent être faites en utilisant
Patroni plutôt que les moyens traditionnels ( pg_ctl , systemctl , etc).
DCS
Patroni s’appuie sur un gestionnaire de configuration distribuée (DCS) pour partager l’état des nœuds
de son agrégat et leur configuration. Dans ce module, nous utilisons etcd comme DCS.
Notre but étant la haute disponibilité, etcd ne doit pas devenir un SPOF (single point of failure). Il doit
donc lui aussi être déployé en agrégat afin d’assurer une tolérance aux pannes et une disponibilité
maximale du service. Un agrégat etcd utilise le protocole RAFT pour assurer la réplication et cohérence
des données entre ses nœuds.
Horloges système
La synchronisation des horloges de tous les serveurs Patroni et etcd est primordiale. Elle doit idéale‑
ment être assurée par le protocole NTP4 .
Les ralentissements causés par les snapshot de sauvegardes des machines virtuelles illustrent ce type
de problème. Une indisponibilité (freeze) trop grande peut entraîner un décalage d’horloge trop im‑
portant, une bascule automatique et une désynchronisation du nœud retardataire. Celui‑ci est alors
dans l’impossibilité de se raccrocher au nouveau primaire et sa reconstruction est inévitable.
L’anomalie se signale par exemple dans les traces d’etcd par le message :
etcd: the clock difference […] is too high
4
Network Time Protocole
0.3 DÉFINITIONS
Patroni permet de :
ʦ – créer / mettre en réplication
– maintenir
– superviser
Patroni est un script écrit en Python. Il permet de maintenir un agrégat d’instances PostgreSQL en
condition opérationnelle et de le superviser afin de provoquer une bascule automatique en cas
d’incident sur le primaire.
Liste des actions disponibles sur le cluster et commandes associées dans patronictl :
Il s’agit donc d’un outil permettant de garantir la haute disponibilité du service de bases de don‑
nées.
Patroni est un script Python qui s’appuie sur la capacité de l’écosystème PostgreSQL à répliquer les
modifications et les rejouer sur un stockage clé valeur distribué pour garantir la haute disponibilité
de PostgreSQL.
Outils :
La réplication physique fournie avec PostgreSQL assure la haute disponibilité des données. Des ou‑
tils fournis avec PostgreSQL comme pg_rewind5 et pg_basebackup6 sont utilisés pour construire ou
reconstruire les instances secondaires. Nous les décrivons dans le module de formation I47 .
Cette capacité est étendue grâce à la possibilité d’utiliser des outils de la communauté comme bar‑
man8 , pgBackRest9 ou WAL‑G10 .
DCS (etcd) :
Patroni s’appuie sur un gestionnaire de configuration distribué (DCS) pour partager l’état des nœuds
de son agrégat et leur configuration commune.
Nous ne mentionnerons dans ce document que etcd, mais il est cependant possible d’utiliser un autre
DCS tel que Consul, ZooKeeper ou même Kubernetes. Tous peuvent être déployés en haute disponi‑
bilité de service.
5
https://www.postgresql.org/docs/current/app‑pgrewind.html
6
https://www.postgresql.org/docs/current/app‑pgbasebackup.html
7
https://dali.bo/i4_html
8
https://www.pgbarman.org/
9
https://pgbackrest.org/
10
https://github.com/wal‑g/wal‑g
ʦ – split‑brain
– leader lock
– heart beat
– Promotion automatique
L’automatisation de la prise de décision de bascule est protégée par un mécanisme de verrou partagé
appelé leader lock. Ce verrou est attribué à une seule instance secondaire suite à une élection basée
sur sa disponibilité et son LSN courant (Log Sequence Number, ou position dans le flux des journaux
de transaction).
La présence de deux primaires dans un agrégat d’instance nous amène à une situation que nous vou‑
lons éviter à tout prix : le split‑brain.
La situation d’un split‑brain est obtenue lorsque l’élection automatique d’un primaire est possible sur
deux nœuds différents, au même moment.
Les données insérées sur les deux nœuds doivent faire l’objet d’un arbitrage pour départager les‑
quelles seront gardées lors du rétablissement d’une situation normale (un seul primaire).
La perte de données est plus probable lors de cet arbitrage, suivant la quantité et la méthode
d’arbitrage.
Le service peut souffrir d’une indisponibilité s’il y a nécessité de restauration partielle ou totale des
données.
L’attribution unique d’un verrou appelé leader lock par Patroni permet de se prémunir d’un split‑brain.
Ce verrou est distribué et stocké dans le DCS.
Une fois ce verrou obtenu, le futur primaire dialogue alors avec les autres nœuds Patroni référencés
dans le DCS, et valide sa promotion en comparant leur type de réplication (synchrone ou asynchrone)
et leur LSN courant.
La promotion provoque la création d’une nouvelle timeline sur l’instance primaire et la chaîne de
connexion ( primary_conninfo ) utilisée par les instances secondaires pour se connecter au primaire
est mise à jour.
Les secondaires se raccrochent ensuite à la timeline du primaire.
0.3.5 Heartbeat
Chaque nœud est en communication régulière avec l’agrégat etcd afin d’informer le système de sa
bonne santé. Le primaire confirme son statut de leader et les secondaires celui de follower.
Lorsque la confirmation du leader ne vient pas, un timeout est atteint sur les followers Patroni, déclen‑
chant alors une procédure de bascule.
L’opération de bootstrap consiste à créer ou recréer l’instance PostgreSQL d’un nœud à partir du nœud
primaire, d’une sauvegarde ou avec initdb (premier nœud). Par défaut, Patroni créé la première
instance avec initdb et lance un pg_basebackup pour créer les réplicas.
Cependant, cela n’est pas toujours souhaitable, notamment sur des gros volumes. Il est alors possible
de paramétrer Patroni pour utiliser un autre outil de restauration de sauvegarde PITR, tel que pgBa‑
ckrest.
Pour reconstruire un nœud ayant pris trop de retard, pgBackrest permet de raccrocher rapidement
une instance ayant un volume de données important grâce à la restauration en mode delta.
Le but de configurer un deuxième site est de disposer d’une tolérance de panne à l’échelle d’un site.
En cas d’incident majeur, le deuxième site est censé prendre la relève.
Cependant, dans le cas d’un agrégat étendu sur deux sites, il devient impossible de différencier la
perte totale d’une salle distante d’une coupure réseau locale. Il est tentant de « favoriser » une salle en
y positionnant une majorité de nœuds de l’agrégat, mais cette approche réduit au final la disponibilité
de service. Détaillons :
– si perte du site A : etcd sur le site B n’a pas le quorum, pas de bascule
– si perte du site B : etcd maintient le quorum, aucun impact sur le primaire
Dans le cas d’un agrégat multisite, la réponse à cette problématique est d’utiliser au minimum trois
sites avec chacun un serveur etcd.
En cas d’isolation réseau d’un des sites, les instances etcd qui s’y trouvent ne peuvent plus y former
de quorum, ce qui empêche la plupart des actions dans etcd. Les instances PostgreSQL isolées sur ce
site ne peuvent donc y être disponible qu’en read only (standby).
Les deux serveurs etcd restants (un par site) continuent à communiquer entre eux et conservent ainsi
le quorum, ce qui leur permet de fonctionner normalement. Le leader du cluster Patroni est donc
fonctionnel, l’instance PostgreSQL qu’il manage peut être démarrée ou maintenue sur l’un de ces deux
sites en tant que primaire sans risque de split‑brain.
Standby cluster
Il est possible de créer un second cluster indépendant sur un second site qui réplique les données de‑
puis le cluster principal. On a alors deux clusters etcd distincts, ce qui nous affranchit des problèmes
décrits précédemment. Chaque site héberge un cluster Patroni indépendant, l’un d’eux étant entière‑
ment en mode standby, aussi appelé standby cluster. La réplication entre site peut être mise en place
via la configuration de Patroni. En cas de perte du premier site, une promotion manuelle doit être
effectuée sur le second site.
ʦ – Placer 1 etcd sur chacun des 3 sites autorise une tolérance de panne d’un site
– Changement de site lors d’une bascule
– Perte de deux sites
Quorum de sites
La présence de trois sites permet de disposer de suffisamment de nœuds pour maintenir le fonction‑
nement de etcd. Ce qui permet à Patroni de fonctionner et déclencher des bascules.
En effet, on a vu que pour départager deux sites en concurrence, il est nécessaire de disposer d’un
arbitre externe et donc d’un troisième site.
Changement de site lors des bascules
Lors de la bascule automatique, Patroni décide de privilégier les nœuds les plus à jour avec le primaire,
puis les plus réactifs. Il n’y a donc aucune garantie que dans certaines conditions particulières, un
nœud d’un autre site soit promu plutôt qu’un nœud géographiquement local.
Le tag failover_priority ne permet pas de régler ce problème, car il permet de privilégier un nœud
par rapport aux autres nœuds qui ont un lag équivalent. De plus, rien ne permet de mettre à jour
cette liste dynamiquement en fonction du datacenter où se trouve les instances.
Perte de deux sites sur trois
S’il ne reste plus qu’un site disponible, promouvoir un de ses nœuds secondaires ne pourra être fait
qu’après plusieurs opérations manuelles visant à reconstruire un cluster avec les nœuds restants.
Si la tolérance de panne du DCS est dépassée, il est toujours possible de désactiver Patroni et promou‑
voir manuellement une instance sur le site restant, et si besoin d’y raccrocher les instances standbys
restantes afin de maintenir le service.
Troisième site en standby
Une autre possibilité consiste à garder le troisième site en dehors du mécanisme de bascule automa‑
tique. Il est prévu de ne le solliciter que manuellement, après avoir constaté la perte totale des deux
premiers sites.
0.4 INSTALLATION
Patroni est empaqueté pour les principales distributions Linux existantes, qu’elles soient dérivées
d’Entreprise Linux (Red Hat, Rocky Linux…) ou de Debian. L’installation ne dure que quelques mi‑
nutes.
Bien entendu, Patroni a besoin des binaires de PostgreSQL afin d’administrer une instance locale.
Utilisez votre méthode favorite ou consultez les méthodes recommandées dans notre module sur
l’installation11 .
Les paquets Patroni sont disponibles pour les distributions Entreprise Linux depuis les dépôts com‑
munautaires PGDG. Ceux‑ci utilisent par ailleurs des dépendances provenant du dépôt EPEL.
Il faut donc au préalable installer les paquets suivants sur tous les nœuds :
– epel-release
– sur EL 7 : https://download.postgresql.org/pub/repos/yum/reporpms/EL‑7‑x86_64/pgdg‑
redhat‑repo‑latest.noarch.rpm
– sur EL 8 : https://download.postgresql.org/pub/repos/yum/reporpms/EL‑8‑x86_64/pgdg‑
redhat‑repo‑latest.noarch.rpm
– sur EL 9 : https://download.postgresql.org/pub/repos/yum/reporpms/EL‑9‑x86_64/pgdg‑
redhat‑repo‑latest.noarch.rpm
11
https://dali.bo/b_html#installation‑de‑postgresql‑depuis‑les‑paquets‑communautaires
Notez que ce paquet installe les paquets patroni et python3-etcd par dépendance, ce dernier
étant la librairie cliente d’etcd en python. Il n’installe pas la partie serveur d’etcd, cette‑ci devant idéa‑
lement être située sur des nœuds distincts.
Pour plus d’information à propos de l’installation d’un cluster etcd, voir le module de formation etcd :
Architecture et fonctionnement12 .
Debian et ses dérivés incluent directement Patroni et etcd dans ses dépôts officiels. Néanmoins, étant
donné la politique de gestion des versions des paquets Debian, les versions de Patroni peuvent rapi‑
dement être désuètes.
De nouvelles versions de Patroni sont régulièrement publiées, incluant des corrections, améliorations
et nouveautés. C’est pourquoi nous vous recommandons d’utiliser autant que faire se peut une ver‑
sion récente.
Pour se faire, nous proposons d’installer les dépôts PGDG avant d’installer Patroni sur les nœuds Post‑
greSQL :
# apt install postgresql-common gnupg
# /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y
# apt install -y patroni
Pour les autres distributions Linux ou si certaines contraintes vous empêchent d’utiliser les méthodes
précédentes, il est possible d’installer Patroni grâce au gestionnaire de paquets Python pip .
12
https://dali.bo/r57_html
Le nom du paquet Python à installer est patroni . Cependant, si vous utilisez cette méthode ma‑
nuelle, il vous sera probablement aussi nécessaire de spécifier la dépendance sur le DCS à utiliser, ici
etcd, avec le format patroni[etcd] . Par exemple :
Vous trouverez la liste des dépendances disponibles dans la documentation de Patroni : https://patr
oni.readthedocs.io/en/latest/installation.html#general‑installation‑for‑pip
0.5 CONFIGURATIONS
ʦ – Configuration statique
– stockée dans le fichier de configuration YAML de chaque nœud
– recharge par patronictl reload
– Configuration dynamique
– stockée dans le DCS
– initialisée depuis la section bootstrap.dcs du fichier de configuration
YAML
– modifiable ensuite par patronictl edit-config
– prise en compte immédiatement si possible
– copiée dans $PGDATA/patroni.dynamic.json à intervalle régulier
– Variables d’environnement
Cette commande doit pouvoir accéder aux binaires de PostgreSQL. Il peut donc être
nécessaire de positionner la variable d’environnement PATH en fonction de votre ins‑
tallation.
13
https://patroni.readthedocs.io/en/latest/dynamic_configuration.html
Toutes les informations présentes dans la section bootstrap sont uniquement utilisées lors de la
création du cluster. Cela inclut la configuration dynamique qui est dans la section bootstrap.dcs ,
mais aussi la configuration de PostgreSQL effectuée dans les sections bootstrap.initdb ,
bootstrap.method , bootstrap.pg_hba , bootstrap.users .
Les modifications des autres sections de cette configuration doivent être reportées dans le fichier de
configuration de tous les nœuds si l’on souhaite qu’elles soient prises en compte globalement. Cela
peut être fait avec des outils d’industrialisation comme Ansible, Chef,…
Le fichier de configuration peut être testé avec l’exécutable de Patroni :
patroni --validate-config /etc/patroni/config.yml
Le second niveau de configuration est la configuration dynamique. Elle est initialisée grâce aux
données de la section bootstrap.dcs du fichier de configuration YAML. Elle est chargée dans le
DCS et n’est plus maintenue qu’à cet endroit. Les modifications doivent être faites avec la commande
patronictl edit-config . Patroni copie cette configuration à intervalle régulier dans le fichier
patroni.dynamic.json placé dans le répertoire de données de l’instance.
Pour finir, certains éléments de configuration peuvent être spécifiés via des variables d’environnement.
Les variables d’environnement ont toujours précédence sur les fichiers de configuration.
L’application de la configuration locale ou dynamique de PostgreSQL suit l’algorithme suivant :
– ALORS le fichier configuré dans custom_conf sera utilisé comme référence de configura‑
tion en lieu et place de postgresql.base.conf et postgresql.conf
– SINON SI le fichier postgresql.base.conf existe
– ALORS un flag pending_restart est donné au nœud et sera retiré lors du prochain redé‑
marrage.
namespace : Le chemin dans lequel est stockée la configuration de Patroni dans le DCS, par
défaut: /service .
scope : Le nom de l’agrégat, utilisé dans les commandes patronictl et pour stocker la configura‑
tion de l’agrégat dans le DCS. Ce paramètre est requis et doit être identique sur tous les nœuds.
name: p1
scope: acme
[…]
Et voici un exemple de l’arborescence créée dans le DCS pour la configuration ci‑dessus et avec deux
serveurs p1 et p2 :
– host
– protocol
– username , password
– cacert , cert , key
Patroni supporte six types de DCS différents : etcd, Consul, Zookeeper, Exhibitor, Kubernetes.
Dans cette formation, nous n’abordons que l’utilisation d’etcd, qui dispose de deux sections en fonc‑
tion de la version du protocole d’etcd utilisée :
L’API v3 est configurée par défaut dans etcd depuis qu’il est en version 3.4. Son implémentation dans
Patroni est considérée comme stable pour la production depuis sa version 2.1.5. Il est recommandé
de l’utiliser lorsque les versions utilisées le permettent car l’API v2 est progressivement dépréciée :
host et hosts : Permettent de définir au choix un ou une liste de nœud etcd au format host:port .
key : Clé du client, peut être ignorée si elle fait partie du certificat.
[…]
etcd3:
hosts:
- 10.0.0.11:2379
- 10.0.0.12:2379
- 10.0.0.13:2379
username: patroniaccess
password: secret
[…]
La configuration du démon Patroni est représentée à la racine de la section bootstrap.dcs puis dans
YAML dynamique. Elle comprend les paramètres suivants :
loop_wait : Temps de pause maximal entre chaque boucle de vérification, par défaut 10s , au mi‑
nimum 3s . Cela correspond au temps nominal de la boucle de vérification. Un problème au
niveau du DCS ou du réseau supérieur ou égal à cette valeur provoque un demote du leader.
ttl : Temps avant l’initialisation d’un failover, par défaut 30s , au minimum 20s . Cela correspond
au temps maximal entre deux mises à jour de la leader key.
retry_timeout : Temps avant de retenter une action échouée sur PostgreSQL ou le DCS, par défaut
10s , au minimum 3s .
maximum_lag_on_failover : Limite supérieure du délai (lag), en octets, pour qu’un nœud follower
puisse participer à une élection.
Si cette règle n’est pas respectée, Patroni ajuste le paramétrage en préférant réduire loop_wait , à
moins qu’il soit déjà à sa valeur minimale, dans ce cas il diminue retry_timeout :
– loop_wait = min_loop_wait = 1s
– retry_timeout = (ttl - min_loop_wait) // 2
Il est possible d’indiquer à Patroni comment construire les instances primaires et secon‑
ʦ daires d’un agrégat. Les sections concernées sont :
– bootstrap.method et bootstrap.initdb
– postgresql.create_replica_methods
– scripts bootstrap.post_bootstrap et bootstrap.post_init
bootstrap:
[…]
method: pgbackrest
pgbackrest:
command: /bin/bash -c "pgbackrest --stanza=test restore"
keep_existing_recovery_conf: True
no_params: True
[…]
14
https://patroni.readthedocs.io/en/latest/replica_bootstrap.html#custom‑bootstrap
Si la méthode initdb est choisie, une section spécifique doit être renseignée pour en modifier les
paramètres. Cela permet par exemple de spécifier si les sommes de contrôles doivent être activées
( data-checksums ) et de définir une locale et un encodage :
bootstrap:
[…]
# ici la méthode est facultative, initdb est la valeur par défaut
method: initdb
initdb:
- data-checksums
- encoding: UTF8
- locale: UTF8
[…]
Les instances secondaires doivent être construites comme des clones de la primaire, une
commande spécifique est donc dédié à leur création. Comme ces secondaires peuvent être
créés à n’importe quel moment de la vie du cluster, cette commande se situe dans la section
postgresql.create_replica_method , conservée dans le DCS.
Cette section peut contenir plusieurs méthodes qui seront testées dans l’ordre d’apparition. Patroni
permet l’utilisation de pg_basebackup (par défaut), pgBackRest, WAL‑G, barman ou d’un script utili‑
sateur pour réaliser cette opération.
Il est possible de fournir des paramètres à l’outil que l’on souhaite utiliser. Tous les paramètres confi‑
gurés seront communiqués sous la forme --<nom>=<valeur> ou --<nom> . Trois paramètres sont
cependant réservés et ne seront pas passés aux scripts :
no_leader : Permet d’utiliser une méthode même si aucune instance n’est démarrée dans l’agrégat.
Désactivé par défaut.
no_param : Permet de ne pas utiliser les paramètres supplémentaires décrits ci‑après. Désactivé par
défaut.
keep_data : Indique à Patroni de ne pas vider le répertoire de données de l’instance avant la réini‑
tialisation. Désactivé par défaut.
Les paramètres suivant seront également fourni au script si l’option no_params reste a False :
connstring : Chaîne de connexion vers le nœud depuis lequel la copie va être réalisée.
- basebackup
pgbackrest:
command: /usr/bin/pgbackrest --stanza=patroni_demo --delta restore
keep_data: True
no_leader: True
no_params: True
basebackup:
- verbose
- max-rate: '100M'
- waldir: /pg_wal/14/pg_wal
[…]
bootstrap:
[…]
users:
admin:
password: password_admin
options:
- createrole
- createdb
[…]
Enfin, des scripts peuvent être déclenchés après l’initialisation de l’instance avec les sections
bootstrap.post_bootstrap ou bootstrap.post_init . Le script reçoit en paramètre une URL de
connexion avec comme utilisateur le super utilisateur de l’instance et est appelé avec la variable
d’environnement PGPASSFILE positionnée.
La configuration de l’agrégat PostgreSQL à déployer et/ou maintenir peut être chargée dans le DCS au
moment de l’initialisation depuis la section bootstrap.dcs.postgresql . Elle est ensuite conservée
dans le YAML dynamique dans la section postgresql . Cette section permet d’avoir une configuration
commune pour toutes les instances. Les paramètres suivants sont disponibles à ce niveau :
connect_address : Adresse IP locale sur laquelle PostgreSQL est accessible pour les autres nœuds
et applications utilisés au sein du cluster, sous la forme [IP|nom d'hôte]:port .
pgpass : Chemin vers un fichier .pgpass . Il est préférable de laisser Patroni gérer ce fichier et de ne
pas ajouter nos propres entrées car elles pourraient être supprimées.
recovery_conf : Configuration supplémentaire à ajouter lors de la configuration d’un nœud follo‑
wer. Même si le fichier recovery.conf disparaît à partir de la version 12 de PostgreSQL, la
section porte toujours ce nom.
custom_conf : Chemin vers un fichier de configuration a utiliser à la place de postgresql.base.conf .
Le fichier doit exister et être accessible par Patroni et PostgreSQL. Patroni ne surveille pas ce
fichier pour détecter des modifications et ne le sauvegarde pas. Il est simplement inclus depuis
le fichier postgresql.conf .
pg_ctl_timeout Temps d’attente maximale pour les actions effectuées avec pg_ctl ( start ,
stop , restart ), par défaut à 60 .
use_slots Utilisation des slots de réplication, activé par défaut pour les versions de PostgreSQL
supérieures à la 9.4.
member_slots_ttl Durée de rétention des slots de réplication lorsqu’un nœud est arrêté et que sa
clé disparaît du DCS. Si cette durée est à zéro, l’ancien comportement est conservé : le slot est
supprimé lorsque la clé disparaît du DCS. La valeur par défaut est : 30min .
use_pg_rewind : Utilise pg_rewind pour reconstruire l’ancien primaire en tant que secondaire
après un failover, par défaut à false .
Pour chaque type d’utilisateur, les éléments de configuration suivants sont disponibles et
correspondent aux paramètres de connexion éponymes : username , password , sslmode ,
sslkey , sslpassword , sslcert , sslrootcert , sslcrl , sslcrldir , gssencmode et
channel_binding .
[…]
postgresql:
[…]
authentication:
superuser:
username: patronidba
password: secret
replication:
username: replicator
password: secretaussi
rewind:
username: rewinder
password: sectrettoujours
[…]
wal_level : Niveau de détail des informations écrites dans les WAL, défaut à replica .
wal_log_hints : Force l’écriture complète d’une page de données lors de sa première modification
après un checkpoint, même pour des modifications non critiques comme celles des hint bits.
Cela permet l’utilisation de pg_rewind . Activé par défaut.
track_commit_timestamp : Permet de tracer l’horodatage des commits dans les journaux de tran‑
sactions. Désactivé par défaut.
max_wal_senders : Nombre maximal de processus wal sender, par défaut à 5 .
wal_keep_segments : Nombre maximal de WAL conservés pour les instances secondaires afin de les
aider à récupérer leur retard, par défaut à 8 . Disponible jusqu’en PostgreSQL 12.
wal_keep_size : Quantité de WAL conservés pour les instances secondaires afin de les aider à récu‑
pérer leur retard, par défaut à 128MB . Disponible à partir de PostgreSQL 13.
listen_addresses : La ou les interfaces sur lesquelles PostgreSQL écoute. Ce paramètre est défini
via le paramètre postgresql.listen ou la variable d’environnement PATRONI_POSTGRESQL_LISTEN .
port : Le port sur lequel écoute l’instance, lui aussi défini par le paramètre postgresql.listen ou
la variable d’environnement PATRONI_POSTGRESQL_LISTEN .
cluster_name : Permet de définir le nom de l’instance qui sera affiché dans la description des pro‑
cessus (eg. par la commande ps ). Il est défini en fonction de la valeur du paramètre scope ou
de la variable d’environnement PATRONI_SCOPE .
hot_standby : Permet d’ouvrir les instances secondaires en lecture seule. Activé par défaut.
Afin que ces paramètres ne puissent pas être modifiés via les fichiers de configuration, ils sont passés
en paramètre de la commande de démarrage de PostgreSQL. Ce n’est le cas que pour la liste ci‑dessus.
Les autres paramètres présents dans la section postgresql.parameters sont appliqués de manière
classique en suivant la hiérarchie décrite précédemment.
Les sous‑sections postgresql.pg_hba et postgresql.pg_ident contiennent chacune une liste de
règles à ajouter aux fichiers respectifs, dans leurs formats respectifs. Voir l’exemple ci‑après.
Enfin, la sous‑section postgresql.callbacks permet d’exécuter des scripts lors des différents chan‑
gements d’état du cluster. Trois paramètres sont communiqués aux scripts : l’action, le rôle du nœud
et le nom du cluster.
Les actions suivantes sont disponibles :
wal_keep_segments: 8
max_wal_senders: 5
max_replication_slots: 5
checkpoint_timeout: 30
pg_hba:
- local all all peer
- host all all 0.0.0.0/0 scram-sha-256
- host replication replicator 10.0.0.21/32 scram-sha-256
- host replication replicator 10.0.0.22/32 scram-sha-256
- host replication replicator 10.0.0.23/32 scram-sha-256
pg_ident:
- superusermapping root postgres
- superusermapping dba postgres
Patroni permet de créer un agrégat de secours appelé standby cluster en utilisant la réplication en cas‑
cade de PostgreSQL. Pour ce faire, un second agrégat est créé et son leader, appelé standby leader, se
connecte à un serveur de l’agrégat principal via le protocole de réplication. Les followers de ce second
agrégat se connectent, eux, au standby leader, pour répliquer les modifications.
Les clusters Patroni sur figure 1 utilisent le même agrégat etcd par simple commodité. Il n’y a aucune
contrainte à ce propos, même si dans le cas de l’architecture présentée cela est tout indiqué.
La section bootstrap.dcs.standby_cluster est utilisée lorsque l’on crée un agrégat de secours15 .
Les éléments suivants sont définis pour cette section :
restore_command : Commande utilisée pour récupérer les WAL via le log shipping.
Lorsque le service Patroni est démarré, il initialise le cluster en se connectant à l’instance spécifiée.
Afin de s’assurer que les WAL soient toujours disponibles pour l’agrégat de secours, il est
b possible d’utiliser un slot permanent avec la section bootstrap.dcs.slots , abordé
plus loin.
Il est préférable de disposer d’une IP virtuelle (VIP) sur le serveur primaire de l’agrégat
primaire. De cette façon, si on perd le serveur source de la réplication vers le standby lea‑
der, la réplication basculera vers un autre nœud. Il est possible qu’il y ait une divergence
en cas de failover sur l’agrégat primaire.
Voici un exemple de configuration pour la création du leader d’un standby cluster. Le nom de
cluster a été changé, une section bootstap.dcs.standby_cluster a été créé et le mot de passe de
l’utilisateur de réplication a été configuré.
scope: acme-standby
name: p3
[…]
bootstrap:
dcs:
[…]
standby_cluster:
host: 10.0.0.21 # p1
port: 5432
[…]
postgresql:
authentication:
replication:
username: replicator
password: repass
[…]
On peut vérifier qu’un cluster a été créé dans le DCS pour les nœuds p3 et p4 (créé séparément) et
que la configuration du standby cluster a été adaptée.
Les commandes suivantes sont lancées depuis un serveur etcd.
$ export ETCDCTL_API=3
{
"ttl": 30,
"loop_wait": 10,
"retry_timeout": 10,
"primary_start_timeout": 300,
"postgresql": {
"use_pg_rewind": false,
"use_slots": true,
"parameters": {
"archive_mode": "on",
"archive_command": "/bin/true"
}
},
"standby_cluster": {
"host": "10.0.0.21",
"port": 5432
}
}
$ patronictl list
+--------+-----------+----------------+-----------+----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+ Cluster: acme-standby (7148326914433478989) ----+----+-----------+
| p3 | 10.0.0.23 | Standby Leader | running | 1 | |
| p4 | 10.0.0.24 | Replica | streaming | 1 | 0 |
+--------+-----------+----------------+-----------+----+-----------+
database : Le nom de la base de données pour laquelle le slot de réplication logique est créé.
Le nommage du slot de réplication persistant doit être fait en gardant à l’esprit qu’il faut éviter les
collisions de nom avec les slots créés par Patroni pour les besoins de la réplication en flux utilisée par
l’agrégat.
La section ignore_slots permet de donner à Patroni une liste de slots de réplication à ignorer.
Chaque slot défini dans cette section est défini par :
type: physical
ignore_slots:
- name: replication_logique_app
type: logical
database: magasin
plugin: test_decoding
- name: standby_hors_aggregat
type: physical
Comme dans PostgreSQL, le mode de réplication par défaut dans Patroni est le mode asynchrone. Il
est possible d’activer le mode synchrone en configurant synchronous_mode à on ou quorum . Ce
mode permet de limiter les risques de perte de données en cas de bascule non programmée. Pour
cela, Patroni n’autorise que les serveurs candidats à la réplication synchrone et le leader à participer
aux votes.
Le facteur de réplication synchrone synchronous_node_count définit le nombre de standby syn‑
chrones qui doivent recevoir les modifications avant de confirmer à l’utilisateur que ses données ont
été commitées.
– synchronous_mode: "on"
Avec cette configuration, Patroni active la réplication synchrone et constitue une liste
d’instances candidates à partir de la liste des instances disponibles. Cette liste est réévaluée à
chaque boucle HA en fonction du lag des serveurs, de leur statut et de leur configuration.
Sur un cluster à trois nœuds : p1 , p2 , p3 ; le paramétrage suivant :
synchronous_mode: on
synchronous_node_count: 1
{"leader":"p1","quorum":0,"sync_standby":"p2"}
name | setting
---------------------------+---------
synchronous_commit | on
synchronous_standby_names | p2
{"leader":"p1","quorum":0,"sync_standby":"p2, p3"}
et synchronous_standby_names devient :
name | setting
---------------------------+---------
synchronous_standby_names | 2 (p2,p3)
– synchronous_mode: "quorum"
Avec cette configuration, Patroni active la réplication synchrone et constitue une liste
d’instances candidates à partir de la liste des instances disponibles. Cette liste est réévaluée à
chaque boucle HA en fonction de leur statut et de leur configuration.
Sur un cluster à trois nœuds : p1 , p2 , p3 ; le paramétrage suivant :
synchronous_mode: quorum
synchronous_node_count: 1
name | setting
---------------------------+---------
synchronous_commit | on
synchronous_standby_names | ANY 1 (p2,p3)
La liste des serveurs peut être influencée par les tag nosync et nostream pour lesquels Patroni
va retirer un serveur de la liste des serveurs candidats à la réplication synchrone et modifier la
valeur du paramètre synchronous_standby_names dans PostgreSQL.
Patroni favorise la haute disponibilité du service. Il lui est possible de dégrader la réplication syn‑
chrone en réduisant le nombre de nœuds requis ( synchronous_node_count ) jusqu’à zéro. C’est pour
cette raison que Patroni reconstruit la liste des serveurs candidats à chaque boucle HA et change la
configuration de PostgreSQL en fonction. Cela a l’avantage d’éviter de bloquer les COMMIT sur le lea‑
der pendant trop longtemps en cas de perte d’une standby synchrone. Il faut juste attendre la pro‑
chaine boucle HA pour que le standby soit retirée de la liste, on attendra donc au pire ttl secondes
et en moyenne loop_wait / 2 secondes.
S’il ne reste plus de standby synchrone, en cas de panne du leader, le failover n’est pas possible.
Pour empêcher la désactivation du mode synchrone et garantir qu’en cas de panne aucun commit
n’est perdu, Patroni dispose de l’option synchronous_mode_strict qui positionne le paramètre
synchronous_standby_names à * dans PostgreSQL quand il n’y a plus assez de candidats.
Cela bloque les écritures en attendant le retour d’un follower synchrone ce qui provoque une
indisponibilité du service.
Note : Il est possible de positionner Patroni avec synchronous_mode à off et, dans la section
parameters dédiée à PostgreSQL, de configurer les paramètres synchronous_commit = on
et synchronous_standby_names = * . puisqu’il gère la bascule automatique et peut éviter la
promotion d’une standby asynchrone et donc la perte de données.
Patroni étant écrit en python, la configuration des traces devrait être familière aux utilisateurs de ce
langage.
Les paramètres suivants peuvent être configurés pour contrôler le contenu et l’emplacement des
traces :
level : Niveau de trace parmi CRITICAL , ERROR , WARNING , INFO et DEBUG . Par défaut INFO .
traceback_level : Niveau de trace à partir duquel les tracebacks sont visibles, par défaut ERROR .
format : Format des traces, défaut : %(asctime)s %(levelname)s: %(message)s . Voir : https://
docs.python.org/3.10/library/logging.html#logrecord‑attributes.
dateformat : Format de date. Voir : https://docs.python.org/3.10/library/logging.html#logging.Fo
rmatter.formatTime.
max_queue_size : Patroni génère une trace en deux temps. Il écrit les traces en mémoire et un thread
séparé s’occupe de reporter ces traces vers un fichier ou la sortie standard. La quantité de traces
gardée en mémoire est par défaut de 1000 enregistrements.
dir : Le répertoire où sont écrits les journaux. Patroni doit avoir les droits en écriture sur ce réper‑
toire.
file_num : Nombre de journaux applicatifs à conserver, par défaut 4 .
file_size : Taille maximale d’un journal applicatif avant qu’un nouveau ne soit créé, par défaut
25MB .
loggers : Cette section permet de définir un niveau de trace par module Python.
Patroni écrit lui même ses journaux applicatifs si et seulement si le paramètre dir est positionné.
Sinon, les traces sont envoyées vers la sortie standard, habituellement capturée vers les journaux
système par journald et/ou syslog .
Exemple où Patroni écrit ses journaux dans le répertoire /var/log/patroni :
[…]
log:
level: INFO
dir: /var/log/patroni
[…]
– connect_address
– listen
– authentication ( username , password )
– SSL ( certfile , keyfile , keyfile_password , cafile ,
verify_client )
L’accès à l’API REST peut être contrôlé grâce aux paramètres de la section restapi :
listen : Permet de définir les adresses et le port sur lesquelles Patroni expose son API REST. Elle
est notamment utilisée par les autres membres de l’agrégat pour vérifier la santé du nœud lors
d’une élection. Cette adresse peut également servir aux health checks d’outils comme HAProxy,
la supervision et les connexions utilisateurs.
connect_address : Adresse IP et port fournis aux autres membres pour l’accès à l’API REST de Pa‑
troni. Information stockée dans le DCS.
proxy_address : Adresse et port pour joindre le pool de connexion ou proxy qui permet d’accéder
à PostgreSQL. Une entrée proxy_url est crée dans le DCS afin de faciliter la découverte de
service.
authentication : Permet de définir un username et password autorisant l’accès à l’API REST.
verify_client : Définis quand la clé est requise : * none (défaut) : l’API REST ne vérifie pas les
certificats ; * requiered : les certificats clients sont requis pour tous les accès a l’API REST ; *
optional : les certificats ne sont requis que pour les accès marqués comme sensibles (appels
PUT , POST , PATCH et DELETE ).
allowlist : Liste d’hôtes autorisés à accéder aux API définies comme sensibles. Les noms d’hôtes,
adresses IP ou sous‑réseaux sont autorisés. Par défaut tout est autorisé.
allowlist_include_members : Autorise les membres de l’agrégat à accéder aux API sensibles.
L’adresse IP est récupérée à partir du paramètre api_url stocké dans le DCS : attention à ce
que ce soit bien l’IP utilisée pour accéder à l’API REST !
Il existe des paramètres supplémentaires permettant d’adapter les en‑têtes HTTP ou HTTPS. Voir :
https://patroni.readthedocs.io/en/latest/yaml_configuration.html#rest‑api
Depuis Patroni 3.2.0, Patroni utilise une connexion dédiée au serveur PostgreSQL.
Voici un exemple qui autorise les accès aux API sensibles uniquement depuis les membres de
l’agrégat :
name: p1
scope: acme
[…]
restapi:
listen: 0.0.0.0:8009
connect_address: 10.0.0.21:8009
allowlist_include_members: true
[…]
On peut voir que l’adresse définie dans connect_address est reportée dans le DCS sous le nom
api_url :
$ export ETCDCTL_API=3
$ etcdctl get --print-value-only '/service/acme/members/p1' | jq
{
"conn_url": "postgres://10.0.0.21:5432/postgres",
"api_url": "http://10.0.0.21:8008/patroni",
"state": "running",
"role": "primary",
"version": "4.0.4",
"xlog_location": 1023513512,
"timeline": 22
}
{
"loop_wait": 10,
"primary_start_timeout": 300,
"postgresql": {
"parameters": {
"archive_command": "/bin/true",
"archive_mode": "on",
"max_connections": 101
},
"use_pg_rewind": false,
"use_slot": true
},
"retry_timeout": 10,
"ttl": 30
}
La même commande lancée depuis un autre serveur se termine avec le message suivant :
Access is denied
– insecure
– certfile
– keyfile
– keyfile_password
– cacert
Le CLI patronictl livré avec Patroni permet d’effectuer différentes tâches d’administration. Cet outil
pouvant être utilisé depuis le poste d’un administrateur, la section de configuration ctl est spécifi‑
quement réservée à son mode d’authentification :
insecure : Autorise les connexions l’API REST sans vérification des certificats SSL.
certfile : Certificat au format PEM (active le SSL si présent).
keyfile : Clé secrète au format PEM.
keyfile_password : Mot de passe pour décrypter la clé.
cacert : Certificat d’autorité de certification.
Afin d’éviter une situation de split‑brain, Patroni doit s’assurer que l’instance PostgreSQL d’un nœud
n’accepte plus de transaction une fois que la leader key qui lui est associées expire. En temps normal,
Patroni essaie d’obtenir cette garantie en arrêtant l’instance. Cependant cette opération peut échouer
si :
– Patroni plante à cause d’un bug, un problème de mémoire ou le processus est tué par une source
extérieure ;
– l’arrêt de PostgreSQL est trop lent ;
– Patroni ne fonctionne pas à cause d’une charge trop importante sur le serveur, ou un autre pro‑
blème d’infrastructure ou d’hyperviseur.
Afin que le cluster réagisse correctement dans ces situations, Patroni supporte l’utilisation d’un watch‑
dog. Un watchdog est un composant doté d’un compte à rebours qui, au moment où il expire, éteint
ou redémarre physiquement le serveur sur‑le‑champ. En conséquence, un logiciel, ici Patroni, doit
donc recharger continuellement ce watchdog timeout (WDT) avant qu’il n’expire. Le destin du serveur
est donc directement lié à la bonne exécution de Patroni.
Activation et Désactivation
Patroni tente d’armer le watchdog sur le nœud qui devient leader avant la promotion de l’instance
PostgreSQL. Si l’utilisation d’un watchdog est requise (voir watchdog.mode ) et que le watchdog ne
s’active pas, le nœud refuse de devenir leader.
Un test est également réalisé lorsqu’un nœud décide de participer à l’élection du primaire et que le
watchdog est requis sur ce dernier. Dans ce cas Patroni vérifie que le device associé au watchdog existe
(voir watchdog.device ) et est accessible. Il contrôle également que le timeout du watchdog est su‑
périeur ou égal à la durée nominale d’une boucle ( loop_wait ).
Lorsqu’une instance perd le statut de leader ou que Patroni est mis en pause, le watchdog est désac‑
tivé.
Mécanique et paramétrage
Par défaut Patroni configure le watchdog pour expirer 5 secondes avant que le ttl n’expire, c’est le
paramètre safety_margin . Patroni calcule donc le watchdog timeout grâce à la formule suivante :
WDT = ttl - safety_margin .
Ces 5 secondes laissent une marge de sécurité avant que le leader key n’expire. Elles permettent de
garantir que l’ancien primaire est bien arrêté au moment où le ttl expire, ce qui déclenche alors une
nouvelle élection. Nous évitons ainsi une situation de split‑brain en cas d’incident ou de blocage.
Pour bien comprendre comment configurer ttl , safety_margin , loop_wait et retry_timeout ,
intéressons‑nous au fonctionnement interne de Patroni.
La boucle de haute disponibilité est exécutée au moins toutes les 10 secondes par défaut, c’est le pa‑
ramètre loop_wait . À la fin de chaque exécution, le processus calcule combien de temps il doit pa‑
tienter avant sa prochaine exécution pour respecter au mieux cette période de loop_wait secondes.
Dans les cas extrêmes (charge, lenteur, etc), il se ré‑exécute sur‑le‑champs pour rattraper son retard.
Le ttl étant de 30 secondes, Patroni a l’équivalent de trois exécutions de boucles pour le rechar‑
ger.
boucle 1 |---------------------- TTL ----------------------.
boucle 2 | |---------------------- TTL ----------------------.
boucle 3 | |---------------------- TTL ------…
0----------------1----------------2---------------3----------->
loop_wait
À chaque exécution de la boucle, après avoir déterminé que l’instance est primaire, Patroni doit :
En temps normal, le temps écoulé entre les actions 1 et 2 est négligeable. Le watchdog expire donc
environ safety_margin secondes avant le TTL de la leader key, ce qui est désiré.
Il faut aussi tenir compte qu’au début d’une boucle, avant que toute action ne commence, le WDT a
dans le meilleur des cas été rechargé il y a déjà loop_wait secondes, lors de l’exécution de la précé‑
dente boucle. Par défaut, la boucle a donc 15 secondes pour effectuer ses toutes premières actions.
boucle 1 |---------------------- TTL ----------------------.
|------------------- WDT ----------------.<~ (1) ~>
boucle 2 | |~ ~ ~ ~ ~ (2) ~ ~ ~ ~ ~>
0----------------1----------------2---------------3----------->
loop_wait
(1) =~ safety_margin
(2) =~ TTL - loop_wait - safety_margin = 30 - 10 - 5 = 15s
(1) retry_timeout
(2) temps de demote = WDT - loop_wait - retry_timeout = 25 - 10 - 10 = 5s
Si l’instance ne s’arrête pas proprement dans les temps, le watchdog arrête toute la machine brutale‑
ment, avec risque de perte des données non encore envoyées aux secondaires.
Le second scénario implique une réponse du DCS anormalement longue (charge, coupure réseau, etc)
ou un incident gelant la machine entre les deux recharges des TTL et WDT. Or, si le watchdog est re‑
chargé plus que safety_margin secondes après le TTL, alors le WDT expire malheureusement après
le prochain TTL.
boucle 1 |---------------------- TTL ----------------------.
|<~ ~ (1) ~ ~>------------------- WDT ----------------.
| <(2)>
0----------------1----------------2---------------3----------->
loop_wait
Ce cas est relativement peu probable, mais possible. De plus, il peut très bien survenir sans aucune
conséquence pour le cluster. Rappelez‑vous que la boucle est de 10 secondes par défaut, les TTL et
WDT pourraient très bien être rechargés correctement à la prochaine boucle, longtemps avant leurs
expirations respectives. Mais dans le pire des cas, la charge du DCS pourrait par exemple être conti‑
nuelle reproduisant ainsi cet événement systématiquement, rendant le watchdog inefficace pour pro‑
téger votre cluster d’un split‑brain.
Si l’on ne souhaite pas courir ce risque, il est possible de réduire le watchdog timeout. En contrepartie,
il faut alors soit augmenter ttl , soit diminuer loop_wait et/ou retry_timeout . Pour illustrer cela,
avec safety_margin = -1 = ttl / 2 (valeur du WDT dans les anciennes versions de Patroni avant
que safety_margin n’apparaisse), si nous ne modifions par ces autres paramètres, avec les calculs
expliqués précédemment nous obtenons alors :
– WDT = ttl / 2 = 30 / 2 = 15 secondes ;
– le temps disponible au début d’une boucle pour réaliser l’ensemble de toutes ses actions, y com‑
pris recharger les TTL et WDT n’est plus que de 5 secondes : WDT - loop_wait = 15 - 10 = 5 secondes ;
– en cas de ralentissement du DCS, le watchdog peut se déclencher avant même d’avoir eu la vali‑
dation du rechargement du TTL : WDT - loop_wait - retry_timeout = 15 - 10 - 10 = -5 secondes
Avec une telle configuration, le cluster est donc beaucoup plus sensible. De plus :
– augmenter ttl : retarde le déclenchement du failover ;
– diminuer loop_wait : augmente la consommation de ressource de Patroni ainsi que le nombre
d’accès au DCS. Cela rend donc l’architecture plus sensible à tous types de ralentissements. La
plus petite valeur autorisée de loop_wait est 1s ;
– diminuer retry_timeout : rends le système plus sensible aux problèmes réseaux ou charge
du DCS. La plus petite valeur autorisée de retry_timeout est 3s .
Quoi qu’il en soit, en cas de gel du serveur PostgreSQL, de charge importante sur les DCS ou les ins‑
tances, ou encore d’incident réseau, le bon correctif reste de régler le problème à la racine.
Voici un résumé des paramètres de la section watchdog qui permettent de configurer le watchdog :
mode : Le watchdog peut être désactivé ( off ), être activé si c’est possible ( automatic ) ou être obli‑
gatoire ( required ). Dans ce dernier mode, si le watchdog ne peut pas s’activer, le nœud ne peut
pas devenir leader. Par défaut à automatic .
Patroni permet de configurer des marqueurs pour adapter le fonctionnement des nœuds dans la sec‑
tion tags du fichier YAML :
clonefrom : Définis le nœud comme source privilégiée pour l’initialisation des secondaires. Si plu‑
sieurs nœuds sont dans ce cas, la source est choisie au hasard. Désactivé par défaut.
noloadbalance : Si activé, le nœud renvoie le code HTTP 503 pour l’accès au endpoint GET /repica
ce qui l’exclut du load balancing. Désactivé par défaut.
replicatefrom : L’adresse IP d’un autre réplica utilisé pour faire de la réplication en cascade.
failover_priority : Ce tag permet de définir la priorité que doit avoir le nœud en cas d’élection.
Si deux nœuds ont reçu et rejoué la même quantité de données, celui qui a la priorité la plus
élevée sera choisi. Une priorité inférieure ou égale à zéro est équivalente à nofailover: true .
nostream : Si ce tag est positionné a true , le nœud n’utilisera pas le protocole de réplication mais
uniquement le log shipping (à condition que restore_command soit configuré). Cela désactive
la copie et la synchronisation des slots de réplication logique sur ce nœud et toutes ses standby.
[…]
tags:
noloadbalance: true
montag: "mon tag a moi"
$ patronictl list
+--------+-----------+---------+-----------+-[…]-+----------------------+
| Member | Host | Role | State | […] | Tags |
+ Cluster: acme (7147602572400925478)[…]---+-[…]-+----------------------+
| p1 | 10.0.0.21 | Leader | running | […] | montag: mon tag a moi|
| | | | | […] | noloadbalance: true |
+--------+-----------+---------+-----------+-[…]-+----------------------+
| p2 | 10.0.0.22 | Replica | streaming | […] | |
+--------+-----------+---------+-----------+-[…]-+----------------------+
Patroni permet de déployer un cluster multinoœud CITUS16 . Le CLI patronictl a également été
adapté pour afficher les informations sur les groupes de serveurs Citus.
Il est possible d’utiliser des variables d’environnement pour configurer la plupart des éléments pré‑
sentés précédemment. On choisit cependant généralement d’utiliser le fichier de configuration YAML
pour cela.
La liste complète est disponible à cette adresse : https://patroni.readthedocs.io/en/latest/ENVIRON
MENT.html#environment‑configuration‑settings
Certaines variables sont cependant utiles au quotidien et méritent d’être chargées au démarrage de
la session de l’utilisateur destiné à manipuler patronictl . Notamment :
16
https://docs.citusdata.com/en/stable/installation/multi_node.html
patronictl permet d’interagir avec l’agrégat pour modifier son comportement ou consulter son
état. Avec la bonne configuration et arguments, il est possible de l’utiliser depuis n’importe quelle
machine, et n’importe quel utilisateur, l’utilisateur système postgres y compris.
$ export PATRONICTL_CONFIG_FILE=/etc/patroni/config.yml
$ patronictl topology
+ Cluster: acme (6876375338380834518) ---+-----------+-----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+--------+----------------+--------------+-----------+-----+-----------+
| p1 | 10.0.0.21:5432 | Leader | running | 122 | |
| + p2 | 10.0.0.22:5432 | Sync Standby | streaming | 122 | 0 |
| + p3 | 10.0.0.23:5432 | Sync Standby | streaming | 122 | 0 |
+--------+----------------+--------------+-----------+-----+-----------+
La commande utilise les informations contenues dans le DCS comme base de ses actions, mais doit
aussi pouvoir atteindre les API REST des démons Patroni.
Le fichier de configuration doit au minimum contenir le paramètre scope . Si le DCS n’y est pas pré‑
sent, il est possible de désigner l’un des nœuds via l’argument --dcs-url ( -d ou --dcs ). Toutes
les commandes suivantes sont équivalentes :
$ cat /etc/patroni/config.yml
scope: acme
$ cat /etc/patroni/config.yml
scope: acme
etcd3:
hosts:
- 10.0.00.11:2379
- 10.0.00.12:2379
- 10.0.00.13:2379
$ export PATRONICTL_CONFIG_FILE=/etc/patroni/config.yml
$ patronictl topology
Options:
-c, --config-file TEXT Configuration file
-d, --dcs-url, --dcs TEXT The DCS connect url
-k, --insecure Allow connections to SSL sites without certs
--help Show this message and exit.
Commands:
dsn Generate a dsn for the provided member, defaults to a dsn...
edit-config Edit cluster configuration
failover Failover to a replica
flush Discard scheduled events
history Show the history of failovers/switchovers
list List the Patroni members for a given Patroni
pause Disable auto failover
query Query a Patroni PostgreSQL member
reinit Reinitialize cluster member
reload Reload cluster member configuration
remove Remove cluster from DCS
restart Restart cluster member
resume Resume auto failover
show-config Show cluster configuration
switchover Switchover to a replica
topology Prints ASCII topology for given cluster
version Output version of patronictl command or a running Patroni...
$ patronictl list
+ Cluster: acme (6876375338380834518) ---+-----------+-----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+--------+----------------+--------------+-----------+-----+-----------+
| p1 | 10.0.0.21:5432 | Sync Standby | running | 123 | 0 |
| p2 | 10.0.0.22:5432 | Leader | streaming | 123 | |
| p3 | 10.0.0.23:5432 | Sync Standby | streaming | 123 | 0 |
+--------+----------------+--------------+-----------+-----+-----------+
La commande topology affiche la liste des nœuds sous la forme d’un arbre débutant par le primaire
courant, suivi des nœuds secondaires.
$ patronictl topology
+ Cluster: acme (6876375338380834518) ---+-----------+-----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+--------+----------------+--------------+-----------+-----+-----------+
| p2 | 10.0.0.21:5432 | Leader | running | 123 | |
| + p1 | 10.0.0.22:5432 | Sync Standby | streaming | 123 | 0 |
| + p3 | 10.0.0.23:5432 | Sync Standby | streaming | 123 | 0 |
+--------+----------------+--------------+------------+-----+-----------+
La colonne state indique l’état du serveur, pour une instance secondaire, streaming signifie que
la réplication en flux est active. running signifie que la réplication ne fonctionne pas, soit le serveur
est incapable de rattraper son retard, soit il est en train de le faire via le log shipping.
La configuration commune des instances peut être affichée avec l’argument show-config :
$ patronictl show-config
loop_wait: 10
maximum_lag_on_failover: 1048576
postgresql:
parameters:
archive_command: pgbackrest --stanza=main archive-push %p
archive_mode: 'on'
checkpoint_timeout: 15min
log_min_duration_statement: -1
wal_keep_size: '1GB'
use_pg_rewind: true
use_slots: true
retry_timeout: 10
synchronous_mode: true
synchronous_node_count: 2
ttl: 30
Cette commande lance l’éditeur par défaut de l’environnement afin d’effectuer une modification dans
la configuration de l’agrégat. Elle nécessite la commande less pour la vérification finale.
Utiliser ALTER SYSTEM , ou modifier manuellement les fichiers du PGDATA , peut me‑
Á ner à des nœuds utilisant des configurations différentes ! Il est fortement conseillé de
positionner le paramètre allow_alter_system à off (à partir de PostgreSQL 17) pour
éviter une erreur de manipulation.
Il ne faut pas modifier postgresql.conf directement, mais utiliser edit-config pour adapter la
configuration YAML dynamique, par exemple :
loop_wait: 10
maximum_lag_on_failover: 1048576
postgresql:
parameters:
shared_buffers: 64MB
work_mem: 70MB
use_pg_rewind: true
use_slots: true
retry_timeout: 10
ttl: 30
Après avoir enregistré ses modifications et quitter l’éditeur, la configuration est écrite dans le DCS
puis appliquée de manière asynchrone par Patroni, sur chacun des nœuds concernés, si celle‑ci ne
nécessite pas de redémarrage. Dans le cas contraire, le nœud est marqué comme pending restart et
doit être redémarré manuellement avec la commande patronictl restart .
En pratique, la configuration de PostgreSQL est stockée par Patroni dans le DCS qui fait référence :
Le pg_hba.conf se modifie avec la même commande. Bien penser à reprendre toute sa configuration
la première fois.
$ patronictl -c /etc/patroni/config.yml edit-config
+++
@@ -10,5 +10,7 @@
work_mem: 50MB
use_pg_rewind: true
use_slots: true
+ pg_hba:
+ - host user erp 192.168.99.0/24 md5
+ - host all all all scram-sha-256
+ - host replication replicator all scram-sha-256
retry_timeout: 10
ttl: 30
La commande switchover permet d’effectuer une bascule du rôle primaire vers l’une des instances
secondaires. Cette commande nécessite de spécifier explicitement l’instance secondaire devant être
promue. L’instance primaire est alors déchue en secondaire, puis relâche le leader lock. Le verrou
ayant disparu, une élection est organisée et seule l’instance Patroni désignée est autorisée à prendre
possession du leader lock, puis promouvoir son instance PostgreSQL locale en production.
Voici un exemple d’exécution de cette commande :
$ patronictl switchover
Primary [p1]:
Candidate ['p2', 'p3'] []: p2
When should the switchover take place (e.g. 2021-05-28T14:48 ) [now]:
Current cluster topology
+ Cluster: acme (6876375338380834518) ---+-----------+-----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+--------+----------------+--------------+-----------+-----+-----------+
| p1 | 10.0.0.21:5432 | Leader | running | 124 | |
| p2 | 10.0.0.22:5432 | Sync Standby | streaming | 124 | 0 |
| p3 | 10.0.0.23:5432 | Sync Standby | streaming | 124 | 0 |
+--------+----------------+--------------+-----------+-----+-----------+
Are you sure you want to switchover cluster acme, demoting current leader p1?
[y/N]: y
Il est également possible de forcer une bascule de manière non interactive avec l’argument option
--force et en spécifiant le primaire courant et le nœud secondaire cible :
La commande failover permet de déclencher une bascule en déclarant défaillant le primaire cou‑
rant. C’est une bonne manière de valider qu’un secondaire est prêt à devenir primaire et que les se‑
condaires sont capables de se raccrocher à lui une fois sa promotion effectuée.
Contrairement à la commande switchover , la commande failover ne nécessite pas de désigner
l’instance à promouvoir. L’élection se déroule normalement et une des meilleures instances secon‑
daires disponible est alors promue.
La commande failover permet aussi de promouvoir une instance lorsque toutes les instances dis‑
ponibles sont au statut replica . Ce genre de cas peut se présenter lors de certaines opérations de
restauration.
Voici un exemple d’utilisation :
$ patronictl failover
Candidate ['p1', 'p3'] []: p1
Current cluster topology
+ Cluster: acme (6876375338380834518) -+-----------+-----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+--------+--------------+--------------+-----------+-----+-----------+
| p1 | 10.0.0.21:5432 | Sync Standby | streaming | 123 | 0 |
| p2 | 10.0.0.22:5432 | Leader | running | 123 | |
| p3 | 10.0.0.23:5432 | Sync Standby | streaming | 123 | 0 |
+--------+----------------+--------------+-----------+-----+-----------+
Are you sure you want to failover cluster acme, demoting current leader p2?
[y/N]: y
ʦ – pause , resume
– history
– reinit
Maintenance :
La commande patronictl pause place le cluster en mode maintenance. Cette commande « dé‑
tache » le démon Patroni de l’instance qu’il manage. Cela a plusieurs effets sur le comportement du
système :
On l’utilise généralement lorsqu’une anomalie conduit à une avalanche de bascules non désirées ou
lorsque l’on doit exécuter des opérations de maintenance sur le nœud qui entreraient en conflit avec
Patroni.
L’option --wait permet de s’assurer que la commande a été prise en compte par tous les nœuds. On
peut observer son effet avec la commande patronictl list qui affiche que le mode maintenance
est activé :
$ patronictl list
+--------+------------+----------------+---------+----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
[…]
+--------+------------+----------------+---------+----+-----------+
Maintenance mode: on
Historique :
$ patronictl history
+-----+------------+------------------------------+--------------------------------+
| TL | LSN | Reason | Timestamp |
+-----+------------+------------------------------+--------------------------------+
| 1 | 25577936 | no recovery target specified | |
| 2 | 83886528 | no recovery target specified | |
| 3 | 83887160 | no recovery target specified | |
[…]
| 122 | 4445962400 | no recovery target specified |2021-05-28T13:41:57.231514+00:00|
| 123 | 4462739616 | no recovery target specified |2021-05-28T13:46:47.366787+00:00|
| 124 | 4479516832 | no recovery target specified |2021-05-28T13:48:44.616172+00:00|
+-----+------------+------------------------------+--------------------------------+
Réinitialisation :
Toutes les données du nœud sont détruites, écrasées par celles du primaire !
Á
$ patronictl reinit acme p2
+ Cluster: acme (6876375338380834518) -----+----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+--------+-----------+---------+-----------+----+-----------+
| p1 | 10.0.0.21 | Leader | running | 8 | |
| p2 | 10.0.0.22 | Replica | streaming | 8 | 0 |
+--------+------------+---------+-----------+----+-----------+
Are you sure you want to reinitialize members p2? [y/N]: y
Success: reinitialize for member p2
Il est impossible de lancer une réinitialisation du nœud primaire puisqu’elle est faite à
Á partir du primaire courant.
ʦ – reload
– attention aux pending restart
– restart
Changement de configuration :
La commande patronictl reload recharge la configuration de tous les nœuds de l’agrégat ou d’un
nœud s’il est spécifié.
$ patronictl reload acme p1
+ Cluster: acme (6876375338380834518) ---+-----------+-----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+--------+----------------+--------------+-----------+-----+-----------+
| p1 | 10.0.0.21:5432 | Leader | running | 122 | |
| p2 | 10.0.0.22:5432 | Sync Standby | streaming | 122 | 0 |
| p3 | 10.0.0.23:5432 | Sync Standby | streaming | 122 | 0 |
+--------+----------------+--------------+-----------+-----+-----------+
Are you sure you want to reload members p1? [y/N]: y
Reload request received for member p1 and will be processed within 10 seconds
été modifiée sont marqués avec la mention pending restart dans patronictl list . Il est néces‑
saire de les redémarrer pour que le changement de configuration soit bien pris en compte.
Redémarrage :
La commande patronictl restart redémarre le nœud spécifié ou l’agrégat entier en commençant
par le primaire, suivi de ses secondaires.
Elle ne provoque pas de changement de rôle si l’opération se passe bien. Il est possible de redémarrer
uniquement les serveurs en mode pending restart grâce à l’option --pending .
L’API REST de Patroni est principalement utilisée par patronictl mais peut tout aussi bien être
consultée par n’importe quel autre outil, tel que curl , wget ou encore un load balancer.
Cette API permet par exemple de confirmer le rôle d’un serveur grâce à une simple requête HTTP sur
l’un des endpoints suivants :
Il est possible d’enrichir la requête API en filtrant sur le lag de réplication. On peut aussi vérifier la
présence ou la valeur d’un tag dans la configuration d’un nœud. Cette option n’est néanmoins pas
disponibles pour les endpoints : /primary , /leader et /standby-leader .
Exemples d’utilisations :
$ curl -I -s http://10.0.0.23:8008/replica?tag_is_candidate=true
HTTP/1.0 200 OK
$ curl -I -s http://10.0.0.23:8008/replica?tag_is_here=true
HTTP/1.0 503 Service Unavailable
$ curl -I -s http://10.0.0.23:8008/replica?tag_doesnt_exist=false
HTTP/1.0 503 Service Unavailable
$ curl -I -s http://10.0.0.23:8008/replica?lag=16MB
HTTP/1.0 200 OK
Comme démontré précédemment, la configuration peut également être modifiée avec une requête
PATCH ou remplacée avec une requête PUT .
Pour finir, il est possible d’interagir avec le cluster pour réaliser certaines actions de maintenances :
– /switchover , /failover
– /restart , /reload , /reinitialize
Par exemple :
$ curl -s http://10.0.0.23:8008/switchover -XPOST \
> -d'{"leader":"p2", "candidate":"p1"}'
Seule une opération de chaque type peut être planifiée. Elles peuvent être dé‑planifiées avec une
requête DELETE .
Les réplicas d’un cluster Patroni peuvent être utilisés pour faire de la répartition de charge en lecture
(query off‑loading). Il faut cependant être conscient des limitations associées à cette utilisation des
instances secondaires.
Si la réplication est asynchrone, l’instance secondaire n’est pas nécessairement à jour. Cependant, la
mise en place d’une réplication synchrone ne règle pas totalement ce problème. En effet, la garantie
de réplication synchrone porte sur l’écriture des données dans les WAL de l’instance secondaire et
la demande de synchronisation sur disque, ce qui ne garanti pas l’application et la visibilité de ces
données. Il est possible de configurer la réplication en remote apply, ce qui garanti que les données
sont visibles sur la secondaire avant que la primaire ne rende la main au client. Dans cette situation
cependant, les données sont potentiellement visibles sur la secondaire avant la primaire.
Un autre point à prendre en compte est la cohérence des données entre les instances secondaires. Sur
ce plan, il n’y a aucune garantie.
L’API REST de Patroni permet de choisir à quel serveur on se connecte. Pour cela, il faut utiliser les
endpoints /replica , /read-only , /asynchronous ou /synchronous . Une requête HTTP renverra
le code 200, si le serveur correspond au filtre du endpoint.
On peut également éliminer des serveurs en fonction de la valeur d’un tag sur la base du retard de
réplication pour les endpoints : /replica , /read-only , /asynchronous .
La chaîne de connexion utilisée pour se connecter à une instance PostgreSQL permet d’indiquer plu‑
sieurs nœuds et sur quel type de nœud se connecter, sans avoir besoin de proxy ou d’appel d’API.
Cette chaîne de connexion existe sous deux formats dont voici des exemples :
host=p1,p2,p3 user=dba dbname=postgres target_session_attrs=primary
postgresql://dba@p1,p2,p3/postgres?target_session_attrs=primary
psql "postgresql://dba@p1,p2,p3:5432/postgres?target_session_attrs=prefer-standby"
0.7.3 HAProxy
Il peut être installé sur un serveur indépendant, mais il est alors nécessaire de penser à sa mise en
haute disponibilité pour éviter de créer un SPOF dans l’architecture.
Une autre solution consiste à placer HAProxy sur le serveur d’application ou de base de données et
coupler la haute disponibilité du répartiteur de charge avec celle de l’application qu’on lui associe.
La documentation de Patroni propose d’utiliser la configuration suivante pour HAProxy :
global
maxconn 100
defaults
log global
mode tcp
retries 2
timeout client 30m
timeout connect 4s
timeout server 30m
timeout check 5s
listen stats
mode http
bind *:7000
stats enable
stats uri /
listen primary
bind *:5000
option httpchk HEAD /primary
http-check expect status 200
default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
server node1 10.0.0.21:5432 maxconn 100 check port 8008
server node2 10.0.0.22:5432 maxconn 100 check port 8008
server node3 10.0.0.23:5432 maxconn 100 check port 8008
listen replicas
bind *:5001
option httpchk HEAD /replica
http-check expect status 200
default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
server node1 10.0.0.21:5432 maxconn 100 check port 8008
server node2 10.0.0.22:5432 maxconn 100 check port 8008
server node3 10.0.0.23:5432 maxconn 100 check port 8008
La section default permet de définir où tracer (paramètre log ), le type de connexion ici tcp (pa‑
ramètre mode ), ainsi que les timeouts pour les connexions à HAProxy et à PostgreSQL (paramètres
timeout* ).
La section listen stats permet de définir un endpoint pour la consultation des statistiques
d’utilisation de HAProxy sur le port 7000. Elles sont consultables en se connectant avec un navigateur
web (paramètre mode ).
La section listen primary permet de renvoyer les connexions sur l’instance primaire lorsque
l’on se connecte au port 5000 de HAProxy (paramètre bind ). Une vérification est réalisée sur le
endpoint /primary de l’API REST de Patroni pour établir quelle instance est la primaire (paramètres
http-check et option httpchl ). Pour ce faire, HAProxy teste pour chaque serveur déclaré
(paramètres server ) si l’API REST qui écoute sur le port 8008 (paramètre check port du serveur)
répond par un code retour HTTP 200.
La section listen replicas permet de renvoyer les connexions sur les instances secondaires
lorsque l’on se connecte au port 5001 de HAProxy. Une vérification est réalisée sur le endpoint
/replica de l’API REST de Patroni pour vérifier quelles instances sont des secondaires. Pour ce
faire, HAProxy teste pour chaque serveur déclaré, si l’API REST qui écoute sur le port 8008 répond par
un code retour HTTP 200.
Pour chacune des sections permettant de se connecter à PostgreSQL, la connexion est vérifiée
toutes les 3 secondes (paramètre inter ). Au bout de trois échecs consécutifs, le serveur est
considéré comme hors service (paramètre fall ) et les sessions en cours sont stoppées (paramètre
on-marked-down shutdown-sessions ). Si un serveur est marqué comme indisponible, il faut 2
tests réussis consécutivement avant que le serveur soit considéré comme disponible (paramètre
rise ).
Il faut faire attention à l’utilisation du paramètre lag=valeur sur le endpoint de l’API REST de Patroni.
Une valeur trop faible peut entraîner des changements de statut fréquents et donc des déconnexions
fréquentes.
Par défaut, la répartition de charge se fait avec un algorithme de round‑robin. On peut changer
l’algorithme en ajoutant un paramètre balance dans la définition d’un endpoint et/ou spécifier des
poids par serveur pour influencer l’algorithme (paramètre weight d’un serveur). Il y a beaucoup de
possibilités d’algorithme, par exemple :
first : Premier serveur (trié par identifiant) avec une connexion disponible. Un identifiant est auto‑
matiquement attribué à tout serveur n’en possédant pas.
Le nom fourni pour la déclaration des serveurs sera visible dans la page de statistiques, ici node1 ,
node2 , node3 .
0.7.4 Keepalived
Une VIP ou Virtual IP address est une adresse IP qui peut être partagée par plusieurs serveurs. Elle n’est
active que sur un serveur à la fois. Cela permet d’avoir un point d’accès unique qui change en fonction
de la disponibilité d’un service sur plusieurs serveurs.
Keepalived permet de gérer une VIP et de s’assurer qu’elle ne soit montée que sur un seul serveur. Il
permet d’utiliser des scripts de vérification, de surveiller l’état d’un processus, la disponibilité d’un
serveur ou la présence d’un fichier de déclenchement afin de conditionner le montage de la VIP.
Keepalived est un outil très versatile qui permet aussi de faire la répartition de charge.
Voici un exemple de configuration pour maintenir une VIP sur le serveur de l’instance primaire d’un
cluster Patroni. Elle doit être mise en place sur tous les serveurs PostgreSQL du cluster.
global_defs {
enable_script_security
script_user root
}
vrrp_script keepalived_check_patroni {
script "/usr/local/bin/keepalived_check_patroni.sh"
interval 3 # interval between checks
timeout 5 # how long to wait for the script return
rise 1 # How many time the script must return ok, for the
# host to be considered healthy (avoid flapping)
fall 1 # How many time the script must return Ko; for the
# host to be considered unhealthy (avoid flapping)
}
vrrp_instance VI_1 {
state MASTER
interface eth1
virtual_router_id 51
priority 244
advert_int 1
virtual_ipaddress {
10.0.0.50/24
}
track_script {
keepalived_check_patroni
}
}
La VIP sera montée sur l’interface eth1 (paramètre interface ). Il est important de choisir un
virtual_router_id inutilisé pour la configuration de la VIP. Il est possible de mettre un poids sur
les serveurs (paramètre priority ), par convention un serveur primaire devrait avoir la priorité 255.
Le cas présent est un peu différent, puisqu’on utilise un script pour déterminer l’état de l’instance
(paramètre track_script ).
/usr/bin/curl \
-X GET -I --fail \
# --cacert ca.pem --cert p1.pem --key p1-key.pem \
https://127.0.0.1:8008/primary &>>/var/log/patroni/keepalived_vip.log
Dans le cadre de ce script, pensez à prévoir une configuration logrotate sur le fichier de log obtenu.
0.8 QUESTIONS
ʦ – C’est le moment !
0.9 QUIZ
https://dali.bo/r58_quiz
ʦ
Avec Debian, ne pas utiliser l’intégration de Patroni dans la structure de gestion multiinstance propo‑
sée par postgresql-common afin de se concentrer sur l’apprentissage.
– Donner les droits à l’utilisateur postgres sur le fichier /dev/watchdog . Après quelques
secondes, que se passe‑t‑il ?
– Insérer une ligne toutes les secondes, à chaque fois dans une nouvelle connexion au pri‑
maire.
– Dans une autre fenêtre, afficher les 20 dernières lignes de cette table.
– Arrêter les processus du nouveau primaire. Il ne reste qu’un nœud actif. Que se passe‑t‑il ?
Si cette étape a déjà été réalisée, arrêter le service PostgreSQL et supprimer le cluster.
# systemctl stop postgresql@16-main
$ pg_dropcluster 16 main
# apt update
[…]
# /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.
Installation de PostgreSQL 16 :
# apt install postgresql-16
[…]
Setting up postgresql-16 (16.2-1.pgdg120+2) ...
Avec Debian, ne pas utiliser l’intégration de Patroni dans la structure de gestion multiinstance propo‑
sée par postgresql-common afin de se concentrer sur l’apprentissage.
– 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 jour‑
naux 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 envi‑
ronnements.
– postgresql.datadir: "/var/lib/postgresql/17/main" : emplacement du PGDATA ;
– postgresql.pg_hba: : vérifier la cohérence des règles pour les clients et la réplication. Éven‑
tuellement, facilitez‑vous la vie pour le reste du TP ;
– postgresql.bindir: "/usr/lib/postgresql/17/bin" : chemin vers les binaires de Post‑
greSQL ;
– postgresql.authentication.replication.password : positionner le mot de passe à attri‑
buer 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 cor‑
rectes ;
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 :
etcd3:
hosts:
- 10.0.0.11:2379
- 10.0.0.12:2379
- 10.0.0.13:2379
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 :
postgresql:
parameters:
logging_collector: on
log_destination: stderr
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 .
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/17/bin'
connect_address: 10.0.0.21:5432
data_dir: '/var/lib/postgresql/17/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 :
chmod 0644 /etc/patroni/config.yml
install -o postgres -g postgres -d /var/log/patroni/acme
Le code retour de la commande est 0 si tout est valide. Sinon, la commande affiche les avertisse‑
ments 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
Sur p1, nous trouvons les messages suivants dans le journal /var/log/patroni/acme/patroni.log :
Le démon Patroni démarre, choisi un serveur etcd, se saisit du leader lock et initialise l’instance Post‑
greSQL locale. Les sorties standard et d’erreur des commandes exécutées par Patroni n’est pas cap‑
turé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/17/bin/pg_ctl -D /var/lib/postgresql/17/main -l
↪ logfile start
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/17/main/log , équivalent à
~postgres/17/main/log .
$ 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
Il est conseillé lors des tests de garder une fenêtre répétant l’ordre régulièrement :
$ watch -n1 patronictl topology
{
"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"
}
}
{
"name": "p1"
}
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 }'
{
"name": "p2"
}
{
"name": "p3"
}
watchdog:
mode: required
# device: /dev/watchdog
safety_margin: 5
Suite à cette modification, le cluster Patroni se retrouve sans primaire. Dans les journaux applicatif de
p3 , ici précédemment marqué Leader, nous trouvons :
– Donner les droits à l’utilisateur 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écom‑
mentant la ligne suivante :
#ExecStartPre=-/usr/bin/sudo /bin/chown postgres /dev/watchdog
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"
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
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"
– Insérer une ligne toutes les secondes, à chaque fois dans une nouvelle connexion au pri‑
maire.
– 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;"'
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
Les insertions échouent le temps de la bascule, ici pendant environ 2 secondes, puis continuent de‑
puis 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
– Arrêter les processus du nouveau primaire. Il ne reste qu’un nœud actif. Que se passe‑t‑il ?
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?
Dans le cadre de cette correction p2 est l’actuel leader, nous redémarrons donc Patroni sur p1 :
rm -rf /var/lib/postgresql/17/main
Nous observons dans les journaux de Patroni et PostgreSQL que l’instance est recrée et se raccroche
à p2 :
$ 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 | […] |
+--------+-----------+---------+------------------+----+-----------+------+
$ 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 | […] |
+--------+-----------+---------+-----------+----+-----------+------+
$ 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 | […] |
+--------+-----------+---------+-----------+----+-----------+------+
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.
---
+++
@@ -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
$ 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 | * | […] |
+--------+-----------+---------+-----------+----+------+-----------------+------+
$ 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
}
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.
# systemctl stop postgresql-16
# systemctl disable postgresql-16
# rm -Rf /var/lib/pgsql/17/data
Complete!
– 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 jour‑
naux 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 envi‑
ronnements.
– postgresql.datadir: "/var/lib/pgsql/17/data" : emplacement du PGDATA ;
– postgresql.pg_hba: : vérifier la cohérence des règles pour les clients et la réplication. Éven‑
tuellement, 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 à attri‑
buer 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 cor‑
rectes ;
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 :
etcd3:
hosts:
- 10.0.0.11:2379
- 10.0.0.12:2379
- 10.0.0.13:2379
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 :
postgresql:
parameters:
logging_collector: on
log_destination: stderr
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 .
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/17/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 :
chmod 0644 /etc/patroni/patroni.yml
install -o postgres -g postgres -d /var/log/patroni/acme
Le code retour de la commande est 0 si tout est valide. Sinon, la commande affiche les avertisse‑
ments 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
Sur p1, nous trouvons les messages suivants dans le journal /var/log/patroni/acme/patroni.log :
Le démon Patroni démarre, choisi un serveur etcd, se saisit du leader lock et initialise l’instance Post‑
greSQL locale. Les sorties standard et d’erreur des commandes exécutées par Patroni n’est pas cap‑
turé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/17/data -l logfile start
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/17/data/log/ , équivalent à
~postgres/17/data/log .
du cluster et idéalement les nœuds du DCS. Sur les machines p1 et p2 , nous pouvons utiliser direc‑
tement le fichier de configuration de Patroni /etc/patroni/patroni.yml . La commande devient :
$ 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
Il est conseillé lors des tests de garder une fenêtre répétant l’ordre régulièrement :
$ watch -n1 patronictl topology
{
"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"
}
}
{
"name": "p1.hapat.vm"
}
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 }'
{
"name": "p2.hapat.vm"
}
{
"name": "p3.hapat.vm"
}
watchdog:
mode: required
# device: /dev/watchdog
safety_margin: 5
Suite à cette modification, le cluster Patroni se retrouve sans primaire. Dans les journaux applicatif de
p3 , ici précédemment marqué Leader, nous trouvons :
– Donner les droits à l’utilisateur 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écom‑
mentant la ligne suivante :
#ExecStartPre=-/usr/bin/sudo /bin/chown postgres /dev/watchdog
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"
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
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"
– Insérer une ligne toutes les secondes, à chaque fois dans une nouvelle connexion au pri‑
maire.
– 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 :
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
Les insertions échouent le temps de la bascule, ici pendant environ 2 secondes, puis continuent de‑
puis 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
– Arrêter les processus du nouveau primaire. Il ne reste qu’un nœud actif. Que se passe‑t‑il ?
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?
Dans le cadre de cette correction p2 est l’actuel leader, nous redémarrons donc Patroni sur p1 :
rm -rf /var/lib/pgsql/17/data
Nous observons dans les journaux de Patroni et PostgreSQL que l’instance est recrée et se raccroche
à p2 :
$ 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 | […] |
+---------------+-----------+---------+------------------+----+-----------+------+
$ 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 | […] |
+---------------+-----------+---------+-----------+----+-----------+------+
$ patronictl failover
Current cluster topology
+ Cluster: acme (7350009258581743592) ----------+----+-----------+------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+-------------+-----------+---------+-----------+----+-----------+------+
$ 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 | […] |
+---------------+-----------+---------+-----------+----+-----------+------+
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 :
$ export PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml
$ patronictl edit-config
[…]
---
+++
@@ -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
$ 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 | * |
+---------------+-----------+---------+-----------+----+------+-----------------+-
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
}
101
DALIBO Formations
Téléchargement gratuit
Les versions électroniques de nos publications sont disponibles gratuitement sous licence open
source ou sous licence Creative Commons.