Aller au contenu

Base de données

Ce document décrit comment installer les bases de données PostgreSQL, MongoDB et Redis au sein d'un cluster Kubernetes. Plusieurs instances de ces bases sont déployées afin d'assurer la haute disponibilité des données.

Prérequis

  • Avoir réalisé le guide Configurer Kubernetes pour Botalista
  • Helm doit être installé (requis pour MongoDB et Redis)

PostgreSQL

La mise en place des bases de données PostgreSQL est effectuée à l'aide de kubegres, un opérateur Kubernetes qui met en place automatiquement des instances de réplications.

Installation

Installer kubegres via son fichier manifest

Bash
kubectl apply -f https://raw.githubusercontent.com/reactive-tech/kubegres/v1.18/kubegres.yaml

Créer un espace de nom postgres ainsi qu'un secret qui contient le mot de passe du super user et du replication user.

Bash
kubectl create ns postgres
cat <<EOF > secret-postgres.yaml
apiVersion: v1
kind: Secret
metadata:
  name: postgres-secret
  namespace: postgres
type: Opaque
stringData:
  superUserPassword: superUserPassword
  replicationUserPassword: replicationUserPassword
EOF
kubectl create -f secret-postgres.yaml

Tip

Il est possible de définir n'importe quel nom pour les clefs superUserPassword et replicationUserPassword.

Si souhaité, il est possible de définir un script bash primary_init_script.sh qui sera exécuté la première fois qu'une instance primaire est créée. Ceci permet de créer en avance les bases de données et utilisateurs.

Créer un fichier postgres-conf.yaml avec le contenu suivant :

YAML
apiVersion: v1
kind: ConfigMap
metadata:
  name: mypostgres-conf
  namespace: postgres

data:

  primary_init_script.sh: |
    #!/bin/bash
    set -e

    dt=$(date '+%d/%m/%Y %H:%M:%S');
    echo "$dt - Running init script the 1st time Primary PostgreSql container is created...";

    POSTGRES_USER="postgres"
    export PGPASSWORD=$POSTGRES_PASSWORD

    echo "$dt - Running: psql -v ON_ERROR_STOP=1 --username $POSTGRES_USER";

    psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL
    CREATE USER backup_user;
    CREATE DATABASE cit_geocoding_db;
    CREATE DATABASE cit_per_db;
    EOSQL

    echo "$dt - Init script is completed";

Appliquer cette ConfigMap avec kubectl apply -f postgres-conf.yaml.

Les bases de données Postgres de Botalista nécessitent l'extension Postgis. Il faut donc créer un objet Kubegres à partir d'une image postgis, qui utilise longhorn-local comme StorageClass et mypostgres-conf comme ConfigMap.

Bash
nano postgres.yaml
YAML
apiVersion: kubegres.reactive-tech.io/v1
kind: Kubegres
metadata:
  name: postgres
  namespace: postgres

spec:
  replicas: 3
  image: postgis/postgis:16-3.4

  database:
    size: 25Gi
    storageClassName: longhorn-local

  customConfig: mypostgres-conf

  scheduler:
    affinity:
      nodeAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
          nodeSelectorTerms:
            - matchExpressions:
                - key: node-role.kubernetes.io/db-host
                  operator: Exists

      podAntiAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
                - key: app
                  operator: In
                  values:
                    - postgres
            topologyKey: kubernetes.io/hostname

  env:
    - name: POSTGRES_PASSWORD
      valueFrom:
        secretKeyRef:
          name: postgres-secret
          key: superUserPassword

    - name: POSTGRES_REPLICATION_PASSWORD
      valueFrom:
        secretKeyRef:
          name: postgres-secret
          key: replicationUserPassword
Bash
kubectl apply -f postgres.yaml

La création de l'objet Kubegres va créer 3 instances de PostgreSQL, avec un service pour accéder à l'instance principale et un autre pour celles de réplication.

Configuration

Note

Cette section contient la marche à suivre pour créer les bases de données des modules de Botalista durant la réalisation de mon travail de Bachelor. Cette marche à suivre n'est pas forcément pertinente dans un autre contexte.

Afin de créer les bases de données et utilisateurs requis par Botalista, il faut exécuter un shell bash au sein du pod de l'instance maître.

L'instance maître devrait correspondre au pod postgres-1-0 après l'installation, néanmoins il est recommandé de vérifier cela en vérifiant l'endpoint correspondant au service postgres.

Bash
$ kubectl describe service/postgres
Name:              postgres
Namespace:         postgres
Labels:            app=postgres
                   replicationRole=primary
Annotations:       <none>
Selector:          app=postgres,replicationRole=primary
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                None
IPs:               None
Port:              <unset>  5432/TCP
TargetPort:        5432/TCP
Endpoints:         192.168.144.90:5432
Session Affinity:  None
Events:            <none>

