Introduction
Patroni est un outil open source qui permet de créer et gérer un cluster PostgreSQL hautement disponible. Il peut être utilisé pour gérer des tâches telles que la réplication, les sauvegardes et les restaurations. En utilisant une configuration HAProxy, Patroni offre un point d'accès unique pour se connecter au serveur primaire du cluster, indépendamment de l'emplacement de la base de données primaire.
Si vous avez besoin de déployer rapidement un cluster PostgreSQL hautement disponible dans votre data centre, n'hésitez pas à nous contacter pour obtenir de l'aide.
Pré-requis
Il est recommandé d'utiliser une machine virtuelle équipée d'Ubuntu avec au moins 8 GB de mémoire RAM, avec Docker et Docker Compose préalablement installés.
Dans cet article, nous allons configurer un cluster de haute disponibilité pour PostgreSQL en utilisant les binaires de PostgreSQL 12, PATRONI, HAProxy et un client PostgreSQL.
Pour docker, nous allons utiliser le sous-réseau 172.18.56.0/24. les adresses IP suivantes seront utilisées:
- postgres1: 172.18.56.41 => Services: PostgreSQL, patroni, etcd, pgbouncer
- postgres2: 172.18.56.42 => Services: PostgreSQL, patroni, etcd, pgbouncer
- postgres3: 172.18.56.43 => Services: PostgreSQL, patroni, etcd, pgbouncer
- haproxy1: 172.18.56.50 => Services: HAProxy
Création et configuration des conteneurs docker
1. Créer un répertoire de travail /app/postgres.
root@data-resilience:/app# mkdir -p /app/postgres root@data-resilience:/app# cd /app/postgres/
2. Créer un Dockerfile avec le contenu suivant:
root@data-resilience:/app/postgres# cat Dockerfile FROM ubuntu:20.04 LABEL maintainer="data-resilience" LABEL version="1" LABEL description="Image docker pour postgresql 12" ARG DEBIAN_FRONTEND=noninteractive RUN apt update RUN apt install -y vim wget ca-certificates curl gnupg lsb-release dnsutils systemd systemctl RUN apt clean
3. Construire l'image docker postgres12-img avec la commande suivante:
docker build -t postgres12-img .
4. Vérifier éventuellement que l'image a bien été créée.
root@data-resilience:/app/postgres# docker images REPOSITORY TAG IMAGE ID CREATED SIZE postgres12-img latest a3117b8d00fb 34 seconds ago 169MB <--- elle est bien là! couchbase/server enterprise-7.0.5 1a64a8235967 4 weeks ago 1.33GB ubuntu 20.04 d5447fc01ae6 5 weeks ago 72.8MB
5. Lancer un conteneur à partir de notre image Docker.
root@data-resilience:/app/postgres# docker run -tid --name postgres12-server postgres12-img fcc80f0d88e7f5000f0e6348acd8413105043f243716a8234e41ca0573379b32 root@data-resilience:/app/postgres#
6. Se connecter au conteneur postgres12-server avec la commande suivante:
docker exec -ti postgres12-server sh
7. Ajouter le dépôt de PostgreSQL.
# curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg # sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" >> /etc/apt/sources.list.d/pgdg.list' # apt update Hit:1 http://archive.ubuntu.com/ubuntu focal InRelease Get:2 http://archive.ubuntu.com/ubuntu focal-updates InRelease [114 kB] Get:3 http://security.ubuntu.com/ubuntu focal-security InRelease [114 kB] Get:4 http://apt.postgresql.org/pub/repos/apt focal-pgdg InRelease [91.6 kB] Get:5 http://archive.ubuntu.com/ubuntu focal-backports InRelease [108 kB] Get:6 http://apt.postgresql.org/pub/repos/apt focal-pgdg/main amd64 Packages [419 kB] Fetched 846 kB in 1s (1152 kB/s) Reading package lists... Done Building dependency tree Reading state information... Done All packages are up to date.
8. Installer postgreSQL 12 en exécutant les commandes suivantes:
apt -y install postgresql-12 ln -s /usr/lib/postgresql/12/bin/* /usr/sbin/ rm -rf /var/lib/postgresql/12/main/*
9. Installer etcd avec la commande suivante:
apt install -y etcd
10. Installer patroni en exécutant les commandes suivantes:
apt -y install python3 python3-pip python3-dev libpq-dev python3-etcd ln -s /usr/bin/python3 /usr/bin/python pip3 install launchpadlib --upgrade setuptools psycopg2 apt install -y patroni
11. Installer pgbouncer.
apt install -y pgbouncer
12. Depuis le conteneur Docker, utiliser la commande echo pour ajouter les variables d'environnement de Patroni dans le fichier /etc/environment.
# echo 'PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin" export PGDATA="/var/lib/postgresql/12/main" export ETCDCTL_API="3" export PATRONI_ETCD_URL="http://127.0.0.1:2379" export PATRONI_SCOP> > > > E="pg_cluster" postgres1=172.18.56.41 postgres2=172.18.56.42 postgres3=172.18.56.43 ENDPOINTS=$postgres1:2379,$postgres2:2379,$postgres3:2379 ' > /etc/environment
13. Nous allons utiliser /etc/hosts pour la résolution des noms des machines. Ajouter les lignes suivantes dans /etc/hosts:
172.18.56.41 postgres1 172.18.56.42 postgres2 172.18.56.43 postgres3
14. Créez une nouvelle image Docker nommée postgres12-img à partir du conteneur postgres12-server.
root@data-resilience:/app/postgres# docker commit postgres12-server postgres12-img sha256:4039441833b32be1f9b7b6dd4a889066ecbcf7bf6508ebbdedb5c193dfbec29b root@data-resilience:/app/postgres#
15. Supprimer le conteneur postgres12-server car il n'est plus nécessaire pour la suite de l'article.
root@data-resilience:/app/postgres# docker rm -f postgres12-server postgres12-server root@data-resilience:/app/postgres#
16. Créer un fichier docker-compose.yml avec le contenu suivant:
root@data-resilience:/app/postgres# cat docker-compose.yml version: '3.5' services: postgres1: tty: true hostname: postgres1 container_name: postgres1 image: postgres12-img environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres volumes: - type: volume source: pg_data1 target: /var/lib/postgresql/data networks: dbNetwork: ipv4_address: 172.18.56.41 ports: - '5432:5432' postgres2: tty: true hostname: postgres2 container_name: postgres2 image: postgres12-img environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres volumes: - type: volume source: pg_data2 target: /var/lib/postgresql/data networks: dbNetwork: ipv4_address: 172.18.56.42 ports: - '5433:5432' postgres3: tty: true hostname: postgres3 container_name: postgres3 image: postgres12-img environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres volumes: - type: volume source: pg_data3 target: /var/lib/postgresql/data networks: dbNetwork: ipv4_address: 172.18.56.43 ports: - '5434:5432' networks: dbNetwork: name: dbNetwork driver: bridge ipam: config: - subnet: 172.18.56.0/24 volumes: pg_data1: pg_data2: pg_data3:
17. Lancer docker-compose avec la commande suivante:
root@data-resilience:/app/postgres# docker-compose up -d Creating volume "postgres_pg_data1" with default driver Creating volume "postgres_pg_data2" with default driver Creating volume "postgres_pg_data3" with default driver Creating postgres2 ... done Creating postgres3 ... done Creating postgres1 ... done
18. Vérifier que les 3 nœuds sont en cours d'exécution.
root@data-resilience:/app/postgres# docker-compose ps Name Command State Ports ---------------------------------------------------------------------- postgres1 bash Up 0.0.0.0:5432->5432/tcp,:::5432->5432/tcp postgres2 bash Up 0.0.0.0:5433->5432/tcp,:::5433->5432/tcp postgres3 bash Up 0.0.0.0:5434->5432/tcp,:::5434->5432/tcp
Configuration du service etcd
Etcd est un magasin de clés-valeurs distribué qui fournit un moyen fiable de stocker les données qui doivent être accessibles par un système distribué ou un groupe de machines. Dans notre cas, il va servir à stocker les données de configuration du cluster patroni. La configuration d'etcd est gérée dans le fichier /etc/default/etcd.
19. Se connecter au serveur postgres1, puis lancer la commande echo pour ajouter les varables de configuration etcd dans le fichier /etc/default/etcd.
root@data-resilience:/app/postgres# docker exec -ti postgres1 sh #echo 'ETCD_NAME=postgres1 ETCD_DATA_DIR="/var/lib/etcd/postgres1" ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379" ETCD_INITIAL_ADVERTISE_PEER_URLS="http://172.18.56.41:2380" ETCD_INITIAL_CLUSTER="postgres1=http://172.18.56.41:2380,postgres2=http://172.18.56.42:2380,postgres3=http://172.18.56.43:2380" ETCD_INITIAL_CLUSTER_STATE="new" ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster" ETCD_ADVERTISE_CLIENT_URLS="http://0.0.0.0:2379" ETCD_ENABLE_V2="true" ETCD_LISTEN_PEER_URLS="http://0.0.0.0:2380"' >> /etc/default/etcd
20. Se connecter au serveur postgres2, puis lancer la commande echo pour ajouter les varables de configuration etcd dans le fichier /etc/default/etcd.
root@data-resilience:/app/postgres# docker exec -ti postgres2 sh #echo 'ETCD_NAME=postgres2 ETCD_DATA_DIR="/var/lib/etcd/postgres2" ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379" ETCD_INITIAL_ADVERTISE_PEER_URLS="http://172.18.56.42:2380" ETCD_INITIAL_CLUSTER="postgres1=http://172.18.56.41:2380,postgres2=http://172.18.56.42:2380,postgres3=http://172.18.56.43:2380" ETCD_INITIAL_CLUSTER_STATE="new" ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster" ETCD_ADVERTISE_CLIENT_URLS="http://0.0.0.0:2379" ETCD_ENABLE_V2="true" ETCD_LISTEN_PEER_URLS="http://0.0.0.0:2380"' >> /etc/default/etcd
21. Se connecter au serveur postgres3, puis lancer la commande echo pour ajouter les varables de configuration etcd dans le fichier /etc/default/etcd.
root@data-resilience:/app/postgres# docker exec -ti postgres3 sh #echo 'ETCD_NAME=postgres3 ETCD_DATA_DIR="/var/lib/etcd/postgres3" ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379" ETCD_INITIAL_ADVERTISE_PEER_URLS="http://172.18.56.43:2380" ETCD_INITIAL_CLUSTER="postgres1=http://172.18.56.41:2380,postgres2=http://172.18.56.42:2380,postgres3=http://172.18.56.43:2380" ETCD_INITIAL_CLUSTER_STATE="new" ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster" ETCD_ADVERTISE_CLIENT_URLS="http://0.0.0.0:2379" ETCD_ENABLE_V2="true" ETCD_LISTEN_PEER_URLS="http://0.0.0.0:2380"' >> /etc/default/etcd
22. Démarrer le service etcd sur les 3 serveurs.
root@data-resilience:/app/postgres# docker exec -ti postgres1 sh -c "service etcd start" * Starting etcd... [ OK ] root@data-resilience:/app/postgres# docker exec -ti postgres2 sh -c "service etcd start" * Starting etcd... [ OK ] root@data-resilience:/app/postgres# docker exec -ti postgres3 sh -c "service etcd start" * Starting etcd... [ OK ]
23. Vérifier l'état du cluster.
root@data-resilience:/app/postgres# docker exec -ti postgres1 etcdctl cluster-health member 1d9b124e367f093a is healthy: got healthy result from http://0.0.0.0:2379 member dccc2d987992f32f is healthy: got healthy result from http://0.0.0.0:2379 member e08d29b27edb2e19 is healthy: got healthy result from http://0.0.0.0:2379 cluster is healthy [ OK ]
24. List les membres du cluster.
root@data-resilience:/app/postgres# docker exec -ti postgres1 etcdctl member list 1d9b124e367f093a: name=postgres3 peerURLs=http://172.18.56.43:2380 clientURLs=http://0.0.0.0:2379 isLeader=false dccc2d987992f32f: name=postgres1 peerURLs=http://172.18.56.41:2380 clientURLs=http://0.0.0.0:2379 isLeader=false e08d29b27edb2e19: name=postgres2 peerURLs=http://172.18.56.42:2380 clientURLs=http://0.0.0.0:2379 isLeader=true root@data-resilience:/app/postgres# docker exec -it postgres1 bash -c 'source /etc/environment ; etcdctl endpoint status --write-out=table --endpoints=$ENDPOINTS' +-------------------+------------------+---------+---------+-----------+-----------+------------+ | ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX | +-------------------+------------------+---------+---------+-----------+-----------+------------+ | 172.18.56.41:2379 | dccc2d987992f32f | 3.2.26 | 25 kB | false | 4497 | 34 | | 172.18.56.42:2379 | e08d29b27edb2e19 | 3.2.26 | 25 kB | true | 4497 | 34 | | 172.18.56.43:2379 | 1d9b124e367f093a | 3.2.26 | 25 kB | false | 4497 | 34 | +-------------------+------------------+---------+---------+-----------+-----------+------------+ [ OK ]
Configuration du service patroni
25. Ajouter les lignes suivantes dans le fichier /etc/patroni/postgres.yml du serveur postgres1:
scope: pg_cluster namespace: /service/ name: postgres1 restapi: listen: postgres1:8008 connect_address: postgres1:8008 etcd: hosts: postgres1:2379,postgres2:2379,postgres3:2379 bootstrap: dcs: ttl: 30 loop_wait: 10 retry_timeout: 10 maximum_lag_on_failover: 1048576 postgresql: use_pg_rewind: true use_slots: true parameters: initdb: - encoding: UTF8 - data-checksums pg_hba: - host replication replicator 127.0.0.1/32 md5 - host replication replicator 0.0.0.0/0 md5 - host all all 0.0.0.0/0 md5 users: admin: password: admin options: - createrole - createdb postgresql: listen: postgres1:5432 connect_address: postgres1:5432 proxy_address: postgres1:6432 data_dir: /var/lib/postgresql/12/main bin_dir: /usr/lib/postgresql/12/bin pgpass: /tmp/pgpass authentication: replication: username: replicator password: replicator superuser: username: postgres password: postgres rewind: username: rewind password: rewind tags: nofailover: false noloadbalance: false clonefrom: false nosync: false
26. Ajouter les lignes suivantes dans le fichier /etc/patroni/postgres.yml du serveur postgres2
scope: pg_cluster namespace: /service/ name: postgres2 restapi: listen: postgres2:8008 connect_address: postgres2:8008 etcd: hosts: postgres1:2379,postgres2:2379,postgres3:2379 bootstrap: dcs: ttl: 30 loop_wait: 10 retry_timeout: 10 maximum_lag_on_failover: 1048576 postgresql: use_pg_rewind: true use_slots: true parameters: initdb: - encoding: UTF8 - data-checksums pg_hba: - host replication replicator 127.0.0.1/32 md5 - host replication replicator 0.0.0.0/0 md5 - host all all 0.0.0.0/0 md5 users: admin: password: admin options: - createrole - createdb postgresql: listen: postgres2:5432 connect_address: postgres2:5432 proxy_address: postgres2:6432 data_dir: /var/lib/postgresql/12/main bin_dir: /usr/lib/postgresql/12/bin pgpass: /tmp/pgpass authentication: replication: username: replicator password: replicator superuser: username: postgres password: postgres rewind: username: rewind password: rewind tags: nofailover: false noloadbalance: false clonefrom: false nosync: false
27. Ajouter les lignes suivantes dans le fichier /etc/patroni/postgres.yml du serveur postgres3
scope: pg_cluster namespace: /service/ name: postgres3 restapi: listen: postgres3:8008 connect_address: postgres3:8008 etcd: hosts: postgres1:2379,postgres2:2379,postgres3:2379 bootstrap: dcs: ttl: 30 loop_wait: 10 retry_timeout: 10 maximum_lag_on_failover: 1048576 postgresql: use_pg_rewind: true use_slots: true parameters: initdb: - encoding: UTF8 - data-checksums pg_hba: - host replication replicator 127.0.0.1/32 md5 - host replication replicator 0.0.0.0/0 md5 - host all all 0.0.0.0/0 md5 users: admin: password: admin options: - createrole - createdb postgresql: listen: postgres3:5432 connect_address: postgres3:5432 proxy_address: postgres3:6432 data_dir: /var/lib/postgresql/12/main bin_dir: /usr/lib/postgresql/12/bin pgpass: /tmp/pgpass authentication: replication: username: replicator password: replicator superuser: username: postgres password: postgres rewind: username: rewind password: rewind tags: nofailover: false noloadbalance: false clonefrom: false nosync: false
28. Démarrer le service patroni sur le serveur postgres1 en premier, puis sur les autres nœuds.
root@data-resilience:/app/postgres# docker exec -ti postgres1 sh -c "service patroni start" * Starting Patroni
29. Vérifier l'état du service patroni.
root@data-resilience:/app/postgres# docker exec -ti postgres1 sh -c "service patroni status" * Patroni is running
30. Consulter le fichier de log /var/log/patroni.log pour vérifier qu'il n'y a pas d'erreurs et que le serveur postgres1 est primaire.
root@data-resilience:/app/postgres# docker exec -ti postgres1 sh -c "tail -f /var/log/patroni.log" 2023-01-18 22:13:33,042 INFO: no action. I am (postgres1), the leader with the lock 2023-01-18 22:13:43,045 INFO: no action. I am (postgres1), the leader with the lock 2023-01-18 22:13:53,056 INFO: no action. I am (postgres1), the leader with the lock 2023-01-18 22:14:03,040 INFO: no action. I am (postgres1), the leader with the lock 2023-01-18 22:14:13,060 INFO: no action. I am (postgres1), the leader with the lock
31. Démarrer le service patroni sur les autres serveurs postgres2 et postgres3.
root@data-resilience:/app/postgres# docker exec -ti postgres2 sh -c "service patroni start" * Creating logfile for Patroni... * Starting Patroni root@data-resilience:/app/postgres# docker exec -ti postgres3 sh -c "service patroni start" * Creating logfile for Patroni... * Starting Patroni
32. Consulter le fichier de log /var/log/patroni.log sur postgres2 pour vérifier qu'il n'y a pas d'erreurs et que le serveur est secondaire.
root@data-resilience:/app/postgres# docker exec -ti postgres2 sh -c "tail -f /var/log/patroni.log" 2023-01-18 22:16:43,054 INFO: no action. I am (postgres2), a secondary, and following a leader (postgres1) 2023-01-18 22:16:47.346 CET [107] LOG: started streaming WAL from primary at 0/3000000 on timeline 1 2023-01-18 22:16:53,062 INFO: no action. I am (postgres2), a secondary, and following a leader (postgres1) 2023-01-18 22:17:03,094 INFO: no action. I am (postgres2), a secondary, and following a leader (postgres1) 2023-01-18 22:17:13,154 INFO: no action. I am (postgres2), a secondary, and following a leader (postgres1)
33. Consulter le fichier de log /var/log/patroni.log sur postgres3 pour vérifier qu'il n'y a pas d'erreurs et que le serveur est secondaire.
root@data-resilience:/app/postgres# docker exec -ti postgres3 sh -c "tail -f /var/log/patroni.log" 2023-01-18 22:17:18,334 INFO: establishing a new patroni connection to the postgres cluster 2023-01-18 22:17:18,352 INFO: no action. I am (postgres3), a secondary, and following a leader (postgres1) 2023-01-18 22:17:23,078 INFO: no action. I am (postgres3), a secondary, and following a leader (postgres1) 2023-01-18 22:17:33,055 INFO: no action. I am (postgres3), a secondary, and following a leader (postgres1)
34. Exécuter la commande suivante pour lister les membres du cluster patroni:
root@data-resilience:/app/postgres# docker exec -it postgres1 bash -c 'source /etc/environment ; patronictl list' + Cluster: pg_cluster (7190103337723752656) +----+-----------+ | Member | Host | Role | State | TL | Lag in MB | +-----------+-----------+---------+---------+----+-----------+ | postgres1 | postgres1 | Leader | running | 1 | | | postgres2 | postgres2 | Replica | running | 1 | 0 | | postgres3 | postgres3 | Replica | running | 1 | 0 | +-----------+-----------+---------+---------+----+-----------+
Configuration pgbouncer
Pgbouncer est un logiciel open-source qui agit comme un proxy de base de données pour PostgreSQL. Il permet de réduire le nombre de connexions physiques à la base de données en utilisant un pool de connexions. Cela permet d'améliorer les performances et de réduire la charge sur la base de données en utilisant moins de ressources système. Il est également utilisé pour la sécurité et aussi il acte comme répartiteur de charge (load balancer).
35. Editer le fichier /etc/pgbouncer/pgbouncer.ini du serveur postgres1 puis ajouter la ligne suivante à la fin de la section [databases]:
* = host=postgres1 port=5432 dbname=postgres
36. Editer le fichier /etc/pgbouncer/pgbouncer.ini du serveur postgres2 puis ajouter la ligne suivante à la fin de la section [databases]:
* = host=postgres2 port=5432 dbname=postgres
37. Editer le fichier /etc/pgbouncer/pgbouncer.ini du serveur postgres3 puis ajouter la ligne suivante à la fin de la section [databases]:
* = host=postgres3 port=5432 dbname=postgres
38. Sur les 3 serveurs, éditer le fichier /etc/pgbouncer/pgbouncer.ini puis modifier la ligne listen_addr = localhost par listen_addr = *.
#listen_addr = localhost listen_addr = *
39. Sur les 3 serveurs, éditer le fichier /etc/pgbouncer/pgbouncer.ini puis ajouter les lignes suivantes dans section [pgbouncer] après "auth_file = /etc/pgbouncer/userlist.txt".
auth_user = pgbouncer auth_query = SELECT p_user, p_password FROM public.lookup($1)
40. Se connecter au serveur postgreSQL primaire (postgres1).
root@data-resilience:/app/postgres# docker exec -ti postgres1 bash -c "psql -h postgres1 -p 5432 -U postgres" Password for user postgres: psql (12.13 (Ubuntu 12.13-1.pgdg20.04+1)) Type "help" for help. postgres=#
41. Créer le rôle pgbouncer en exécutant la commande suivante:
postgres=# CREATE ROLE pgbouncer with LOGIN encrypted password 'pgbouncer'; CREATE ROLE
42. Créer la fonction lookup pour récupérer le mot de passe du rôle pgbouncer depuis la base de données.
postgres=# CREATE FUNCTION public.lookup ( postgres(# INOUT p_user name, postgres(# OUT p_password text postgres(# ) RETURNS record postgres-# LANGUAGE sql SECURITY DEFINER SET search_path = pg_catalog AS postgres-# $$SELECT usename, passwd FROM pg_shadow WHERE usename = p_user$$; CREATE FUNCTION
43. Récupérer le mot de passe crypté du rôle pgbouncer avec la requête suivante:
postgres=# select * from pg_shadow;
usename | usesysid | usecreatedb | usesuper | userepl | usebypassrls | passwd | valuntil | useconfig
------------+----------+-------------+----------+---------+--------------+-------------------------------------+----------+-----------
postgres | 10 | t | t | t | t | md53175bce1d3201d16594cebf9d7eb3f9d | |
replicator | 16384 | f | f | t | f | md55577127b7ffb431f05a1dcd318438d11 | |
rewind | 16385 | f | f | f | f | md5856a52b2f53683332cc7ec84489ac63f | |
admin | 16386 | t | f | f | f | md5f6fdffe48c908deb0f4c3bd36c032e72 | |
pgbouncer | 16387 | f | f | f | f | md5be5544d3807b54dd0637f2439ecb03b9 | |
(5 rows)
44. Ajouter le mot de passe crypté dans le fichier /etc/pgbouncer/userlist.txt.
root@data-resilience:/app/postgres# docker exec -ti postgres1 bash -c "cat /etc/pgbouncer/userlist.txt" "pgbouncer" "md5be5544d3807b54dd0637f2439ecb03b9"
45. Démarrer le service pgbouncer sur les 3 serveurs.
root@data-resilience:/app/postgres# docker exec -it postgres1 bash -c "service pgbouncer start" * Starting PgBouncer pgbouncer root@data-resilience:/app/postgres# docker exec -it postgres2 bash -c "service pgbouncer start" * Starting PgBouncer pgbouncer root@data-resilience:/app/postgres# docker exec -it postgres3 bash -c "service pgbouncer start" * Starting PgBouncer pgbouncer
46. Tester l'authentification avec pgbouncer en se connectant à la base de données sur le port 6432. En effet, pgbouncer utilise par défaut le port 6432.
root@data-resilience:/app/postgres# docker exec -ti postgres1 bash -c "psql -h postgres1 -p 6432 -U postgres" Password for user postgres: psql (12.13 (Ubuntu 12.13-1.pgdg20.04+1)) Type "help" for help. postgres=# \q
Installation HAProxy
47. Ajouter le service haproxy dans le ficher docker-compose.yml comme indiqué ci-dessous.
haproxy1: tty: true hostname: haproxy1 container_name: haproxy1 image: ubuntu:20.04 networks: dbNetwork: ipv4_address: 172.18.56.50 ports: - '5000:5000' - '5001:5001' - '7000:7000'
48. Lancer le conteneur haproxy1.
root@data-resilience:/app/postgres# docker-compose up -d haproxy1 Creating haproxy1 ... done
49. Exécuter les commandes suivantes pour installer HAProxy:
apt update apt install -y haproxy apt install -y vim
50. Ajouter les lignes suivantes dans le fichier /etc/haproxy/haproxy.cfg pour configurer le HAProxy. Nous avons inclus tous les trois nœuds de base de données dans les sections primaires et de réplication: c'est parce que chaque nœud de base de données est un candidat potentiel pour être soit primaire ou réplique.
global log 127.0.0.1 local2 chroot /var/lib/haproxy stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners stats timeout 30s user haproxy group haproxy maxconn 100 daemon defaults mode tcp log global option tcplog retries 3 timeout queue 1m timeout connect 10s timeout client 1m timeout server 1m timeout check 10s maxconn 100 listen stats mode http bind *:7000 stats enable stats uri / listen primary bind *:5000 option httpchk OPTIONS /master http-check expect status 200 default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions server postgres1 172.18.56.41:6432 maxconn 100 check port 8008 server postgres2 172.18.56.42:6432 maxconn 100 check port 8008 server postgres3 172.18.56.43:6432 maxconn 100 check port 8008 listen replicas bind *:5001 balance roundrobin option httpchk OPTIONS /replica http-check expect status 200 default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions server postgres1 172.18.56.41:6432 maxconn 100 check port 8008 server postgres2 172.18.56.42:6432 maxconn 100 check port 8008 server postgres3 172.18.56.43:6432 maxconn 100 check port 8008
51. Démarrer le service HAProxy avec la commande suivante:
root@data-resilience:/app/postgres# docker exec -ti haproxy1 sh -c "service haproxy start" * Starting haproxy haproxy
52. Vérifier le statut de HAProxy.
root@data-resilience:/app/postgres# docker exec -ti haproxy1 sh -c "service haproxy status" haproxy is running.
53. Vérifier que le HAProxy est bien configuré en se rendant sur l'URL http://172.18.56.50:7000/.
Installation du client postgreSQL
Nous allons utiliser un client postgreSQL pour effectuer nos tests sur le cluster.
54. Exécuter la commande suivante pour installer le client postgreSQL sur votre host ubuntu.
apt install -y postgresql-client python3-psycopg2
55. sauvegarder le mot de passe de postgres dans le fichier ~/.pgpass. Pour chaque mot de passe, il faut ajouter une ligne pour la connexion sur le port d'écriture 5000 et une autre ligne pour se connecter sur le port de lecture 5001 comme indiqué ci-dessous.
root@data-resilience:/app/postgres# echo "localhost:5000:*:postgres:postgres" >> ~/.pgpass root@data-resilience:/app/postgres# echo "localhost:5001:*:postgres:postgres" >> ~/.pgpass root@data-resilience:/app/postgres# chmod 0600 ~/.pgpass root@data-resilience:/app/postgres# cat ~/.pgpass localhost:5000:*:postgres:postgres localhost:5001:*:postgres:postgres
56. Tester les requêtes de lecture.
root@data-resilience:/app/postgres# psql -h localhost -p 5001 -U postgres -t -c "select inet_server_addr()" 172.18.56.42 root@data-resilience:/app/postgres# psql -h localhost -p 5001 -U postgres -t -c "select inet_server_addr()" 172.18.56.43 root@data-resilience:/app/postgres# psql -h localhost -p 5001 -U postgres -t -c "select inet_server_addr()" 172.18.56.42 root@data-resilience:/app/postgres# psql -h localhost -p 5001 -U postgres -t -c "select inet_server_addr()" 172.18.56.43 root@data-resilience:/app/postgres# psql -h localhost -p 5001 -U postgres -t -c "select inet_server_addr()" 172.18.56.42
57. Tester les requêtes d'écriture.
root@data-resilience:/app/postgres# psql -h localhost -p 5000 -U postgres -t -c "select inet_server_addr()" 172.18.56.41 root@data-resilience:/app/postgres# psql -h localhost -p 5000 -U postgres -t -c "select inet_server_addr()" 172.18.56.41 root@data-resilience:/app/postgres# psql -h localhost -p 5000 -U postgres -t -c "select inet_server_addr()" 172.18.56.41
Test PostgreSQL High Availability (HA) Cluster Replication
Nous allons créer une base de donner pour effectuer nos tests.
58. Créer le répertoire /opt/data pour les datafiles de la base de données sur les 3 serveurs.
root@data-resilience:~# docker exec -it postgres1 bash -c "mkdir -p /opt/data" root@data-resilience:~# docker exec -it postgres1 bash -c "chown -R postgres:postgres /opt/data" root@data-resilience:~# docker exec -it postgres2 bash -c "mkdir -p /opt/data" root@data-resilience:~# docker exec -it postgres2 bash -c "chown -R postgres:postgres /opt/data" root@data-resilience:~# docker exec -it postgres3 bash -c "mkdir -p /opt/data" root@data-resilience:~# docker exec -it postgres3 bash -c "chown -R postgres:postgres /opt/data"
59. Créer le tablespace tbs_data.
root@data-resilience:~# psql -h localhost -p 5000 -U postgres -c "CREATE TABLESPACE tbs_data OWNER postgres LOCATION '/opt/data'" CREATE TABLESPACE
60. Créer la base dataresilience.
root@data-resilience:~# psql -h localhost -p 5000 -U postgres -c "create database dataresilience TABLESPACE tbs_data" CREATE DATABASE
61. Créer le schémat test.
root@data-resilience:~# psql -h localhost -p 5000 -U postgres -d dataresilience -c "create schema test" CREATE SCHEMA
62. Créer la table log suivante. A noter qu'ici, on s'interesse principalement à la colonne host_addr qui sera utilisée pour indiquer sur que noeud de cluster, la ligne a été ajoutée.
root@data-resilience:~# psql -h localhost -p 5000 -U postgres -d dataresilience -c "create table test.log(date timestamp, host_addr character varying(15), message character varying(30))" CREATE TABLE
63. Créer un utilisateur avec des droits sur la base dataresilience.
root@data-resilience:~# psql -h localhost -p 5000 -U postgres -d dataresilience -c "create user rachid login password 'rachid'" CREATE ROLE root@data-resilience:~# psql -h localhost -p 5000 -U postgres -d dataresilience -c "grant usage on schema test to rachid;" GRANT root@data-resilience:~# psql -h localhost -p 5000 -U postgres -d dataresilience -c "grant insert, update, delete, select on test.log to rachid;" GRANT
64. Ajouter le compte précédement créé dans le fichier ~/.pgpass.
root@data-resilience:~# echo "localhost:5000:*:rachid:rachid" >> ~/.pgpass root@data-resilience:~# echo "localhost:5001:*:rachid:rachid" >> ~/.pgpass
65. Notre premier test consiste à insérer une ligne dans la table log puis de lancer une requête SQL pour afficher son contenu. Dans ce premier test, tous les 3 noeuds sont up.
root@data-resilience:~# psql -h localhost -p 5000 -U rachid -d dataresilience -c "INSERT INTO test.log (date, host_addr, message) VALUES (current_date, inet_server_addr(), 'tous les serveurs sont up')" INSERT 0 1 root@data-resilience:~# psql -h localhost -p 5001 -U rachid -d dataresilience -c "select date, host_addr as insert_from_host, message, inet_server_addr select_from_host from test.log, inet_server_addr();" date | insert_from_host | message | select_from_host ---------------------+------------------+---------------------------+------------------ 2023-01-25 00:00:00 | 172.18.56.41/32 | tous les serveurs sont up | 172.18.56.43 (1 row)
Le constat est clair, la ligne insérée est envoyée au serveur principal, mais la requête de lecture récupère les données à partir du serveur de réplication, dans ce cas, le serveur Postgres3.
66. Nous allons ici insérer une nouvelle ligne puis afficher une nouvelle fois le contenu de la table log.
root@data-resilience:~# psql -h localhost -p 5000 -U rachid -d dataresilience -c "INSERT INTO test.log (date, host_addr, message) VALUES (current_date, inet_server_addr(), 'tous les serveurs sont up')" INSERT 0 1 root@data-resilience:~# psql -h localhost -p 5001 -U rachid -d dataresilience -c "select date, host_addr as insert_from_host, message, inet_server_addr select_from_host from test.log, inet_server_addr();" date | insert_from_host | message | select_from_host ---------------------+------------------+---------------------------+------------------ 2023-01-25 00:00:00 | 172.18.56.41/32 | tous les serveurs sont up | 172.18.56.42 2023-01-25 00:00:00 | 172.18.56.41/32 | tous les serveurs sont up | 172.18.56.42 (2 rows)
Comme prévu, l'écriture est envoyée au serveur primaire postgres1. Notre requête de sélection récupère les données cette fois-ci depuis l'autre serveur de réplication postgres2. En exécutant plusieurs fois la requête de lecture, on constate que le load balancing fonctionne très bien.
67. Pour notre dernier test, nous allons simuler la perte du nœud primaire postgres1 en arrêtant simplement le service etcd.
root@data-resilience:/app/postgres# docker exec -ti postgres1 sh -c "service etcd stop" * Stopping etcd... [ OK ] root@data-resilience:/app/postgres# docker exec -ti postgres1 sh -c "service etcd status" * etcd is not running root@data-resilience:/app/postgres# docker exec -it postgres2 bash -c 'source /etc/environment ; patronictl list' + Cluster: pg_cluster (7191529948583444670) +----+-----------+ | Member | Host | Role | State | TL | Lag in MB | +-----------+-----------+---------+---------+----+-----------+ | postgres2 | postgres2 | Leader | running | 5 | | | postgres3 | postgres3 | Replica | running | 5 | 0 | +-----------+-----------+---------+---------+----+-----------+
Le cluster a élu un nouveau leader qui est le serveur postgres2.
68. Comme avant, on insère une nouvelle ligne puis on affiche le contenu de la table log.
root@data-resilience:/app/postgres# psql -h localhost -p 5000 -U rachid -d dataresilience -c "INSERT INTO test.log (date, host_addr, message) VALUES (current_date, inet_server_addr(), 'tous les serveurs sont up')" INSERT 0 1 root@data-resilience:/app/postgres# psql -h localhost -p 5001 -U rachid -d dataresilience -c "select date, host_addr as insert_from_host, message, inet_server_addr select_from_host from test.log, inet_server_addr();" date | insert_from_host | message | select_from_host ---------------------+------------------+---------------------------+------------------ 2023-01-25 00:00:00 | 172.18.56.41/32 | tous les serveurs sont up | 172.18.56.43 2023-01-25 00:00:00 | 172.18.56.41/32 | tous les serveurs sont up | 172.18.56.43 2023-01-25 00:00:00 | 172.18.56.42/32 | tous les serveurs sont up | 172.18.56.43 (3 rows)
Cette fois, l'écriture est passée par le serveur postgres2 qui est devenu primaire suite à la défaillance du noeud postgres1. La haute disponibilité fonctionne parfaitement!