Quelques explications concernant Kubernetes
Pod : un ou plusieurs conteneurs applicatifsService : permet d’accéder durablement à un ou
plusieurs PodsDeployment, Secret,
Configmap, …Cluster, Database, Backup,
…Pod, Service, Secret, …
kubectl, …Tout ceci sera détaillé au fur et à mesure du module, n’ayez crainte !
Pods)Custom Resource Definitions (extension de l’API
Kubernetes)Pod CloudNativePG
ghcr.io/cloudnative-pg/cloudnative-pg:1.28.0Pod PostgreSQL
ghcr.io/cloudnative-pg/postgresqlminimal ou standard avec OS
18.1-minimal-trixie<-> État actuelappapp, streaming_replicapg_hbaSecrets : postgresql-app (contient le mot de passe du
rôle app)
kubectl describe secrets postgresql-appServices : postgresql-r, postgresql-ro,
postgresql-rwspec.bootstrap.initdb du fichier YAML du
Clusterinitdb est utiliséepostgresql.confpg_hba.confpg_ident.confALTER SYSTEM désactivé
allow_alter_system à falseprimaryUpdateStrategy)kubectlkubectl cnpg --helpCluster, une instance
spécifiquestatuspsqlpromotebackuplogsreloadrestart%h du paramètre log_line_prefixJSONPodlog_* non modifiablesinstances: N1 primaire et N-1 secondairesObjectStoreapiVersion: barmancloud.cnpg.io/v1
kind: ObjectStore
metadata:
name: scaleway-store
spec:
configuration:
destinationPath: "s3://<bucket>/<folder>/"
endpointURL: "https://s3.<region>.scw.cloud"
s3Credentials:
accessKeyId:
name: scaleway-api-secret
key: ACCESS_KEY_ID
secretAccessKey:
name: scaleway-api-secret
key: ACCESS_SECRET_KEY
region:
name: scaleway-api-secret
key: ACCESS_REGIONarchive_mode à on)archive_command :
/controller/manager wal-archive …isWALArchiver)Backup et
ScheduledBackupspec.backup d’un objet
Clusterspec.backup.volumeSnapshotStorageClassContainer Storage Interfacespec.bootstrap.recovery
barmanObjectStorevolumeSnapshotsImageCatalog ou
ClusterImageCatalogprimaryUpdateStrategy et
primaryUpdateMethod
Clusterpg_dump/pg_restore
microservice ou monolithpg_upgradeCustom Resource Definitionsinstance-managerCLUSTERS_ROLLOUT_DELAYINSTANCES_ROLLOUT_DELAYPod en conservant les volumescnpgpostmasterBut : Prendre en main le cluster Kubernetes.
Se connecter à la machine qui vous est dédiée.
ssh root@A.B.C.D
Trouver la version de l’utilitaire
kubectl.
kubectl version
kubectl version
Client Version: v1.34.3
Kustomize Version: v5.7.1
Server Version: v1.34.0
L’utilitaire kubectl vous permet d’interagir avec le
cluster Kubernetes déployé.
Lister les nœuds du cluster Kubernetes.
kubectl get nodes
NAME STATUS ROLES AGE VERSION
kind-control-plane Ready control-plane 135m v1.34.0
kind-worker Ready <none> 134m v1.34.0
kind-worker2 Ready <none> 134m v1.34.0
Cet utilitaire sait avec quel cluster Kubernetes interagir grâce au
fichier ~/.kube/config qui se trouve dans le répertoire de
votre utilisateur.
But : Installer l’opérateur dans le cluster Kubernetes ainsi que des modules complémentaires.
Il existe plusieurs méthodes pour installer l’opérateur : soit en
appliquant directement les fichiers YAML soit en utilisant le
Helm Chart fourni par le projet. Pour cet atelier, nous
utiliserons la première méthode, plus simple et rapide.
Installer la version 1.27.1 de l’opérateur avec la commande
kubectl apply -f:
kubectl apply --server-side -f \
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.27/releases/cnpg-1.27.1.yaml
namespace/cnpg-system serverside-applied
customresourcedefinition.apiextensions.k8s.io/backups.postgresql.cnpg.io serverside-applied
customresourcedefinition.apiextensions.k8s.io/clusterimagecatalogs.postgresql.cnpg.io serverside-applied
customresourcedefinition.apiextensions.k8s.io/clusters.postgresql.cnpg.io serverside-applied
customresourcedefinition.apiextensions.k8s.io/imagecatalogs.postgresql.cnpg.io serverside-applied
customresourcedefinition.apiextensions.k8s.io/poolers.postgresql.cnpg.io serverside-applied
customresourcedefinition.apiextensions.k8s.io/scheduledbackups.postgresql.cnpg.io serverside-applied
serviceaccount/cnpg-manager serverside-applied
clusterrole.rbac.authorization.k8s.io/cnpg-manager serverside-applied
clusterrolebinding.rbac.authorization.k8s.io/cnpg-manager-rolebinding serverside-applied
configmap/cnpg-default-monitoring serverside-applied
service/cnpg-webhook-service serverside-applied
deployment.apps/cnpg-controller-manager serverside-applied
mutatingwebhookconfiguration.admissionregistration.k8s.io/cnpg-mutating-webhook-configuration serverside-applied
validatingwebhookconfiguration.admissionregistration.k8s.io/cnpg-validating-webhook-configuration serverside-applied
Les fichiers seront récupérés depuis internet et appliqués sur votre
cluster Kubernetes. Pour rappel, l’outil kubectl sait avec
quel cluster Kubernetes interagir grâce au fichier
kubeconfig.
Ces fichiers là contiennent la définition de différents ressources :
Namespace;CustomResourceDefinition pour les différentes
ressources que l’opérateur va gérer (Backup,
Cluster, …)ServiceAccount,
unClusterRoleBinding et surtout un déploiement du
controller CloudNativePG.Par défaut, le Controller, cerveau de l’opérateur, sera
déployé dans le Namespace cnpg-system, créé lors
de l’installation du l’opérateur. Ce controller n’est ni plus ni moins
qu’une application. On peut voir le controller avec la
commande kubectl get pods et en indiquant le bon
Namespace :
Lister les
Podsprésents dans lenamespacecnpg-system.
kubectl get pods -n cnpg-system
NAME READY STATUS RESTARTS AGE
cnpg-controller-manager-7fc549dc69-xq7gq 1/1 Running 0 11s
Retrouver la liste des nouvelles ressources Kubernetes disponibles grâce à l’opérateur CloudNativePG.
kubectl api-resources --api-group postgresql.cnpg.io
backups postgresql.cnpg.io/v1 true Backup
clusterimagecatalogs postgresql.cnpg.io/v1 false ClusterImageCatalog
clusters postgresql.cnpg.io/v1 true Cluster
imagecatalogs postgresql.cnpg.io/v1 true ImageCatalog
poolers postgresql.cnpg.io/v1 true Pooler
scheduledbackups postgresql.cnpg.io/v1 true ScheduledBackup
But : Déployer un cluster PostgreSQL mono-instance, s’y connecter et suivre les traces de l’opérateur et de l’instance.
Voici un exemple de fichier YAML très simple qui permet de déployer une instance PostgreSQL en version 17.0 avec 5 Go de volume associés au
PGDATAet 5 autres aux journaux de transactions.
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: postgresql-demo
spec:
imageName: ghcr.io/cloudnative-pg/postgresql:17.5-standard-bookworm
instances: 1
storage:
size: 5Gi
walStorage:
size: 5Gi
affinity:
enablePodAntiAffinity: true
topologyKey: kubernetes.io/hostname
podAntiAffinityType: preferred
resources:
requests:
memory: "256Mi"
cpu: "0.5"
limits:
memory: "512Mi"
cpu: "1"Quelques informations supplémentaires sur le contenu de ce fichier :
apiVersion : La version de l’API de Kubernetes est
utilisée ;kind : Le type d’objet créé ;metadata : Des informations pour identifier l’objet
;spec : La définition de l’objet en question (“l’état
désiré”) ;imageName : Le nom de l’image utilisée ;instances : Le nombre d’instances voulues (sera
toujours 1 primaire + le reste en secondaire(s) ;)storage : Les informations sur le stockage souhaité
pour le PGDATA ;walStorage : Les informations sur le stockage souhaité
pour les WALs ;affinity : Indique où et comment seront déployées les
instances de ce Cluster ;resources : L’indication de requests et
limits sur la RAM et CPU.Créer le fichier
postgresql-demo.yamldans lehome directorydedaliboet copier le contenu YAML ci-dessus :
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: postgresql-demo
spec:
imageName: ghcr.io/cloudnative-pg/postgresql:17.5-standard-bookworm
instances: 1
storage:
size: 5Gi
walStorage:
size: 5Gi
affinity:
enablePodAntiAffinity: true
topologyKey: kubernetes.io/hostname
podAntiAffinityType: preferred
resources:
requests:
memory: "256Mi"
cpu: "0.5"
limits:
memory: "512Mi"
cpu: "1"
Dans un autre terminal sur la VM, suivre les traces du
controlleravec la commandekubectl logs -f -n cnpg-system <POD>et l’utilitairejq. Pour retrouver le nom duPoddu controlleur, vous pouvez utiliserkubectl get pod -A.
kubectl logs -f -n cnpg-system cnpg-controller-manager-7fc549dc69-8v8xw | jq
Retourner dans l’ancienne session SSH et créer l’instance PostgreSQL à partir du fichier
~/postgresql-demo.yamlaveckubectl. En parallèle regarder ce qu’il se passe dans les traces ducontroller:
kubectl apply -f ~/postgresql-demo.yaml
cluster.postgresql.cnpg.io/postgresql-demo created
Une instance PostgreSQL est désormais en train d’être déployée par l’opérateur. Vous avez décrit ce que vous souhaitiez avoir, l’opérateur fait le reste.
Plusieurs choses se passent lorsque vous appliquez ce fichier avec
kubectl. Tout d’abord l’opérateur va déployer un premier
Pod appelé
<clusterName>-1-initdb-<random>.
initdb devrait vous faire penser à la la commande à
exécuter lorsque vous devez créer une instance manuellement par
exemple.
kubectl get pods --watch
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
postgresql-demo-1-initdb-5pndc 0/1 Pending 0 2s <none> <none> <none> <none>
Ce Pod là se repose sur une image qui doit être
téléchargée. C’est pour cela que vous devez avoir autorisé l’accès vers
internet (ou votre dépôt local d’images) à votre cluster. Lorsque
celle-ci est récupérée, le Pod est “amorcé” et les
conteneurs d’initialisation sont déployés, comme on peut le voir avec
cette seconde remontée. Ici il existe un conteneur d’initialisation mais
aucun n’est terminé.
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
postgresql-demo-1-initdb-5pndc 0/1 Init:0/1 0 13s <none> k8s-demo <none> <none>
Au fur et à mesure, le Pod passe par d’autres états
…
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
postgresql-demo-1-initdb-5pndc 0/1 PodInitializing 0 24s 10.244.228.68 k8s-demo <none> <none>
… jusqu’à l’état Running. À cette étape-ci, le
Pod va notamment initialiser l’instance avec la création de
l’arborescence du PGDATA.
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
postgresql-demo-1-initdb-5pndc 1/1 Running 0 35s 10.244.228.68 k8s-demo <none> <none>
Enfin, lorsque cette étape est terminée, l’opérateur CloudNativePG
déploie un autre Pod qui cette fois-ci ne porte plus le mot
initdb. Un numéro est ajouté à la fin du nom. Une adresse
IP est attribuée à ce Pod (IP privée RFC 1918) :
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
postgresql-demo-1 1/1 Running 0 10m 10.244.228.69 k8s-demo <none> <none>
Votre Pod est prêt et donc votre instance aussi !
Se connecter à l’instance et vérifier la version de celle-ci. Vous pouvez utiliser
kubectl exec […]comme ceci :
kubectl exec -it postgresql-demo-1 -c postgres -- psql
ou, via le plugin :
kubectl cnpg psql postgresql-demo
psql (17.5 (Debian 17.5-1.pgdg120+1))
Type "help" for help.
postgres=# select version()\gx
-[ RECORD 1 ]----------------------------------------------------------------------------------------------------------------
version | PostgreSQL 17.5 (Debian 17.5-1.pgdg120+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
La commande kubectl exec -it permet d’exécuter un
programme au sein du Pod. L’outil psql étant
présent dans l’image, cela est possible. Essayez avec vim,
qui lui n’est pas présent dans l’image, un message d’erreur
apparaîtra.
Si besoin, pour quitter psql, vous pouvez utiliser
control+d, \q ou exit.
postgres=# \q
Suivre les traces de l’instance avec la commande
kubectl logs -f postgresql-demo-1.
kubectl logs -f postgresql-demo-1
{"level":"info","ts":"2025-12-11T14:23:56.028300944Z","logger":"postgres","msg":"record","logging_pod":"postgresql-demo-1","record":{"log_time":"2025-12-11 14:23:56.027 UTC","process_id":"30","session_id":"693ad3fb.1e","session_line_num":"5","session_start_time":"2025-12-11 14:23:55 UTC","transaction_id":"0","error_severity":"LOG","sql_state_code":"00000","message":"listening on Unix socket \"/controller/run/.s.PGSQL.5432\"","backend_type":"postmaster","query_id":"0"}}
{"level":"info","ts":"2025-12-11T14:23:56.075466089Z","logger":"postgres","msg":"record","logging_pod":"postgresql-demo-1","record":{"log_time":"2025-12-11 14:23:56.075 UTC","process_id":"36","session_id":"693ad3fc.24","session_line_num":"1","session_start_time":"2025-12-11 14:23:56 UTC","transaction_id":"0","error_severity":"LOG","sql_state_code":"00000","message":"database system was shut down at 2025-12-11 14:23:48 UTC","backend_type":"startup","query_id":"0"}}
{"level":"info","ts":"2025-12-11T14:23:56.108020409Z","logger":"postgres","msg":"record","logging_pod":"postgresql-demo-1","record":{"log_time":"2025-12-11 14:23:56.107 UTC","process_id":"30","session_id":"693ad3fb.1e","session_line_num":"6","session_start_time":"2025-12-11 14:23:55 UTC","transaction_id":"0","error_severity":"LOG","sql_state_code":"00000","message":"database system is ready to accept connections","backend_type":"postmaster","query_id":"0"}}Les logs de l’instances sont récupérés au format JSON, et sont en
l’état peu exploitables. Pour les lire plus facilement, vous pouvez
utiliser l’outil jq.
kubectl logs -f postgresql-demo-1 | jq
[...]
{
"level": "info",
"ts": "2025-09-16T11:58:11.525416928Z",
"logger": "postgres",
"msg": "record",
"logging_pod": "postgresql-demo-1",
"record": {
"log_time": "2025-09-16 11:58:11.525 UTC",
"process_id": "22",
"session_id": "68c950d3.16",
"session_line_num": "6",
"session_start_time": "2025-09-16 11:58:11 UTC",
"transaction_id": "0",
"error_severity": "LOG",
"sql_state_code": "00000",
"message": "database system is ready to accept connections",
"backend_type": "postmaster",
"query_id": "0"
}
}But : Découvrir les éléments automatiquement créés par l’opérateur.
Avec quelques lignes de YAML et peu de commandes, une instance PostgreSQL est déployée et accessible. De nombreuses choses sont créées automatiquement pour nous. Voyons de quoi il s’agit.
Bases de données
Retrouver la liste des bases de données dans l’instance déployée. La meta-commande
\lde psql vous permet de récupérer la liste des bases.
kubectl exec -it postgresql-demo-1 -- psql -c '\l'
ou
kubectl cnpg psql postgresql-demo -- -c '\l'
Defaulted container "postgres" out of: postgres, bootstrap-controller (init)
List of databases
Name | Owner | Encoding | Locale Provider | Collate | Ctype | Locale | ICU Rules | Access privileges
-----------+----------+----------+-----------------+---------+-------+--------+-----------+-----------------------
app | app | UTF8 | libc | C | C | | |
postgres | postgres | UTF8 | libc | C | C | | |
template0 | postgres | UTF8 | libc | C | C | | | =c/postgres +
| | | | | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | libc | C | C | | | =c/postgres +
| | | | | | | | postgres=CTc/postgres
(4 rows)
Par défaut, une base de données app est créée dans
l’instance PostgreSQL.
Rôles et Secret
Retrouver la liste des rôles dans l’instance déployée. La méta-commande psql
\dupeut vous aider.
La méta-commande psql \du vous permet de récupérer la
liste des rôles.
kubectl exec -it postgresql-demo-1 -- psql -c "\du"
ou
kubectl cnpg psql postgresql-demo -- -c '\du'
List of roles
Role name | Attributes
-------------------+------------------------------------------------------------
app |
postgres | Superuser, Create role, Create DB, Replication, Bypass RLS
streaming_replica | Replication
Par défaut deux rôles sont créés : app et
streaming_replica. Dans la liste des bases de données, on
peut d’ailleurs voir que le rôle app est propriétaire de la
base app.
Se déconnecter de l’instance.
postgres=# \q
Se connecter à la base de données
appavec le rôleapp. Une erreur devrait vous être retournée.
kubectl exec -it postgresql-demo-1 -- psql -U app
ou
kubectl cnpg psql postgresql-demo -- -U app
psql: error: connection to server on socket "/controller/run/.s.PGSQL.5432" failed: FATAL: Peer authentication failed for user "app"
command terminated with exit code 2
Se connecter à la base de données
appavec le rôleappet en passant par la stack TCP/IP.
L’authentification du rôle app avec la méthode
peer ne peut pas se faire. Mais alors comment se connecter
avec app ? En sachant que listen_addresses est
positionné à * par défaut, une solution pour tester la
connexion est de passer par la stack TCP/IP classique en
utilisant l’option -h de psql.
kubectl exec -it postgresql-demo-1 -- psql -U app -h 127.0.0.1
Defaulted container "postgres" out of: postgres, bootstrap-controller (init)
Password for user app:
Il faut comprendre que le 127.0.0.1 fait référence à
l’adresse localhost du Pod. On demande à psql,
via kubectl, de se connecter sur l’interface
localhost du Pod… mais il nous faut le mot de
passe de app… où le trouver ?
CloudNativePG crée automatiquement un Secret qui
contient des informations de connexion, notamment le mot de passe de
app.
Récupérer la liste des
Secretsdu cluster aveckubectl get secrets.
kubectl get secrets
NAME TYPE DATA AGE
postgresql-demo-app kubernetes.io/basic-auth 11 24m
postgresql-demo-ca Opaque 2 24m
postgresql-demo-replication kubernetes.io/tls 2 24m
postgresql-demo-server kubernetes.io/tls 2 24m
Récupérer le mot de passe présent dans le
Secretpostgresql-demo-app. N’oubliez pas qu’il est encodé en BASE64, il faut décoder le contenu obtenu.
kubectl get secret postgresql-demo-app -o json | jq '.data.password'
"VFdyejRQbmY1RWMwVjFjUHlqYkdFZnI5RG52WE5YaXN0NUhIaFZkOENwSkpKOEthVkVLUkNxUGwweTRzaGlVbw=="
Ou bien sans jq, avec une commande kubectl
un peu plus poussée :
kubectl get secret postgresql-demo-app --no-headers -o custom-columns=Passwd:.data.password
VFdyejRQbmY1RWMwVjFjUHlqYkdFZnI5RG52WE5YaXN0NUhIaFZkOENwSkpKOEthVkVLUkNxUGwweTRzaGlVbw==
Le résultat est encodé en BASE64. Il faut donc le décoder avec l’une des commandes suivantes :
kubectl get secret postgresql-demo-app --no-headers -o custom-columns=Passwd:.data.password | base64 -d
TWrz4Pnf5Ec0V1cPyjbGEfr9DnvXNXist5HHhVd8CpJJJ8KaVEKRCqPl0y4shiUo
Se connecter à l’instance en utilisant le mot de passe.
kubectl exec -it postgresql-demo-1 -- psql -U app -h 127.0.0.1
Defaulted container "postgres" out of: postgres, bootstrap-controller (init)
Password for user app:
psql (17.5 (Debian 17.5-1.pgdg120+1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off, ALPN: postgresql)
Type "help" for help.
app=>
L’astuce d’utiliser kubectl et psql avec
l’option -h permet à des administrateurs de se connecter,
mais cela n’est pas envisageable pour des applications. Les applications
doivent passer par les objets Services.
Services
Un Service est une couche d’abstraction qui permet
d’accéder à un ensemble de Pods spécifiques. L’association
Service - Pods se fait via des
labels. Un label est une étiquette, un tag,
apposée à une ressource.
Retrouver la liste des
Servicesdans le cluster Kubernetes.
Comme toutes les autres ressources Kubernetes, vous pouvez récupérer
les objets Services avec get.
kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 6h24m
postgresql-demo-r ClusterIP 10.105.219.134 <none> 5432/TCP 6h11m
postgresql-demo-ro ClusterIP 10.105.155.153 <none> 5432/TCP 6h11m
postgresql-demo-rw ClusterIP 10.105.191.44 <none> 5432/TCP 6h11m
À chaque cluster PostgreSQL déployé, trois services sont créés :
postgresql-demo-rw qui est en lecture/écriture;postgresql-demo-ro qui sont en lecture seule;postgresql-demo-r.Retrouver les labels définis sur le
Podde votre instance :
kubectl get pod postgresql-demo-1 --show-labels
NAME READY STATUS RESTARTS AGE LABELS
postgresql-demo-1 1/1 Running 0 26h cnpg.io/cluster=postgresql-demo,cnpg.io/instanceName=postgresql-demo-1,cnpg.io/instanceRole=primary,cnpg.io/podRole=instance,role=primary
Retrouver la description du
Servicepostgresql-demo-roet retrouver la partieSelectorqui indique à quel(s)Pod(s) sera associé ceService.
kubectl describe service postgresql-demo-ro
Name: postgresql-demo-ro
Namespace: default
Labels: cnpg.io/cluster=postgresql-demo
Annotations: cnpg.io/operatorVersion: 1.27.1
Selector: cnpg.io/cluster=postgresql-demo,cnpg.io/instanceRole=replica
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.96.123.203
IPs: 10.96.123.203
Port: postgres 5432/TCP
TargetPort: 5432/TCP
Endpoints:
Session Affinity: None
Internal Traffic Policy: Cluster
Events: <none>
Lorsqu’une bascule a lieu, les labels des Pods
sont mis à jour et l’association Service - Pod
est automatiquement adaptée.
De ce fait, si vos applications utilisent bien le nom du
Service dans les informations de connexion, elles seront
automatiquement redirigées vers la nouvelle instance primaire par
exemple.
Installer une instance PostgreSQL ne suffit pas. Il faut en plus la
configurer. Habituellement, la configuration se fait dans le fichier
postgresql.conf et nécessite soit un rechargement, soit un
redémarrage de l’instance selon le paramètre modifié. Nous allons voir
comment le faire sur notre instance postgresql-demo-1.
Dupliquer le fichier
~/postgresql-demo.yamlpour conserver une copie de la définition initiale de l’instance.
cp ~/postgresql-demo.yaml ~/postgresql-demo.bckp
Modifier le fichier
~/postgresql-demo.yamlet ajouter la sectionpostgresql.parameterscomme dans l’exemple. Nous allons tout d’abord modifier les paramètresshared_buffersetmax_connections. Positionnez-les respectivement à256MBet10.
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: postgresql-demo
spec:
imageName: ghcr.io/cloudnative-pg/postgresql:17.5-standard-bookworm
instances: 1
storage:
size: 5Gi
walStorage:
size: 5Gi
postgresql:
parameters:
shared_buffers: '256MB'
max_connections: '10'
affinity:
enablePodAntiAffinity: true
topologyKey: kubernetes.io/hostname
podAntiAffinityType: preferred
resources:
requests:
memory: "256Mi"
cpu: "0.5"
limits:
memory: "512Mi"
cpu: "1"Suivre les traces du
Podet de l’instance aveckubectl logs -f postgresql-demo-1 | jq.
Utiliser
kubectl apply -f ~/postgresql-demo.yamlpour appliquer les modifications.
Dans les traces du Pod, certains messages indiquent très
clairement ce qu’il va se passer. Les champs msg et
message sont les plus intéressants. Par exemple, celui-ci
qui indique qu’un rechargement de la configuration est nécessaire.
{
"level": "info",
"ts": "2025-12-11T14:59:02.686861537Z",
"msg": "Requesting configuration reload",
"logger": "instance-manager",
"logging_pod": "postgresql-demo-1",
"controller": "instance-cluster",
"controllerGroup": "postgresql.cnpg.io",
"controllerKind": "Cluster",
"Cluster": {
"name": "postgresql-demo",
"namespace": "default"
},
"namespace": "default",
"name": "postgresql-demo",
"reconcileID": "2fc475ca-80e9-4563-927a-f63ea3559eeb",
"pgdata": "/var/lib/postgresql/data/pgdata",
"pgCtlOptions": [
"-D",
"/var/lib/postgresql/data/pgdata",
"reload"
]
}Ou encore celui-ci, quelques lignes plus loin, qui indique que le paramètre modifié implique qu’un redémarrage de l’instance est nécessaire.
{
"level": "info",
"ts": "2025-12-11T14:59:02.696822747Z",
"logger": "postgres",
"msg": "record",
"logging_pod": "postgresql-demo-1",
"record": {
"log_time": "2025-12-11 14:59:02.696 UTC",
"process_id": "30",
"session_id": "693ad3fb.1e",
"session_line_num": "8",
"session_start_time": "2025-12-11 14:23:55 UTC",
"transaction_id": "0",
"error_severity": "LOG",
"sql_state_code": "55P02",
"message": "parameter \"max_connections\" cannot be changed without restarting the server",
"backend_type": "postmaster",
"query_id": "0"
}
}À la toute fin, votre instance est de nouveau accessible comme l’indique la ligne JSON :
{
"level": "info",
"ts": "2025-12-11T14:59:04.359349097Z",
"logger": "postgres",
"msg": "record",
"logging_pod": "postgresql-demo-1",
"record": {
"log_time": "2025-12-11 14:59:04.359 UTC",
"process_id": "870",
"session_id": "693adc38.366",
"session_line_num": "6",
"session_start_time": "2025-12-11 14:59:04 UTC",
"transaction_id": "0",
"error_severity": "LOG",
"sql_state_code": "00000",
"message": "database system is ready to accept connections",
"backend_type": "postmaster",
"query_id": "0"
}
}Il faut donc comprendre que dès qu’une modification est apportée à la configuration, par défaut, l’opérateur CloudNativePG va faire en sorte qu’elle soit prise immédiatement en compte.
Un rechargement de la configuration sera effectué si le paramètre ne nécessite pas le redémarrage de l’instance. Si un redémarrage est effectué, alors les connexions seront coupées et devront être refaites à l’instance PostgreSQL par les applications. Vous devez donc bien savoir ce que vous devez faire. Des précautions sont donc plus que nécessaires.
Modifier le paramètre
work_memet réappliquer la définition YAML aveckubectl apply -f ~/postgresql-demo.yaml.
Le paramètre work_mem ne nécessite pas un redémarrage de
l’instance. Voici un exemple de trace obtenue lors de la modification de
ce paramètre.
{
"level": "info",
"ts": "2025-12-11T15:10:01.128090861Z",
"logger": "postgres",
"msg": "record",
"logging_pod": "postgresql-demo-1",
"record": {
"log_time": "2025-12-11 15:10:01.108 UTC",
"process_id": "870",
"session_id": "693adc38.366",
"session_line_num": "7",
"session_start_time": "2025-12-11 14:59:04 UTC",
"transaction_id": "0",
"error_severity": "LOG",
"sql_state_code": "00000",
"message": "received SIGHUP, reloading configuration files",
"backend_type": "postmaster",
"query_id": "0"
}La ligne message indique que seul un rechargement de la
configuration a été nécessaire.
Vérifier que la modification a bien été prise en compte. Vous pouvez le voir dans les traces ou alors directement en vous connectant à l’instance et en utilisant
show work_memdans le promptpsql;
Dans les traces, regarder le champ message.
{
"level": "info",
"ts": "2025-12-11T15:10:01.128178235Z",
"logger": "postgres",
"msg": "record",
"logging_pod": "postgresql-demo-1",
"record": {
"log_time": "2025-12-11 15:10:01.110 UTC",
"process_id": "870",
"session_id": "693adc38.366",
"session_line_num": "8",
"session_start_time": "2025-12-11 14:59:04 UTC",
"transaction_id": "0",
"error_severity": "LOG",
"sql_state_code": "00000",
"message": "parameter \"work_mem\" changed to \"8MB\"",
"backend_type": "postmaster",
"query_id": "0"
}
}kubectl exec -it postgresql-demo-1 -- psql
ou, via le plugin :
kubectl cnpg psql postgresql-demo
Defaulted container "postgres" out of: postgres, bootstrap-controller (init)
psql (17.0 (Debian 17.0-1.pgdg110+1))
Type "help" for help.
postgres=# show work_mem ;
work_mem
----------
8MB
(1 row)
Certains paramètres PostgreSQL ne sont pas modifiables. C’est le parti pris des développeurs de CloudNativePG. La liste se trouve dans la documentation du projet (ici).
Il existe plusieurs méthodes pour créer un rôle dans une instance.
L’ordre SQL CREATE ROLE ... peut évidemment être utilisé,
mais pour cet exemple, nous allons passer par la méthode déclarative et
demander à l’opérateur de faire en sorte que le rôle soit présent dans
l’instance.
Créer un rôle
dbaayant les droitsSUPERUSERdans l’instance. Cela peut se faire grâce à la spécificationspec.managed.rolesdans l’objetCluster.
L’ajout d’un rôle se fait avec la section managed du
fichier yaml. Par exemple dans notre fichier
~/postgresql-demo.yaml, cela donnerait :
[...]
spec:
[...]
managed:
roles:
- name: dba
ensure: present
comment: Administrateur
login: true
superuser: trueAppliquer la modification avec
kubectl apply -f ~/postgresql-demo.yaml.
kubectl apply -f ~/postgresql-demo.yaml
cluster.postgresql.cnpg.io/postgresql-demo configured
Vérifier que le rôle a bien été créé.
kubectl exec -it postgresql-demo-1 -- psql -c '\du'
Defaulted container "postgres" out of: postgres, bootstrap-controller (init)
List of roles
Role name | Attributes
-------------------+------------------------------------------------------------
app |
dba | Superuser
postgres | Superuser, Create role, Create DB, Replication, Bypass RLS
streaming_replica | Replication
Le rôle est bien créé mais il n’a actuellement pas de mot de passe configuré.
Si vous souhaitez en ajouter un, vous pouvez le faire de plusieurs manières :
ALTER ROLE ... SET PASSWORD ...
;\password <user> (la préférer à
ALTER ROLE...);Secret.C’est cette dernière méthode que nous allons suivre. Pour cela, le
mot de passe n’est jamais passé en clair dans le fichier YAML. Il est en
fait nécessaire de créer un objet Secret qui contiendra ce
mot de passe encodé en BASE64 ainsi que le nom du rôle. C’est ce
Secret là qui sera utilisé dans le fichier YAML.
Encoder le nom du rôle (
dba) en BASE64.
printf "dba" | base64
ZGJh
Encoder le mot de passe (
ilovemydba) en BASE64.
Vous pouvez ajouter un espace avant echo pour que la
commande n’apparaisse pas dans l’historique de l’utilisateur
dalibo.
printf "ilovemydba" | base64
aWxvdmVteWRiYQ==
Créer un fichier
~/secret.yamlavec le contenu suivant puis créer leSecret.
apiVersion: v1
data:
username: ZGJh
password: aWxvdmVteWRiYQ==
kind: Secret
metadata:
name: secret-password-dba
labels:
cnpg.io/reload: "true"
type: kubernetes.io/basic-authkubectl apply -f ~/secret.yaml
secret/secret-password-dba created
Ajouter ce mot de passe à la définition du rôle
dbadans le fichier~/postgresql-demo.yaml, via l’informationpasswordSecret.
[...]
managed:
roles:
- name: dba
ensure: present
comment: Administrateur
login: true
superuser: true
passwordSecret:
name: secret-password-dbaAppliquer les modifications.
kubectl apply -f ~/postgresql-demo.yaml
cluster.postgresql.cnpg.io/postgresql-demo configured
Se connecter avec ce nouveau rôle à la base
postgres.
Le rôle dba peut désormais se connecter avec son super
mot de passe. Par exemple :
kubectl exec -it postgresql-demo-1 -c postgres -- psql -d postgres -U dba -h 127.0.0.1
Defaulted container "postgres" out of: postgres, bootstrap-controller (init)
Password for user dba:
psql (17.0 (Debian 17.0-1.pgdg110+1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off, ALPN: postgresql)
Type "help" for help.
postgres=#
Créer une base de données
db1dans l’instancepostgresql-demo. Le propriétaire de cette base doit être le rôleapp. Pour cela, créer un fichierdb1.yamlcontenant la définition d’une ressourceDatabase.
Voici la déclaration d’une telle base de données.
apiVersion: postgresql.cnpg.io/v1
kind: Database
metadata:
name: db1
spec:
name: db1
owner: app
cluster:
name: postgresql-demoCréer la nouvelle ressource avec
kubectl apply -f ~/db1.yaml.
kubectl apply -f db1.yaml
database.postgresql.cnpg.io/db1 created
Vérifier la présence de cette base de données dans l’instance.
Plusieurs possibilités. En voici une :
kubectl cnpg psql postgresql-demo -- -c "\l"
List of databases
Name | Owner | Encoding | Locale Provider | Collate | Ctype | Locale | ICU Rules | Access privileges
-----------+----------+----------+-----------------+---------+-------+--------+-----------+-----------------------
app | app | UTF8 | libc | C | C | | |
db1 | app | UTF8 | libc | C | C | | |
postgres | postgres | UTF8 | libc | C | C | | |
template0 | postgres | UTF8 | libc | C | C | | | =c/postgres +
| | | | | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | libc | C | C | | | =c/postgres +
| | | | | | | | postgres=CTc/postgres
(5 rows)
But : Déployer une instance secondaire dans le
cluster postgresql-demo.
Notre instance actuellement déployée ne possède pas de secondaire.
L’ajout de secondaire se fait facilement en modifiant le paramètre
instances dans la section spec de notre
fichier yaml.
Déployer un secondaire à votre instance en modifiant le paramètre
instancesà 2.
kubectl apply -f ~/postgresql-demo.yaml
Un second Pod va être déployé.
kubectl get pod | grep demo
postgresql-demo-1 1/1 Running 0 73m
postgresql-demo-2-join-h2vvw 0/1 Init:0/1 0 38s
kubectl get pod | grep demo
postgresql-demo-1 1/1 Running 0 73m
postgresql-demo-2-join-h2vvw 0/1 PodInitializing 0 58s
kubectl get pod | grep demo
postgresql-demo-1 1/1 Running 0 73m
postgresql-demo-2 1/1 Running 0 31s
Et voilà, un secondaire a été créé ! L’opérateur CloudNativePG
s’assure de tout configurer : ajout du paramètre
primary_conninfo, création du fichier
standby.signal, mise à jour de pg_hba.conf,
slot de réplication, etc. Le secondaire se connecte alors au
primaire en utilisant la réplication physique native de PostgreSQL
(Streaming Replication).
Regardons la configuration qui est mise en place par défaut.
Se connecter avec
psqlau secondaire nouvellement créé.
kubectl exec -it postgresql-demo-2 -c postgres -- psql
Récupérer le contenu du paramètre
primary_conninfo.
postgres=# \x
Expanded display is on.
postgres=# show primary_conninfo ;
-[ RECORD 1 ]----+---------------------
primary_conninfo | host=postgresql-demo-rw user=streaming_replica [...]La sortie a été mise en forme pour plus de lisibilité.
host=postgresql-demo-rw
user=streaming_replica
port=5432
sslkey=/controller/certificates/streaming_replica.key
sslcert=/controller/certificates/streaming_replica.crt
sslrootcert=/controller/certificates/server-ca.crt
application_name=postgresql-demo-2
sslmode=verify-caLe secondaire utilise le Service
postgresql-demo-rw pour accéder à l’instance primaire. Vous
comprendrez qu’une résolution DNS interne au cluster Kubernetes
doit se faire pour retrouver l’adresse IP associée. La réplication
utilise l’utilisateur dédié streaming_replica créé par
CloudNativePG lors du déploiement de la première instance.
L’authentification se fait par certificat. Le paramètre
application_name permet d’indiquer un nom d’application
dans les informations liée la connexion.
Se connecter avec
psqlau primaire.
kubectl exec -it postgresql-demo-1 -c postgres -- psql
ou, via le plugin :
kubectl cnpg psql postgresql-demo
Récupérer le contenu de la table
pg_stat_replication.
postgres=# select * from pg_stat_replication\gx
-[ RECORD 1 ]----+------------------------------
pid | 16026
usesysid | 16386
usename | streaming_replica
application_name | postgresql-demo-2
client_addr | 10.244.1.11
client_hostname |
client_port | 54458
backend_start | 2025-12-12 07:13:15.829844+00
backend_xmin |
state | streaming
sent_lsn | 0/B000130
write_lsn | 0/B000130
flush_lsn | 0/B000130
replay_lsn | 0/B000130
write_lag | 00:00:00.000806
flush_lag | 00:00:00.013548
replay_lag | 00:00:00.014192
sync_priority | 0
sync_state | async
reply_time | 2025-12-12 07:17:57.892685+00Par défaut, c’est une réplication asynchrone qui est créée.
Récupérer le contenu de la table
pg_replication_slots.
postgres=# select * from pg_replication_slots \gx
-[ RECORD 1 ]-------+------------------------
slot_name | _cnpg_postgresql_demo_2
plugin |
slot_type | physical
datoid |
database |
temporary | f
active | t
active_pid | 16026
xmin |
catalog_xmin |
restart_lsn | 0/C000060
confirmed_flush_lsn |
wal_status | reserved
safe_wal_size |
two_phase | f
inactive_since |
conflicting |
invalidation_reason |
failover | f
synced | fPar défaut, CloudNativePG crée automatiquement un slot de réplication pour sécuriser la réplication. Son nom permet de savoir facilement à quoi il correspond.
Le slot de réplication garantit au secondaire que son primaire ne recyclera pas les journaux de transactions dont il aura encore besoin. Le secondaire peut donc prendre un retard conséquent sans risque de décrochage. Attention à l’accumulation des WALs qu’il peut y avoir sur le primaire en cas de retard ou de problème (coupure réseau, crash secondaire, etc).
Sur le primaire, créer une table dans la base
app.
Sur le primaire :
postgres=# \c app
app=# CREATE TABLE ma_table (i int);
CREATE TABLE
Vérifier qu’elle se trouve également sur le secondaire.
Sur le secondaire :
postgres=# \c app
You are now connected to database "app" as user "postgres".
app=# \dt
List of relations
Schema | Name | Type | Owner
--------+----------+-------+----------
public | ma_table | table | postgres
(1 row)
La réplication fonctionne !
L’opérateur CloudNativePG veille à déployer les instances PostgreSQL sur des nœuds différents afin de garantir la disponibilité. Cela permet de réduire les risques liés à un incident en s’assurant que toutes les instances ne sont pas affectées simultanément. Cette configuration permet également la répartition de la charge entre plusieurs nœuds pour des opérations de lecture.
Trouver le nom du nœud où est déployée chaque instance.
kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
postgresql-demo-1 1/1 Running 0 16h 10.244.2.11 kind-worker2 <none> <none>
postgresql-demo-2 1/1 Running 0 8m34s 10.244.1.11 kind-worker <none> <none>
Cette répartition est possible grâce notamment à la configuration de
l’affinité et anti-affinité entre Pod ajoutée au YAML du
Cluster.
But : Tester et comprendre le mécanisme de bascule entre primaire et secondaire.
Trouver quelle est l’instance primaire du cluster
postgresql-demo.
Vous devriez trouver que l’instance postgresql-demo-1
est l’instance primaire. Pour le savoir, plusieurs méthodes :
pg_is_in_recovery() en étant
connecté sur une instance avec psql. Si elle retourne
false, l’instance est primaire, sinon, à true,
elle est secondaire.kubectl exec -it postgresql-demo-1 -c postgres -- psql -c 'SELECT pg_is_in_recovery();'
pg_is_in_recovery
-------------------
f
(1 row)
kubectl exec -it postgresql-demo-2 -c postgres -- psql -c 'SELECT pg_is_in_recovery();'
pg_is_in_recovery
-------------------
t
(1 row)
cnpg de kubectl et de sa
commande status ;kubectl cnpg status postgresql-demo
Cluster Summary
Name default/postgresql-demo
System ID: 7582605931997384732
PostgreSQL Image: ghcr.io/cloudnative-pg/postgresql:17.5-standard-bookworm
Primary instance: postgresql-demo-1
Primary promotion time: 2025-12-11 14:23:56 +0000 UTC (17h6m44s)
Status: Cluster in healthy state
Instances: 2
Ready instances: 2
Size: 247M
Current Write LSN: 0/E000000 (Timeline: 1 - WAL File: 00000001000000000000000E)
[...]
Promouvoir l’instance
postgresql-demo-2comme nouvelle primaire avec le plugincnpgdekubectl.
Attention, il y a bien un espace entre le nom du cluster et l’identifiant de l’instance.
kubectl cnpg promote postgresql-demo 2
{"level":"info","ts":"2024-12-06T10:40:18.767552528Z","msg":"Cluster is not healthy"}
Node postgresql-demo-2 in cluster postgresql-demo will be promoted
Vérifier que le cluster est en bonne santé et que
postgresql-demo-2est désormais l’instance primaire.
kubectl cnpg status postgresql-demo
[...]
Primary instance: postgresql-demo-2
Primary promotion time: 2025-12-12 07:31:26 +0000 UTC (30s)
Status: Cluster in healthy state
[...]
Instances status
Name Current LSN Replication role Status QoS Manager Version Node
---- ----------- ---------------- ------ --- --------------- ----
postgresql-demo-2 0/F001078 Primary OK Burstable 1.27.1 kind-worker
postgresql-demo-1 0/F001078 Standby (async) OK Burstable 1.27.1 kind-worker2
Les applications auront évidemment une coupure réseau, comme il y a une bascule.
Lorsqu’une erreur survient sur le primaire le mécanisme de
failover va être déclenché. Ce mécanisme sera démarré après une
certaine durée modifiable via le paramètre
.spec.failoverDelay (par défaut à 0) dans la définition du
cluster PostgreSQL.
L’erreur peut être, par exemple, un problème sur le volume associé, le Pod primaire qui serait supprimé, le conteneur PostgreSQL qui serait KO, etc… (voir la documentation sur les probes).
Ajouter le paramètre
.spec.failoverDelayà votre instance et le postionner à 10 secondes.
Le prendre en compte avec
kubectl apply -f ~/postgresql-demo.yaml.
kubectl apply -f ~/postgresql-demo.yaml
cluster.postgresql.cnpg.io/postgresql-demo configured
Dans une autre session, lancer la commande
watch kubectl get pod.
watch kubectl get pod
Détruire le
Podcorrespondant à l’instance primaire.
kubectl delete pod postgresql-demo-2
pod "postgresql-demo-2" deleted
Regarder comment réagit le
Cluster.
Une bascule sur le seul Pod disponible est faite après
10 secondes. L’instance postgresql-demo-1 est
automatiquement promue primaire. Dans la foulée, un Pod est
recréé pour retrouver la situation initiale.
Instances status
Name Current LSN Replication role Status QoS Manager Version Node
---- ----------- ---------------- ------ --- --------------- ----
postgresql-demo-1 0/11002BF8 Primary OK Burstable 1.27.1 kind-worker2
postgresql-demo-2 0/11002BF8 Standby (async) OK Burstable 1.27.1 kind-worker
Voici un exemple avec un délai .spec.failoverDelay à 20
secondes.
But : Mettre en place une sauvegarde PITR sur un stockage S3 (archivage et sauvegarde complète).
Comme vous le savez certainement, il existe le concept de sauvegarde physique PITR comme mécanisme de sauvegarde d’une instance. Pour mettre en place cela, il est d’abord nécessaire de faire une sauvegarde physique de l’arborescence de l’instance. Ceci peut être fait à chaud. Le second élément essentiel est l’archivage des journaux de transactions (WAL) qui seront rejoués après une restauration pour rétablir un état cohérent.
En déployant une instance avec CloudNativePG, la seule solution de
sauvegarde PITR utilisable est Barman Cloud. Très connu
dans l’éco-système PostgreSQL, cet outil nous permet de faire la
sauvegarde physique et l’archivage des WALs. Les commandes passées pour
la mettre en place le seront de manière automatique mais une
configuration doit être rajoutée dans le fichier YAML de définition.
La mise en place d’une solution de sauvegarde nécessite, depuis la version 1.26, l’installation d’un plugin dédié aux sauvegardes. Le projet CloudNativePG met à disposition le plugin Barman Cloud CNPG-I.
Nous allons donc l’installer sur notre cluster Kubernetes. Aussi,
l’outil cert-manager doit être présent dans le cluster
Kubernetes. Il est utilisé pour la génération de certificats pour la
communication entre l’opérateur et le plugin de sauvegarde.
Installer
cert-manageravec la commande suivante.
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.19.2/cert-manager.yaml
Patienter quelques minutes, le temps que
cert-managers’installe, puis installer le plugin avec la commande suivante.
kubectl apply -f https://github.com/cloudnative-pg/plugin-barman-cloud/releases/download/v0.11.0/manifest.yaml
Vérifier que le plugin est bien installé dans le
Namespacecnpg-system.
kubectl get pod -n cnpg-system
NAME READY STATUS RESTARTS AGE
barman-cloud-6858cdc47f-gr2bh 1/1 Running 0 3m32s
cnpg-controller-manager-7b7fcf5cf6-8rmj9 1/1 Running 0 9m18s
Créer le fichier
~/s3-creds.yamlavec le contenu suivant.
Les paramètres ACCESS_* doivent contenir l’information
encodée en BASE64. Si vous utilisez la commande echo,
n’oubliez l’option -n qui empêche la prise en compte du
saut de ligne. Les informations vous seront données par le
formateur.
---
apiVersion: v1
kind: Secret
metadata:
name: s3-creds
type: Opaque
data:
ACCESS_KEY_ID: bWluaW9hZG1pbg==
ACCESS_REGION: ZnItcGFy
ACCESS_SECRET_KEY: bWluaW9hZG1pbg==Créer le
Secretdans votre cluster Kubernetes avec la commandekubectl apply -f ~/s3-creds.yaml.
kubectl apply -f s3-creds.yaml
secret/s3-creds createdkubectl delete -f postgresql-demo.yaml
Ce Secret contient les informations de la clé API qui
permettra de s’authentifier au Bucket S3 et de déposer les
WAL et les sauvegardes. Ici il s’agit de minioadmin.
Pour cette partie du TP, nous allons créer une autre instance PostgreSQL (
postgresql-with-backup-demo), donc un objet de typeClusteret un nouveau nom.
Créer le fichier
~/postgresql-with-backup-demo.yamlavec le contenu suivant.
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: postgresql-with-backup-demo
spec:
imageName: ghcr.io/cloudnative-pg/postgresql:17.5-standard-bookworm
instances: 1
storage:
size: 5Gi
walStorage:
size: 5Gi
postgresql:
parameters:
shared_buffers: '256MB'
max_connections: '10'
work_mem: "8MB"
archive_timeout: "20min"
resources:
requests:
memory: "256Mi"
cpu: "0.5"
limits:
memory: "512Mi"
cpu: "1"
plugins:
- name: barman-cloud.cloudnative-pg.io
isWALArchiver: true
parameters:
barmanObjectName: objectstore-demo La partie backup indique quel plugin de
sauvegarde utilisé, ici Barman Cloud. Il est également
indiqué que c’est ce plugin qui sera en charge de l’archivage
des journaux de transactions grâce à le paramètre
isWALArchiver à true.
Créer le fichier
~/objectstore-demo.yamlpour créer l’ObjectStorequi sera utilisé par le nouveauCluster. N’oubliez pas de modifier le paramètreendpointURLavec l’adresse IP du conteneurminio.
apiVersion: barmancloud.cnpg.io/v1
kind: ObjectStore
metadata:
name: objectstore-demo
spec:
configuration:
destinationPath: "s3://cnpg/"
endpointURL: "http://172.18.0.5:9000"
s3Credentials:
accessKeyId:
name: s3-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: s3-creds
key: ACCESS_SECRET_KEY
region:
name: s3-creds
key: ACCESS_REGION
wal:
compression: gzipLa configuration des paramètres endpointURL et
destinationPath devra être adaptée selon votre fournisseur
de stockage S3. Les paramètres ci-dessus fonctionnent bien avec minio en
local. Faites vraiment attention, vous risquerez de perdre beaucoup de
temps … vraiment :).
Créer l’
ObjectStoreavec la commande :
kubectl apply -f ~/objectstore-demo.yaml
Configurer l’utilitaire
mcpour qu’il se connecte au conteneurminioavec sa commandealias:
mc alias set minio http://172.18.0.5:9000 minioadmin minioadmin
Créer un nouveau bucket
cnpgdans minio avec la commande :
mc mb minio/cnpg
Bucket created successfully `minio/cnpg`.
Créer le nouveau
ClusterPostgreSQL avec la commande :
kubectl apply -f ~/postgresql-with-backup-demo.yaml
Vérifier que l’archivage se passe correctement directement dans la vue
pg_stat_archiver.
kubectl cnpg psql postgresql-with-backup-demo
postgres=# \x
Expanded display is on.
postgres=# select * from pg_stat_archiver ;
-[ RECORD 1 ]------+------------------------------
archived_count | 2
last_archived_wal | 000000010000000000000002
last_archived_time | 2025-12-12 10:22:11.071834+00
failed_count | 0
last_failed_wal |
last_failed_time |
stats_reset | 2025-12-12 10:20:09.349145+00
Vérifier que des WAL soient bien présents dans le bucket
cnpgavec la commandemc ls.
Regardons dossier par dossier ce qui est créé dans le
bucket. Tout d’abord, le dossier
postgresql-with-backup-demo/, qui reprend le nom du
Cluster est bien créé dans cnpg.
mc ls minio/cnpg/
[2026-04-02 16:12:42 CEST] 0B postgresql-with-backup-demo/
… qui contient lui-même un dossier wals.
mc ls minio/cnpg/postgresql-with-backup-demo/
[2026-04-02 16:15:23 CEST] 0B wals/
Les journaux (WAL) sont enregistrés dans des dossiers qui reprennent
la timeline de l’instance.
mc ls minio/cnpg/postgresql-with-backup-demo/wals/
[2026-04-02 16:15:55 CEST] 0B 0000000100000000/
Et enfin, dans ce dernier dossier, se trouvent les journaux de transaction compressés.
mc ls minio/cnpg/postgresql-with-backup-demo/wals/0000000100000000/
[2026-04-02 16:10:15 CEST] 2.3MiB STANDARD 000000010000000000000001.gz
C’est un super point de départ. Mais pour le moment, il n’est pas possible de faire quelconque restauration comme il nous manque une sauvegarde complète de l’instance.
Créer le fichier
~/letsbackup.yamlavec le contenu suivant :
apiVersion: postgresql.cnpg.io/v1
kind: Backup
metadata:
name: first-backup
spec:
cluster:
name: postgresql-with-backup-demo
method: plugin
pluginConfiguration:
name: barman-cloud.cloudnative-pg.ioIl faut donner un nom à cet objet Backup et le nom du
cluster PostgreSQL que l’on souhaite sauvegarder ainsi qu’avec quelle
méthode de sauvegarde cela va être fait, ici via le plugin
Barman Cloud.
Créer cette ressource avec avec
kubectl.
kubectl apply -f ~/letsbackup.yaml
backup.postgresql.cnpg.io/first-backup created
Vérifier le statut de l’objet
Backup:
kubectl get backup
NAME AGE CLUSTER METHOD PHASE ERROR
first-backup 14s postgresql-with-backup-demo plugin started
Chercher dans les traces du
Podune preuve que la sauvegarde complète s’est bien déroulée.
kubectl logs postgresql-with-backup-demo-1 | grep completed | jq
{
"level": "info",
"ts": "2025-12-12T10:29:49.602829931Z",
"msg": "Backup completed",
"pluginConfiguration": {
"name": "barman-cloud.cloudnative-pg.io"
},
"backupName": "first-backup",
"backupNamespace": "default",
"logging_pod": "postgresql-with-backup-demo-1"
}Observer les changements dans le conteneur
minio.
Au niveau du conteneur, un nouveau dossier base est
apparu à côté de wals.
mc ls minio/cnpg/postgresql-with-backup-demo/
[2026-04-02 16:18:09 CEST] 0B base/
[2026-04-02 16:18:09 CEST] 0B wals/
Il contient toutes les sauvegardes faites jusqu’à présent.
mc ls minio/cnpg/postgresql-with-backup-demo/base/
[2026-04-02 16:18:33 CEST] 0B 20260402T141657/
La sauvegarde physique se trouve dans ce dossier et comporte un
fichier d’informations et une archive tar.
mc ls minio/cnpg/postgresql-with-backup-demo/base/20260402T141657/
[2026-04-02 16:17:03 CEST] 1.4KiB STANDARD backup.info
[2026-04-02 16:17:03 CEST] 32MiB STANDARD data.tar
Incroyable ! Nous avons une sauvegarde et un archivage des WALs qui semblent se dérouler correctement. Mais, qu’est ce qu’il se cache derrière cela ?
La première chose que nous pouvons chercher à savoir par exemple, est
quel outil est utilisé pour archiver les journaux. Le paramètre
archive_command nous donne un début de réponse.
kubectl exec -it postgresql-with-backup-demo-1 -- psql -c "SHOW archive_command"
archive_command
------------------------------------------------------------------------------------
/controller/manager wal-archive --log-destination /controller/log/postgres.json %p
(1 row)Un outil appelé manager présent dans le conteneur est
utilisé avec l’option wal-archive suivie de plusieurs
paramètres. %p est un placeholders qui permet
d’indiquer le WAL courant.
Se connecter à l’instance. Créer une table et insérer quelques données.
kubectl exec -it postgresql-with-backup-demo-1 -- psql
ou, via le plugin :
kubectl cnpg psql postgresql-with-backup-demo
Ne pas oublier d’exécuter l’ordre CHECKPOINT qui
permettra de forcer la synchronisation des données sur disque et la
création d’un point de cohérence sans attendre l’expiration de
checkpoint_timeout.
Forcer la création d’un nouveau journal de transactions avec
SELECT pg_switch_wal();.
Il devrait apparaitre dans votre Bucket S3.
But : Restaurer notre instance depuis la sauvegarde PITR existante.
Les restaurations se font obligatoirement dans une nouvelle instance
PostgreSQL. Le principe de restauration in-place n’est donc pas
possible. Attention donc si vous souhaitez conserver le nom du
Cluster vous devrez détruire le précédent
Cluster qui porterait ce nom.
Maintenant qu’une instance est déployée et qu’une sauvegarde a été faite, attardons-nous sur les manières qui existent pour restaurer une instance.
Aussi, c’est l’occasion de faire un petit rappel ! N’oubliez pas de tester vos procédures de restauration fréquemment !
Aussi, c’est l’occasion de faire un petit rappel ! N’oubliez pas de tester vos procédures de restauration fréquemment !
Simuler un crash. Détruire l’instance
postgresql-with-backup-demo(Nous sommes bien évidemment ici dans un exercice de destruction maîtrisé par des professionnels).
kubectl delete -f ~/postgresql-with-backup-demo.yaml
Créer un nouveau fichier
~/postgresql-restored-demo.yamlavec le contenu suivant. L’idée est de créer une nouvelle instancepostgresql-restored-demoet d’indiquer avec la sectionbootstrapqu’elle doit démarrer à partir d’une sauvegarde.
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: postgresql-restored-demo
spec:
imageName: ghcr.io/cloudnative-pg/postgresql:17.5-standard-bookworm
instances: 1
storage:
size: 5Gi
walStorage:
size: 5Gi
postgresql:
parameters:
shared_buffers: '256MB'
max_connections: '10'
work_mem: '8MB'
archive_timeout: '20min'
resources:
requests:
memory: "256Mi"
cpu: "0.5"
limits:
memory: "512Mi"
cpu: "1"
bootstrap:
recovery:
source: source
externalClusters:
- name: source
plugin:
name: barman-cloud.cloudnative-pg.io
parameters:
barmanObjectName: objectstore-demo
serverName: postgresql-with-backup-demo
plugins:
- name: barman-cloud.cloudnative-pg.io
isWALArchiver: true
parameters:
barmanObjectName: objectstore-demo # réutilisation du bucket S3L’emplacement de la sauvegarde est indiqué dans l’attribut
source de la section bootstrap.recovery. Il
fait référence à un externalClusters qui contient les
informations de l’ObjectStore utilisé et présent dans le
cluster Kuberetes.
Créer votre nouvelle instance avec
kubectl apply -f ~/postgresql-restored-demo.yaml.
kubectl apply -f ~/postgresql-restored-demo.yaml
Lorsque l’instance est prête, s’y connecter et vérifier que les données s’y trouvent bien.
kubectl exec -it postgresql-restored-demo-1 -c postgres -- psql -c "select count(*) from t1;"
count
-------
100
(1 row)
La méthode que nous venons de suivre, suppose que vous ayez accès à
l’objet ObjectStore créé dans le cluster
Kubernetes. Mais qu’en est-il si c’est tout le cluster
Kubernetes qui est en panne et doit être recréé ?
Dans ce cas-là, l’objet ObjectStore n’existe plus. Soit
vous le recréez, soit vous renseigner directement les informations de
connexion au stockage S3.
Du côté du Bucket S3, un nouveau dossier est
automatiquement créé avec le nom du nouvel objet Cluster.
Cela est du à la partie plugins dans laquelle nous avons
indiquer de réutiliser l’ObjectStore précédent.
mc ls minio/cnpg
[2026-04-02 16:22:02 CEST] 0B postgresql-restored-demo/
[2026-04-02 16:22:02 CEST] 0B postgresql-with-backup-demo/
Dans cet exemple, la restauration s’est faite sur le même
cluster Kubernetes. Dans le cas où vous devez la faire
ailleurs, n’oubliez pas de recréer le Secret qui contient
les informations de l’API Key nécessaire à l’accès au stockage S3.
Supprimer les instances
postgresql-demoqui ne vont plus nous servir par la suite.
kubectl delete -f postgresql-demo.yaml
Il ne doit rester que le cluster PostgreSQL
postgresql-restored-demo.
kubectl get pod
NAME READY STATUS RESTARTS AGE
postgresql-restored-demo-1 1/1 Running 2 (26m ago) 2d15h
But : Effectuer une montée de version mineure de PostgreSQL.
La version de PostgreSQL est indiquée dans le nom et le tag de l’image déployée. La modification de celle-ci entraîne une montée de version de l’instance. Cette montée de version peut se faire automatiquement ou de manière supervisée (appelée “manuelle” dans la documentation).
Dans une autre session SSH, lancer la commande
watch kubectl get podspour voir ce qu’il se passe pendant la montée de version.
Vous devriez voir la chose suivante avec un rafraichissement toutes les 2 secondes.
Every 2.0s: kubectl get pods scw-boring-keller: Mon Nov 25 08:40:03 2024
NAME READY STATUS RESTARTS AGE
postgresql-restored-demo-1 1/1 Running 2 (46m ago) 2d16h
Modifier la version de PostgreSQL de
17.5à17.6dans le fichier~/postgresql-restored-demo.yamlet appliquer la modification aveckubectl apply.
kubectl apply -f ~/postgresql-restored-demo.yaml
cluster.postgresql.cnpg.io/postgresql-restored-demo configured
Le Pod de l’instance en version 17.5 est supprimé.
Un nouveau Pod est créé.
Le temps de télécharger l’image et de le redémarré, il passe à l’état
Running.
Un select version() indique que nous sommes bien passés
en version 17.6.
Par défaut, une instance PostgreSQL est déployée de telle sorte que
la montée de version se fasse automatiquement, c’est-à-dire que
l’opérateur arrête puis redémarre l’instance tout seul. Voyons
maintenant le cas d’une montée de version en mode
supervised.
Le paramètre primaryUpdateStrategy permet de définir la
stratégie à suivre lors d’une mise à jour de l’instance primaire. Il est
positionné par défaut à unsupervised. C’est le comportement
que nous venons de voir avec l’exemple précédent.
Positionner le paramètre
spec.primaryUpdateStrategyàsupervised, modifier la version en la passant de17.6à17.7et tenter de faire la montée de version. Que constatez vous ?
spec:
imageName: ghcr.io/cloudnative-pg/postgresql:17.7-standard-bookworm
instances: 1
primaryUpdateStrategy: supervisedkubectl apply -f ~/postgresql-restored-demo.yaml
Aïe …
The Cluster "postgresql-restored-demo" is invalid: spec.primaryUpdateStrategy:
Invalid value: "supervised": supervised update strategy is not allowed for clusters
with a single instance
Nous venons de découvrir une première subtilité. Ce paramètre n’est en réalité pas utilisable avec une seule instance. En effet ce paramètre permet de contrôler la manière dont est redémarrée l’instance primaire après que toutes les instances secondaires aient été mis à jour. Ici, nous n’avons qu’une primaire de déployée.
Ceci nous permet donc de voir redéployer un secondaire. Rien de plus simple.
Ajouter un secondaire à votre cluster PostgreSQL en modifiant la ligne
instancesdu fichierpostgresql-restored-demo.yamlet en appliquant la modification.
Un premier Pod join va être créé puis le
Pod de l’instance secondaire sera finalement déployé. Nous
nous retrouvons donc avec deux Pods reprenant le nom du
cluster, incrémentés de 1.
kubectl get pod
NAME READY STATUS RESTARTS AGE
postgresql-restored-demo-1 2/2 Running 0 6m16s
postgresql-restored-demo-2-join-7ckvl 0/1 Pending 0 5s
kubectl get pod
NAME READY STATUS RESTARTS AGE
postgresql-restored-demo-1 1/1 Running 0 61m
postgresql-restored-demo-2 1/1 Running 0 20s
Vérifier les versions des deux instances. Que constatez-vous ?
Comme nous avons modifié la version de l’image en 17.7,
l’instance secondaire qui vient d’être déployée est bien dans cette
version. La version du primaire est quant à elle restée en
17.6, ce qui est normal comme nous sommes dans une montée
de version supervisée.
# primaire
kubectl exec -it postgresql-restored-demo-1 -c postgres -- psql -c 'show server_version;'
server_version
-------------------------------
17.6 (Debian 17.6-2.pgdg12+1)
(1 row)
# secondaire
kubectl exec -it postgresql-restored-demo-2 -c postgres -- psql -c 'show server_version;'
server_version
-------------------------------
17.7 (Debian 17.7-3.pgdg12+1)
(1 row)
Il nous reste donc à signifier à CloudNativePG que le primaire peut
être mis à jour à son tour. Pour cela, il faut passer par le
plugin CloudNativePG de kubectl et procéder à une
bascule sur le secondaire qui deviendra le nouveau primaire avec la
ligne de commande :
kubectl cnpg promote [cluster] [new_primary].
Faite une bascule manuelle sur l’instance secondaire.
kubectl cnpg promote postgresql-restored-demo postgresql-restored-demo-2
L’ancien secondaire est désormais primaire comme on peut le voir dans
la sortie de kubectl cnpg status postgresql-restored-demo
qui donne l’état du cluster PostgreSQL, en particulier, avec la ligne
Primary instance: postgresql-restored-demo-2, ou encore
dans le tableau.
Instances status
Name Current LSN Replication role Status QoS Manager Version Node
---- ----------- ---------------- ------ --- --------------- ----
postgresql-restored-demo-2 0/D001108 Primary OK Burstable 1.27.1 kind-worker
postgresql-restored-demo-1 0/D001108 Standby (async) OK Burstable 1.27.1 kind-worker2
Les deux instances sont bien en version 17.7.
kubectl exec -it postgresql-restored-demo-1 -c postgres -- psql -c 'show server_version;'
server_version
-------------------------------
17.7 (Debian 17.7-3.pgdg12+1)
(1 row)
Il existe d’autres cas où le redémarrage des instances est
nécessaire. Par exemple, si un paramètre comme
max_connections ou shared_buffers a été
modifié. Dans ce cas-là, si vous faites toujours une montée de version
supervisée, il vous est possible de ne pas faire de bascule mais
simplement de redémarrer l’instance primaire avec la ligne de commande
kubectl cnpg restart [cluster] [current_primary];
But : Effectuer une montée de version majeure de PostgreSQL.
Là aussi, la version majeure de PostgreSQL est indiquée dans le nom et le tag de l’image déployée. La modification de celle-ci entraîne le déclenchement d’une In-Place Major Upgrade. Ce mécanisme se fait nécessairement sur des instances arrêtées. L’opérateur CloudNativePG les arrêtera pour vous.
Pour cet exercice, nous allons mettre à jour un Cluster
en version 16.11 vers la version 18.1.
Créer le fichier
~/postgresql-16-to-18.yamlet ajouter le contenu suivant puis appliquer le aveckubectl.
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: postgresql-test
spec:
instances: 2
imageName: ghcr.io/cloudnative-pg/postgresql:16.11-standard-bookworm
storage:
size: 1Gikubectl apply -f ~/postgresql-16-to-18.yaml
Vérifier que les deux instances soient correctement déployées.
kubectl get pod
NAME READY STATUS RESTARTS AGE
postgresql-test-1 1/1 Running 0 38s
postgresql-test-2 1/1 Running 0 20s
Créer une table et y insérer quelques lignes.
kubectl cnpg psql postgresql-test -- -c "CREATE TABLE t1 (c1 INTEGER PRIMARY KEY, c2 text)";
CREATE TABLE
kubectl cnpg psql postgresql-test -- -c "INSERT INTO t1 SELECT generate_series(1,100), generate_series(1,100)::text";
INSERT 0 100
Ouvrir un nouveau terminal et lancer la commande
kubectl get pod --watch.
Ouvrir un nouveau terminal et lancer la commande
kubectl get pv --watch.
Ouvrir un nouveau terminal et lancer la commande
kubectl get pvc --watch.
Modifier le tag du paramètre
imageNamedu fichier~/postgresql-16-to-18.yamlen le passant de16.11à18.1puis appliquer la modification aveckubectl.
kubectl apply -f ~/postgresql-16-to-18.yaml
cluster.postgresql.cnpg.io/postgresql-test configured
Observez ce qu’il se passe dans les différents terminaux que vous avez ouvert.
Les deux instances passent en Terminating
postgresql-test-1 1/1 Terminating 0 4m26s
postgresql-test-2 1/1 Terminating 0 4m8s
La montée de version est déclenchée et un nouveau Pod
apparait :
postgresql-test-1-major-upgrade-7hl8w 0/1 Init:0/2 0 0s
postgresql-test-1-major-upgrade-7hl8w 0/1 Init:1/2 0 1s
postgresql-test-1-major-upgrade-7hl8w 0/1 PodInitializing 0 2s
postgresql-test-1-major-upgrade-7hl8w 1/1 Running 0 18s
postgresql-test-1-major-upgrade-7hl8w 0/1 Completed 0 45s
Lorsque qu’il a terminé de s’exécuté, le Pod
correspondant à l’instance primaire est relancé :
postgresql-test-1 0/1 Pending 0 0s
postgresql-test-1 0/1 Init:0/1 0 0s
postgresql-test-1 0/1 PodInitializing 0 0s
postgresql-test-1 0/1 Running 0 10s
postgresql-test-1 1/1 Running 0 10s
À ce moment là, le volume (PV) qui existait et qui était rattaché à
l’ancienne instance secondaire postgresql-test-2 sont
supprimés.
pvc-23b021b2-163b-41e3-9791-721b8635bee5 1Gi RWO Delete Released default/postgresql-test-2 standard <unset> 5m3s
pvc-23b021b2-163b-41e3-9791-721b8635bee5 1Gi RWO Delete Terminating default/postgresql-test-2 standard <unset> 5m6s
Même chose pour le PVC :
postgresql-test-2 Terminating pvc-23b021b2-163b-41e3-9791-721b8635bee5 1Gi RWO standard <unset> 5m5s
Pour respecter ce qui est demandé dans la définition YAML, une
instance secondaire postgresql-test-3 est créée et va se
joindre à la nouvelle instance primaire postgresql-test-1.
Cela se fait par le déploiement d’un Pod intermédiaire
suffixé par -join-XXXXX. Il sera détruit à la fin de
l’opération.
postgresql-test-3-join-tcrbs 0/1 Pending 0 0s
postgresql-test-3-join-tcrbs 0/1 Init:0/1 0 5s
postgresql-test-3-join-tcrbs 0/1 PodInitializing 0 6s
postgresql-test-3-join-tcrbs 1/1 Running 0 7s
postgresql-test-3-join-tcrbs 0/1 Completed 0 10s
postgresql-test-3 0/1 Pending 0 0s
postgresql-test-3 0/1 Init:0/1 0 0s
postgresql-test-3 0/1 PodInitializing 0 0s
postgresql-test-3 0/1 Running 0 10s
postgresql-test-3 1/1 Running 0 10s
postgresql-test-3-join-tcrbs 0/1 Terminating 0 22s
Vérifier que les données soient toujours présentes.
kubectl cnpg psql postgresql-test -- -c "SELECT count(*) FROM t1;"
Vérifier la version de PostgreSQL.
kubectl cnpg psql postgresql-test -- -c "show server_version;"
But : Effectuer une montée de version de l’opérateur CNPG.
Nous avons déployé la version 1.27.1 de l’opérateur.
Nous allons nous intéresser à la manière de le mettre à jour.
Lorsqu’une nouvelle version de l’opérateur est déployée, un nouveau
Pod se crée. Lorsque celui-ci est prêt, l’ancien opérateur
est tout simplement supprimé. Cette mise à jour déclenche également la
mise à jour d’un composant présentant dans les Pods des
instances PostgreSQL.
Lorsqu’un Pod PostgreSQL est déployé, un
InitContainer est créé en amont et permet de récupérer du
code correspondant à l’instance-manager. Il permet de
contrôler l’instance, son cycle, ses redémarrages, etc. La version de ce
manager est étroitement liée à la version de l’opérateur.
Pour information, c’est ce processus qui va lancer PostgreSQL et qui
aura le pid 1 dans le Pod.
cat /proc/1/cmdline
/controller/manager instance run--status-port-tls--log-level=info
Attention si vous avez utilisé votre opérateur pour
déployer plusieurs instances PostgreSQL, lorsque vous mettez à jour
l’opérateur, tous les Pods seront, mis à
jour en même temps (ou quasiment). Il y aura donc une coupure de service
pour chaque instance. C’est le fonctionnement par défaut.
Dans une première console, lancer la commande
watchsuivante :
watch kubectl get pod
Dans une seconde console, lancer la commande
watchsuivante :
watch kubectl get pod -n cnpg-system
Dans une autre console, appliquer les fichiers YAML correspondant à la version
1.28.0de l’opérateur.
kubectl apply --server-side -f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.28/releases/cnpg-1.28.0.yaml
Regarder ce qu’il se passe au niveau des différents
Pods(opérateur et PostgreSQL)
Les instances PostgreSQL déployées par l’opérateur sont redémarrées lors d’une montée de version de l’opérateur. Selon la configuration des instances, une opération manuelle sera nécessaire pour mettre à jour le primaire.
Il existe une méthode pour éviter ce comportement. Cependant elle ne
garantit pas le critère immuable que devrait suivre un
Pod.
À titre d’information, voici la méthode à suivre pour y parvenir.
Pour cela il faut modifier la configuration de l’opérateur en passant le
paramètre ENABLE_INSTANCE_MANAGER_INPLACE_UPDATES à
true. Cela permettra de mettre à jour le
manager sans pour autant redémarrer le Pod
complet. Cette configuration doit être faite dans un objet
ConfigMap.
But : Découvrir des fonctionnalités plus complexes.
Il est possible de programmer des sauvegardes régulières avec la
ressource ScheduledBackup.
Voici un exemple de définition qui permet de déclencher une
sauvegarde appelée backup-every-day tous les jours à 16h00
pour le cluster postgresql-restored-demo :
apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
name: backup-every-day
spec:
schedule: "0 42 18 * * *"
backupOwnerReference: self
cluster:
name: postgresql-restored-demo
method: plugin
pluginConfiguration:
name: barman-cloud.cloudnative-pg.ioAttention, l’option schedule prend bien six paramètres
(le premier étant les secondes), contrairement au CronJob
dans Kubernetes ou aux lignes de /etc/crontab qui en prenne
que cinq.
Créer le fichier
~/backup-every-day.yamlavec le contenu ci-dessus en modifiant l’heure d’exécution pour que la sauvegarde s’exécute dans 5 à 10 minutes.
Créer l’objet
ScheduledBackupaveckubectl.
kubectl apply -f ~/backup-every-day.yaml
scheduledbackup.postgresql.cnpg.io/backup-every-day created
Suivez les traces de l’opérateur avec
kubectl logs -n cnpg-system cnpg-controller-manager-6b9f78f594-hd5qq | grep backup | jq. Vous devriez voir le déclenchement de la sauvegarde.
kubectl logs -n cnpg-system cnpg-controller-manager-6b9f78f594-hd5qq | grep backup | jq
{
"level": "info",
"ts": "2025-12-15T18:42:00.095050921Z",
"msg": "Starting backup",
"controller": "backup",
"controllerGroup": "postgresql.cnpg.io",
"controllerKind": "Backup",
"Backup": {
"name": "backup-every-day-20251215184200",
"namespace": "default"
},
"namespace": "default",
"name": "backup-every-day-20251215184200",
"reconcileID": "65490b4b-415b-41d2-be86-b1bab33de9b4",
"cluster": "postgresql-restored-demo",
"pod": "postgresql-restored-demo-1"
}Vous verrez alors la sauvegarde sur votre emplacement de stockage S3.
Il existe plusieurs méthodes pour mettre en place une réplication synchrone. Le but ici n’est pas de les évoquer ni de les comparer, mais simplement de voir le principe de la configuration.
Pour l’exemple, nous mettrons en place la méthode par
Quorum.
Modifier la configuration de l’instance en rajoutant le bloc suivant à votre fichier
~/postgresql-restored-demo.yaml.
kubectl apply -f postgresql.yaml
cluster.postgresql.cnpg.io/postgresql-demo configured
Vérifier que la réplication est synchrone en regardant le champ
sync_statede la vuepg_stat_replicationde l’instance primaire.
postgres=# select * from pg_stat_replication\gx
-[ RECORD 1 ]----+------------------------------
pid | 1210
usesysid | 16386
usename | streaming_replica
application_name | postgresql-restored-demo-1
client_addr | 10.244.3.29
client_hostname |
client_port | 51518
backend_start | 2025-12-15 18:29:04.335147+00
backend_xmin |
state | streaming
sent_lsn | 0/D000000
write_lsn | 0/D000000
flush_lsn | 0/D000000
replay_lsn | 0/D000000
write_lag |
flush_lag |
replay_lag |
sync_priority | 1
sync_state | quorum
reply_time | 2025-12-15 18:52:30.849357+00sync_state est bien à quorum.
Plus d’informations sur la documentation.
Pour voir comment une application peut se connecter à une instance, nous allons déployer pgAdmin dans le cluster Kubernetes.
Ouvrir une nouvelle session SSH. Passer en tant qu’utilisateur
dalibo.
Créer le fichier
~/pgadmin.yamlavec le contenu suivant et le déployer dans le cluster Kubernetes.
apiVersion: apps/v1
kind: Deployment
metadata:
name: pgadmin
spec:
replicas: 1
selector:
matchLabels:
app: pgadmin
template:
metadata:
labels:
app: pgadmin
spec:
containers:
- name: pgadmin
image: dpage/pgadmin4
ports:
- containerPort: 80
env:
- name: PGADMIN_DEFAULT_EMAIL
value: admin@example.com
- name: PGADMIN_DEFAULT_PASSWORD
value: adminkubectl apply -f ~/pgadmin.yaml
deployment.apps/pgadmin created
Récupérer le nom du
PodpgAdmin déployé, et lancer la commande suivante :
kubectl port-forward --address 0.0.0.0 pgadmin-*****-***** 8888:80
Cette commande permet de forwarder le trafic entrant sur le
port TCP 8888 de la machine vers le port 80 du Pod pgAdmin,
rendant ainsi accessible l’application. Cette méthode reste valide pour
des démonstrations, n’allez pas mettre ça en production :).
Accéder à l’interface de pgAdmin via votre navigateur
http://adresseippublique:8888et connectez vous à l’interface (admin@example.com / admin).
L’adresse IP publique de la machine peut être retrouvée avec la commande suivante :
ip -f inet addr show ens2
2: ens2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
altname enp0s2
inet 51.158.67.253/32 metric 100 scope global dynamic ens2
valid_lft 842sec preferred_lft 842sec
Créer une nouvelle connexion avec les informations suivantes :
postgresql-demo (Onglet General) ;postgresql-demo-rw (Onglet
Connection) ;5432 ;app;Secret;Save.Créer une nouvelle connexion avec cette fois-ci
postgresql-demo-rocomme paramètreHost name/addresset créer une tableCREATE TABLE ma_table (i int);.
Cette commande ne pourra pas être exécutée comme le
Service renvoie sur une instance secondaire qui est
nécessairement en lecture seule. Le message d’erreur sera :
Vous avez maintenant l’application pgAdmin déployée dans Kubernetes
qui a accès à l’instance postgresql-demo. L’exemple
ci-dessus montre comment une application peut accéder à une base de
données en utilisant la ressource Service prévue à cet
effet. Application et base se trouvent toutes deux dans Kubernetes.
Accéder à l’instance PostgreSQL depuis une application externe (i.e
déployée ailleurs) est plus complexe, demande le déploiement d’autres
ressources … mais ne sera pas traité dans ce workshop.