$ kubectl get pod -o wide
NAME           READY   STATUS    ...   IP               NODE                ...
postgres-1-0   1/1     Running   ...   192.168.144.90   isc-botalista-07    ...
postgres-2-0   1/1     Running   ...   192.168.39.231   isc-botalista-08    ...
postgres-3-0   1/1     Running   ...   192.168.250.15   isc-botalista-06    ...

Exécuter ensuite un bash interactif au pod dont l'IP correspond à l'endpoint du service postgres (ici postgres-1-0) afin d'importer les fichiers dumps.

Les commandes ci-dessous vont tout d'abord ajouter une copie des fichiers dump dans un répertoire du pod postgres, pour ensuite les importer dans la base de données.

Bash
export DUMP_FOLDER=<dump_folder>

# Create a copy of the dump files in the /tmp folder of the pod
for dump_file in $DUMP_FOLDER/*.sql; \
do kubectl exec -i pod/postgres-1-0 -- \
bash -c "cat > /tmp/$(basename -- $dump_file)" < $dump_file; done;

# Start a shell inside the pod
kubectl exec -it pod/postgres-1-0 -- bash

# Add a leading space to prevent saving the command in the bash history
 export PGPASSWORD="<your_password>"

# Import the dump files
# psql -U postgres -d <database> -f <dump.sql>
psql -U postgres -d chat_bota_acq_db -f chat_bota_acq_prd.sql
...

Configuration spécifique pour les tables business_cit :

Bash
# Create databases, users and grant permissions.
psql -U postgres

CREATE DATABASE business_cit_geocoding_db;
CREATE DATABASE business_cit_per_db;

CREATE USER backup_user;
CREATE USER business_cit_geocoding_user WITH PASSWORD '<password>';
CREATE USER business_cit_per_user WITH PASSWORD '<password>';

GRANT SELECT ON ALL TABLES IN SCHEMA public TO backup_user;
GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO backup_user;

GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO business_cit_geocoding_user;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO business_cit_geocoding_user;

GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO business_cit_per_user;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO business_cit_per_user;
exit

# Import dumps
psql -U postgres -f /tmp/business_cit_geocoding_prd.sql -d business_cit_geocoding_db
psql -U postgres -f /tmp/business_cit_per_prd.sql -d business_cit_per_db

MongoDB

MongoDB est installé avec Helm.

Installation de l'opérateur

Bash
1
2
3
4
helm repo add mongodb https://mongodb.github.io/helm-charts
helm repo update
helm install community-operator mongodb/community-operator -n mongodb --create-namespace
kubectl config set-context --current --namespace=mongodb

Création du Replica Set

Une fois l'opérateur installé, créer le fichier mongodbcommunity_cr.yaml suivant

YAML
apiVersion: mongodbcommunity.mongodb.com/v1
kind: MongoDBCommunity
metadata:
  name: mongodb
  namespace: mongodb
spec:
  members: 3
  type: ReplicaSet
  version: "6.0.5"
  security:
    authentication:
      # SCRAM-SHA-1 seems required, but should but avoided
      modes: ["SCRAM", "SCRAM-SHA-1"]
  users:
    # Admin user
    - name: bota-admin
      db: admin
      passwordSecretRef:
        name: admin-password
      roles:
        - name: clusterAdmin
          db: admin
        - name: userAdminAnyDatabase
          db: admin
      scramCredentialsSecretName: admin-scram
    # Botalista users
    - name: chat_bota_exp_user
      db: chat_bota_exp_db
      passwordSecretRef:
        name: chat-password
      roles:
        - name: readWrite
          db: chat_bota_exp_db
      scramCredentialsSecretName: chat-scram
    - name: g_bota_exp_user
      db: g_bota_exp_db
      passwordSecretRef:
        name: g-password
      roles:
        - name: readWrite
          db: g_bota_exp_db
      scramCredentialsSecretName: g-scram

  additionalMongodConfig:
    storage.wiredTiger.engineConfig.journalCompressor: zlib

  statefulSet:
    spec:
      template:
        spec:
          affinity:
            nodeAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
                nodeSelectorTerms:
                  - matchExpressions:
                      - key: node-role.kubernetes.io/db-host
                        operator: Exists

            podAntiAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
                - labelSelector:
                    matchExpressions:
                      - key: app
                        operator: In
                        values:
                          - mongodb-svc
                  topologyKey: kubernetes.io/hostname

      volumeClaimTemplates:
        - metadata:
            name: data-volume
          spec:
            accessModes: ["ReadWriteOnce"]
            storageClassName: longhorn-local
            resources:
              requests:
                storage: 2Gi # You may need to increase the storage size
        - metadata:
            name: logs-volume
          spec:
            accessModes: ["ReadWriteOnce"]
            storageClassName: longhorn-local
            resources:
              requests:
                storage: 1Gi # You may need to increase the storage size

Ce fichier permet de créer un Replica Set MongoDB avec 3 membres, tout en y définissant des utilisateurs à créer à partir d'un secret Kubernetes.

YAML
1
2
3
4
5
6
7
8
9
# This secret can be deleted after creating the mongodb cluster
apiVersion: v1
kind: Secret
metadata:
  name: admin-password
  namespace: mongodb
type: Opaque
stringData:
  password: superUserPassword

Une fois les secrets créés, le Replica Set peut à son tour être créé :

Bash
kubectl apply -f mongodbcommunity_cr.yaml

Info

Il est recommandé de supprimer les secrets Kubernetes une fois le Replica Set opérationnel.

La chaîne de connexion suivante permet d'accéder au Replica Set créé :

Bash
# mongodb://<replica-set-name>-svc.<my-namespace>:27017/?replicaSet=<replica-set-name>
mongodb://mongodb-svc.mongodb:27017/?replicaSet=mongodb

Se connecter via le service sans spécifier le Replica Set peut provoquer des erreurs pour les requêtes en écritures, car le service à lui seul ne permet pas de garantir une connexion à l'instance principale.

Redis

Redis Sentinel est utilisé pour assurer la haute disponibilité de la base de données. L'installation est effectuée avec Helm.

Installation

Tout d'abord créer les fichiers values.yaml et redis-password pour configurer Redis.

Bash
cat <<EOF > values.yaml
global:
  storageClass: longhorn-local
auth:
  usePasswordFiles: true
  existingSecret: redis-password-secret
master:
  persistence:
    size: 1Gi
  resources:
    requests:
      cpu: 50m
      memory: 256Mi
    limits:
      cpu: 250m
      memory: 512Mi
replica:
  replicaCount: 3
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: node-role.kubernetes.io/db-host
                operator: Exists

    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchExpressions:
              - key: app.kubernetes.io/name
                operator: In
                values:
                  - redis
          topologyKey: kubernetes.io/hostname
sentinel:
  enabled: true
  masterSet: redis
  persistence:
    size: 100Mi
  resources:
    requests:
      cpu: 50m
      memory: 256Mi
    limits:
      cpu: 250m
      memory: 512Mi
EOF

nano redis-password

Le fichier redis-password contient uniquement le mot de passe souhaité.

Créer ensuite un espace de nom redis qui contiendra le secret ainsi que les instances de Redis.

Bash
# Create the namespace
kubectl create ns redis && kubectl config set-context --current --namespace=redis

# Create the secret
kubectl create secret generic redis-password-secret --from-file=redis-password

# Install Redis with Helm
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm install redis bitnami/redis -f values.yaml

Connexion à Redis

Seulement 1 service Kubernetes est créé lorsque Sentinel est activé. Il dispose de deux ports :

  • Le port 6379 pour les requêtes en lecture
  • Le port 26379 pour l'accès à Sentinel

Pour les commandes en écriture, il faut se connecter à Sentinel et récupérer le maître actuel avec la commande suivante :

Bash
# SENTINEL get-master-addr-by-name <name of your MasterSet>
SENTINEL get-master-addr-by-name redis

Vérification

La commande suivante permet de s'assurer que chaque replica de pod contient une instance de Redis et de Sentinel :

Bash
1
2
3
4
5
6
7
8
$ kubectl top pod --containers
POD            NAME       CPU(cores)   MEMORY(bytes)
redis-node-0   redis      18m          3Mi
redis-node-0   sentinel   15m          3Mi
redis-node-1   redis      16m          3Mi
redis-node-1   sentinel   16m          3Mi
redis-node-2   redis      16m          3Mi
redis-node-2   sentinel   18m          3Mi

Réplication sans failover

La désactivation de Sentinel est envisageable si le mécanisme de failover avec Sentinel n'est pas souhaité ou nécessaire. Voici le fichier values.yaml modifié en conséquence:

YAML
global:
  storageClass: longhorn-local
auth:
  usePasswordFiles: true
  existingSecret: redis-password-secret
master:
  persistence:
    size: 1Gi
  resources:
    requests:
      cpu: 50m
      memory: 256Mi
    limits:
      cpu: 250m
      memory: 512Mi
replica:
  replicaCount: 2 # This was changed from 3 to 2
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: node-role.kubernetes.io/db-host
                operator: Exists

    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchExpressions:
              - key: app.kubernetes.io/name
                operator: In
                values:
                  - redis
          topologyKey: kubernetes.io/hostname
  persistence:
    size: 1Gi
  resources:
    requests:
      cpu: 50m
      memory: 256Mi
    limits:
      cpu: 250m
      memory: 512Mi
sentinel:
  enabled: false # This is now disabled

Cette configuration expose 2 services (port 6379) au lieu d'un seul :

  • service/redis-master: pour les requêtes en lecture/écriture
  • service/redis-replicas: pour les requêtes en lecture

Ressources

PostgreSQL :

MongoDB:

Redis :