Hello à toutes et tous !
Pour faire suite à l’article d’Emmanuel sur l’installation de PostgreSQL sur un cluster minikube local, aujourd’hui nous allons découvrir l’opérateur Kubegres qui permet de facilement déployer un cluster PostgreSQL avec Primary et Standby à l’intérieur de pods K8s, sans avoir à créer chaque brique une par une comme on devrait le faire avec un simple StatefulSet.
Pourquoi Kubegres apporte un vrai plus
En un mot : SIM-PLI-CI-TE !
Parce qu’il introduit un nouveau type d’objet :
kind: Kubegres
Les specs à l’intérieur de cet objet encapsulent déjà tout ce qui est nécessaire pour créer les StatefulSets, les ClusterIPs, le Physical Volumes et le PVC associé, la ConfigMap et les Pods. Pas besoin de tout créer à l’avance et le fichier de déploiement est beaucoup plus compact !
Installation de Kubegres
Nous utiliserons minikube pour montrer comment déployer Kubegres, je vous renvoie à l’article d’Emmanuel cité plus haut pour son installation. Comme nous allons devoir attribuer des fractions de ressource RAM et CPU entre les pods, j’activerai juste en plus la partie metrics-server :
$ minikube addons enable metrics-server
La première étape consiste à installer l’opérateur. Cette première étape va créer un nombre importants d’objets pour nous, comme en témoigne le contenu de son fichier de déploiement
$ curl --silent https://raw.githubusercontent.com/reactive-tech/kubegres/v1.16/kubegres.yaml \ | grep -w 'kind:' | awk -F':' '{print $2}' \ | sort -u ClusterRole ClusterRoleBinding ConfigMap ControllerManagerConfig CustomResourceDefinition Deployment Kubegres Namespace Role RoleBinding Service ServiceAccount
:
On déploie donc via kubectl:
$ kubectl apply -f https://raw.githubusercontent.com/reactive-tech/kubegres/v1.16/kubegres.yaml namespace/kubegres-system created customresourcedefinition.apiextensions.k8s.io/kubegres.kubegres.reactive-tech.io created serviceaccount/kubegres-controller-manager created role.rbac.authorization.k8s.io/kubegres-leader-election-role created clusterrole.rbac.authorization.k8s.io/kubegres-manager-role created clusterrole.rbac.authorization.k8s.io/kubegres-metrics-reader created clusterrole.rbac.authorization.k8s.io/kubegres-proxy-role created rolebinding.rbac.authorization.k8s.io/kubegres-leader-election-rolebinding created clusterrolebinding.rbac.authorization.k8s.io/kubegres-manager-rolebinding created clusterrolebinding.rbac.authorization.k8s.io/kubegres-proxy-rolebinding created configmap/kubegres-manager-config created service/kubegres-controller-manager-metrics-service created deployment.apps/kubegres-controller-manager created
Un namespace kubegres-system a été créé, dans lequel notre cluster pourra éventuellement s’inscrire. Cela dit, la bonne pratique consisterait à créer un namespace à part et laisser le controller-manager kubegres dans son namespace système, mais pour ne pas interférer avec mes autres namespaces, je vais volontairement rajouter le cluster dedans:
$ kubectl get all --namespace=kubegres-system NAME READY STATUS RESTARTS AGE pod/kubegres-controller-manager-794468bbff-bxzxk 2/2 Running 0 3m35s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubegres-controller-manager-metrics-service ClusterIP 10.100.182.92 <none> 8443/TCP 3m35s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/kubegres-controller-manager 1/1 1 1 3m35s NAME DESIRED CURRENT READY AGE replicaset.apps/kubegres-controller-manager-794468bbff 1 1 1 3m35s
Création du cluster en 15.2
Avant de créer le cluster PostgreSQL, il va falloir créer un secret qui va contenir les mots de passe du primaire et de sa standby.
$ vi postgres-secret.yaml (...) apiVersion: v1 kind: Secret metadata: name: mypostgres-secret namespace: kubegres-system type: Opaque stringData: superUserPassword: capdata replicationUserPassword: capdatarep $ kubectl apply -f postgres-secret.yaml -n kubegres-system secret/mypostgres-secret created
Enfin nous pouvons créer le cluster avec un seul fichier de déploiement:
apiVersion: kubegres.reactive-tech.io/v1 kind: Kubegres metadata: name: kpostgres namespace: kubegres-system spec: replicas: 2 image: postgres:15.2 port: 5432 database: size: 200Mi storageClassName: standard volumeMount: /var/lib/postgresql/data failover: isDisabled: false promotePod: "kpostgres-2-0" resources: limits: memory: "1Gi" cpu: "1" requests: memory: "500Mi" cpu: "0.5" probe: livenessProbe: exec: command: - sh - -c - exec pg_isready -U postgres -h $POD_IP failureThreshold: 10 initialDelaySeconds: 60 periodSeconds: 20 successThreshold: 1 timeoutSeconds: 15 readinessProbe: exec: command: - sh - -c - exec pg_isready -U postgres -h $POD_IP failureThreshold: 3 initialDelaySeconds: 5 periodSeconds: 5 successThreshold: 1 timeoutSeconds: 3 env: - name: POSTGRES_PASSWORD valueFrom: secretKeyRef: name: mypostgres-secret key: superUserPassword - name: POSTGRES_REPLICATION_PASSWORD valueFrom: secretKeyRef: name: mypostgres-secret key: replicationUserPassword
Remarques sur les sections :
– La spec database.size permet de dimensionner le PVC à une taille initiale, mais attention ! En fonction de la version de Kubernetes, il peut être compliqué de modifier cette valeur ensuite comme rapporté dans cet issue du github Kubegres.
– On ne créé ‘que’ 2 réplicas au sens StatefulSet, c’est à dire un primaire et une standby.
– La spec failover permet de désactiver ou d’activer le promote automatique de la standby, et de dire quel est le noeud préférentiel.
– 2 types de sondes : une pour décider de redémarrer le container en cas d’échec de connexion (livenessProbe), et une autre pour autoriser les connexions (readinessProbe). Le fonctionnement du failover est expliqué plus loin.
– resources permet de limiter l’utilisation des pods à des plages de CPU et RAM en jouant avec requests et limit.
– Il est aussi possible d’utiliser l’anti-affinité pour empêcher les pods primary et standby d’atterrir sur le même node K8s. On ne le fait évidemment pas dans notre exemple car sur minikube cela n’aurait aucun sens.
La liste complète des propriétés peut être retrouvée sur la page de documentation de Kubegres.
Une fois le cluster déployé, nous pouvons vérifier tous les nouveaux objets qui ont été créés:
$ kubectl apply -f postgres-cluster.yaml -n kubegres-system kubegres.kubegres.reactive-tech.io/kpostgres created $ kubectl get all -n kubegres-system NAME READY STATUS RESTARTS AGE pod/kpostgres-1-0 1/1 Running 2 5d16h pod/kpostgres-2-0 1/1 Running 3 5d16h pod/kubegres-controller-manager-794468bbff-bxzxk 2/2 Running 13 (23m ago) 5d18h NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kpostgres ClusterIP None <none> 5432/TCP 5d16h service/kpostgres-replica ClusterIP None <none> 5432/TCP 5d16h service/kubegres-controller-manager-metrics-service ClusterIP 10.100.182.92 <none> 8443/TCP 5d18h NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/kubegres-controller-manager 1/1 1 1 5d18h NAME DESIRED CURRENT READY AGE replicaset.apps/kubegres-controller-manager-794468bbff 1 1 1 5d18h NAME READY AGE statefulset.apps/kpostgres-1 1/1 5d16h statefulset.apps/kpostgres-2 1/1 5d16h
En plus du controller manager, nous avons donc 2 nouveaux pods, 2 services ClusterIP et 2 StatefulSets, un pour chaque instance PostgreSQL.
Nous pouvons à partir de là tester la connexion au service (kpostgres), en démarrant un conteneur client ephemeral (kubectl run … –rm). On rappelle que kubegres ne créé par défaut que des Headless Services (ClusterIP sans adresse attribuée) pour éviter de les rendre visibles depuis l’extérieur du cluster. On partira toujours du principe que sur un cluster Kubernetes, tout est deployment / pod :
$ kubectl run postgresql-postgresql-client --rm --tty -i --restart='Never' \ --namespace kubegres-system --image postgres:15.2 \ --env="PGPASSWORD=capdata" --command -- psql \ --host kpostgres -U postgres -c "select version();" version -------------------------------------------------------------------------------- --------------------------------------------- PostgreSQL 15.2 (Debian 15.2-1.pgdg110+1) on x86_64-pc-linux-gnu, compiled by g cc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit (1 row) pod "postgresql-postgresql-client" deleted
Comportement en cas d’échec du serveur primaire
Le deploiement est configuré avec 2 sondes (liveness et readiness) qui toutes 2 utilisent la commande pg_isready pour vérifier la disponibilité des instances primaire et standby. D’ailleurs on peut le tester nous-mêmes pour véfifier le retour de la commande :
$ kubectl exec --tty --stdin -n kubegres-system kpostgres-1-0 -- pg_isready -U postgres && echo $? /var/run/postgresql:5432 - accepting connections 0
Notre Failover est paramétré sur automatique, avec une préférence pour repartir sur le noeud 2, on va simuler une perte du service en faisant un pg_ctl stop sur le premier pod:
$ kubectl exec --tty --stdin -n kubegres-system kpostgres-1-0 -- su postgres \ -c "pg_ctl stop -D /var/lib/postgresql/data/pgdata -m fast" waiting for server to shut down....command terminated with exit code 137
On voit que la reconnexion immédiate ne fonctionne pas tout de suite:
$ kubectl run postgresql-postgresql-client --rm --tty -i --restart='Never' --namespace kubegres-system --image postgres:15.2 --env="PGPASSWORD=capdata" --command -- psql --host kpostgres -U postgres -c "select inet_server_addr();" (...) psql: error: could not translate host name "kpostgres" to address: Temporary failure in name resolution pod "postgresql-postgresql-client" deleted pod kubegres-system/postgresql-postgresql-client terminated (Error)
D’après les logs le failover dure une dizaine de secondes seulement:
$ kubectl logs pod/kubegres-controller-manager-794468bbff-bxzxk -c manager -n kubegres-system --timestamps | grep 'FailOver:' 2023-04-19T20:11:22.687346601Z 1.6819350826873376e+09 INFO controllers.Kubegres FailOver: Deleting the failing Primary StatefulSet. {"Primary name": "kpostgres-1"} 2023-04-19T20:11:22.690961293Z 1.6819350826909401e+09 INFO controllers.Kubegres FailOver: Waiting before promoting a Replica to a Primary... {"Replica to promote": "kpostgres-2"} 2023-04-19T20:11:33.712923151Z 1.6819350937128017e+09 INFO controllers.Kubegres FailOver: Promoting Replica to Primary. {"Replica to promote": "kpostgres-2"} 2023-04-19T20:11:33.713030040Z 1.6819350937129502e+09 DEBUG events Normal {"object": {"kind":"Kubegres","namespace":"kubegres-system","name":"kpostgres","uid":"56c2a503-93f5-4bf8-8d0a-fe88cdaf2bde","apiVersion":"kubegres.reactive-tech.io/v1","resourceVersion":"95195"}, "reason": "FailOver", "message": "FailOver: Promoting Replica to Primary. 'Replica to promote': kpostgres-2"}
La nouvelle connexion indique que l’on a changé de host:
$ kubectl run postgresql-postgresql-client --rm --tty -i --restart='Never' --namespace kubegres-system --image postgres:15.2 --env="PGPASSWORD=capdata" --command -- psql --host kpostgres -U postgres -c "select inet_server_addr();" inet_server_addr ------------------ 172.17.0.6 (1 row) pod "postgresql-postgresql-client" deleted
Si on inventorie les objets, on voit qu’un nouveau Pod et StatefulSet ont été créés (AGE=43s):
$ kubectl get pods,svc,statefulset -n kubegres-system NAME READY STATUS RESTARTS AGE pod/kpostgres-2-0 1/1 Running 0 85s pod/kpostgres-3-0 1/1 Running 0 43s pod/kubegres-controller-manager-794468bbff-bxzxk 2/2 Running 18 (41m ago) 6d7h NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kpostgres ClusterIP None <none> 5432/TCP 6d5h service/kpostgres-replica ClusterIP None <none> 5432/TCP 6d5h service/kubegres-controller-manager-metrics-service ClusterIP 10.100.182.92 <none> 8443/TCP 6d7h NAME READY AGE statefulset.apps/kpostgres-2 1/1 6d5h statefulset.apps/kpostgres-3 1/1 43s
Paramétrer des sauvegardes
Pour pouvoir générer des sauvegardes de type pg_dumpall, il suffit de créer un PVC dédié et de rajouter une planification dans notre fichier de déploiement, qui à son tour va créer une ressource de type CronJob:
$ vi postgres-backup.yaml (...) apiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-backup-pvc namespace: kubegres-system spec: storageClassName: "standard" accessModes: - ReadWriteOnce resources: requests: storage: 200Mi $ kubectl apply -f postgres-backup.yaml persistentvolumeclaim/my-backup-pvc created $ kubectl get pvc -n kubegres-system NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE my-backup-pvc Bound pvc-b84656cd-904b-4a2b-89b1-e43ddb062be8 200Mi RWO standard 16s postgres-db-kpostgres-1-0 Bound pvc-03f2e981-a1aa-446e-8f63-e5164a59f742 200Mi RWO standard 6d6h postgres-db-kpostgres-2-0 Bound pvc-cbdf5810-e5e0-4950-8b06-5243f835be7d 200Mi RWO standard 6d6h postgres-db-kpostgres-3-0 Bound pvc-dffef05b-f7b6-4618-802f-5fbfff2a0cba 200Mi RWO standard 37m
Le CronJob est alors en place, on a programmé toutes les 5 minutes pour en voir passer un assez rapidement quand même :
$ kubectl get CronJob -n kubegres-system NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE backup-kpostgres */5 * * * * False 0 72s 3m38s
Pour gérer le backup, kubegres créé un nouveau pod à chaque fois, ce qui permet de contrôler le bon fonctionnement des sauvegardes:
$ kubectl get pods -n kubegres-system NAME READY STATUS RESTARTS AGE backup-kpostgres-28032310-97jqx 0/1 Completed 0 85s kpostgres-4-0 1/1 Running 0 5m2s kpostgres-5-0 1/1 Running 0 4m4s kubegres-controller-manager-794468bbff-bxzxk 2/2 Running 18 (99m ago) 6d8h $ kubectl logs backup-kpostgres-28032310-97jqx -n kubegres-system 19/04/2023 21:10:01 - Starting DB backup of Kubegres resource kpostgres into file: /var/lib/backup/kpostgres-backup-19_04_2023_21_10_01.gz 19/04/2023 21:10:01 - Running: pg_dumpall -h kpostgres-replica -U postgres -c | gzip > /var/lib/backup/kpostgres-backup-19_04_2023_21_10_01. gz 19/04/2023 21:10:01 - DB backup completed for Kubegres resource kpostgres into file: /var/lib/backup/kpostgres-backup-19_04_2023_21_10_01.gz
Conclusion
Kubegres apporte une grande aisance dans le déploiement de clusters PostgreSQL avec Streaming Replication. Il aurait été beaucoup plus fastidieux ici de tout créer à la main.
Nous ne l’avons pas abordé ici, mais il est aussi possible de customiser la configuration en réalisant son propre ConfigMap, créer d’autres PVC pour gérer les archives etc… Dans le prochain épisode, nous le comparerons à PGO, l’opérateur concurrent de CrunchyDB.
A bientôt et doucement sur les chocolats 🙂 !
Continuez votre lecture sur le blog :
- PGO : opérateurs kubernetes pour PostgreSQL, la suite ! (David Baffaleuf) [ContainerDevopsPostgreSQL]
- PostgreSQL sur la solution Kubernetes locale Minikube (Emmanuel RAMI) [ContainerPostgreSQL]
- Comparatif des gestionnaires de VIP dans un cluster Patroni : épisode 2 (VIP-MANAGER) (David Baffaleuf) [ContainerPostgreSQL]
- PGO : la suite (Sarah FAVEERE) [PostgreSQL]
- Comparatif des gestionnaires de VIP dans un cluster Patroni : épisode 1 (KEEPALIVED) (David Baffaleuf) [ContainerPostgreSQL]