0

Kubegres : l’opérateur Kubernetes clé en main pour PostgreSQL

twitterlinkedinmail

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 :

twitterlinkedinmail

David Baffaleuf

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.