angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

PostgreSQL backup avec Docker SDK pour Python

Évitez les scripts Bash complexes. Écrivez vos scripts en Python !

9 mars 2023
post main image
https://www.pexels.com/nl-nl/@pixabay/

Ceci est un court billet sur la sauvegarde d'une base de données Dockerized PostgreSQL . Pour accéder à la base de données, nous lançons généralement un script Bash sur l'hôte, avec des commandes comme :

docker exec -t <container> bash -c '<command>'

Dans ce billet, nous remplaçons notre script Bash par un script Python . Pourquoi ? Parce que nous connaissons Python et que la programmation en Bash peut prendre du temps. Bien que nous puissions utiliser 'subprocess' pour exécuter toutes les commandes système ici, nous incluons le SDK Docker pour Python et obtenons beaucoup d'extras. Comme toujours, j'utilise Ubuntu (22.04).

Opération de sauvegarde

Nous allons effectuer une sauvegarde dans les étapes suivantes :

  • Sauvegarde de la base de données dans un fichier temporaire à l'intérieur du conteneur.
  • Copier le fichier de sauvegarde à l'extérieur du conteneur.
  • Suppression du fichier temporaire.
  • Affichage de l'heure de l'opération de sauvegarde.

Le script de sauvegarde Bash

Le script de sauvegarde Bash est présenté ci-dessous. Il vérifie s'il y a des erreurs pendant l'opération de sauvegarde et de copie. Le script démarre avec les paramètres du conteneur et de la base de données.

#!/usr/bin/bash
# backup.sh
pg_container=8c49633bda68
pg_user=postgres
db_name=my_db
db_user=postgres

# start
SECONDS=0

# backupfile inside docker container
tmp_backupfile=/tmp/${db_name}.pg_dump
# backupfile on the host
backupfile=${db_name}.pg_dump.`date +%Y%m%d"_"%H%M%S`.sql

echo "tmp_backupfile = ${tmp_backupfile}"
echo "backupfile = ${backupfile}"

echo "removing previous tmp_backupfile ..."
docker exec -t ${pg_container} bash -c 'rm -f -- ${0}' ${tmp_backupfile}

echo "dumping database to tmp_backupfile ..."
docker exec -t --user ${pg_user} ${pg_container} bash -c 'pg_dump ${0} -Fc -U ${1} -f ${2}' ${db_name} ${pg_user} ${tmp_backupfile}
exit_code=$?
if [ $exit_code -ne 0 ] ; then
    echo "dump error, exit_code = ${exit_code}"
    exit $exit_code
fi

echo "copying tmp_backupfile to backupfile ..."
docker cp "${pg_container}":${tmp_backupfile} ${backupfile}
exit_code=$?
if [ $exit_code -ne 0 ] ; then
    echo "copy error, exit_code = ${exit_code}"
    exit $exit_code
fi

echo "removing tmp_backupfile ..."
docker exec -t ${pg_container} bash -c 'rm -f -- ${0}' ${tmp_backupfile}

fsize=`stat --printf="%s" ${backupfile}`
elapsed_secs=$SECONDS

echo "ready, backupfile = ${backupfile}, size = ${fsize}, seconds = ${elapsed_secs}"

Le script de sauvegarde Python

Pour le script Python , nous installons d'abord le SDK Python pour Docker (dans un virtual environment) :

pip install docker

Le script de sauvegarde Python est présenté ci-dessous. Il vérifie les erreurs au cours de l'opération de sauvegarde et de copie.

# backup.py
import datetime
import logging
import os
import subprocess
import sys
import time

import docker

def get_logger():
    logger_format = '[%(asctime)s] [%(levelname)s] %(message)s'
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)
    # console
    console_handler = logging.StreamHandler(sys.stdout)
    console_handler.setLevel(logging.DEBUG)
    console_handler.setFormatter(logging.Formatter(logger_format))
    logger.addHandler(console_handler)                                            
    return logger

logger = get_logger()

class DockerUtils:
    def __init__(
        self,
        container=None,
    ):
        self.client = docker.from_env()
        self.container = self.client.containers.get(container)

    def docker_exec(self, cmd, **kwargs):
        return self.container.exec_run(cmd, **kwargs)

    def remove_container_file(self, f):
        r = self.docker_exec('ls ' + f)
        if r.exit_code == 0:
            r = self.docker_exec('rm ' + f)

    def docker_cp_from_container(self, src, dst):
        r = subprocess.run(['docker', 'cp', self.container.short_id + ':' + src, dst])
        return r

def main():
    pg_container = '8c49633bda68'
    pg_user = 'postgres'
    db_name = 'my_db'
    db_user = 'postgres'

    # start
    dt_start = datetime.datetime.now()

    # backupfile inside docker container
    tmp_backupfile = os.path.join('/tmp/', db_name + '.pg_dump')
    # backupfile on the host
    backupfile = './' + db_name + '.pg_dump.' + datetime.datetime.now().strftime('%Y%m%d_%H%M%S')

    logger.debug('tmp_backupfile = {}'.format(tmp_backupfile))
    logger.debug('backupfile = {}'.format(backupfile))

    # instantiate and set container
    du = DockerUtils(container=pg_container)

    logger.debug('removing previous tmp_backupfile ...')
    du.remove_container_file(tmp_backupfile)

    logger.debug('dumping database to tmp_backupfile ...')
    cmd = 'pg_dump {0} -Fc -U {1} -f {2}'.format(db_name, db_user, tmp_backupfile)
    kwargs = {'user': pg_user}
    r = du.docker_exec(cmd, **kwargs)
    if r.exit_code != 0:
        logger.error('dump error: {}'.format(str(r.output.decode('utf-8'))))
        sys.exit()
    
    logger.debug('copying tmp_backupfile to backupfile ...')
    r = du.docker_cp_from_container(tmp_backupfile, backupfile)
    if r.returncode != 0:
        logger.error('copy error = {}'.format(r.returncode))
        sys.exit()

    logger.debug('removing tmp_backupfile ...')
    du.remove_container_file(tmp_backupfile)

    fsize = os.stat(backupfile).st_size
    elapsed_secs = int((datetime.datetime.now() - dt_start).total_seconds())

    logger.info('ready, backupfile = {}, size = {}, seconds = {}'.format(backupfile, fsize, elapsed_secs))

if __name__ == "__main__":
    main()

Exécution sans virtual environment

Mais attendez, avec Python nous avons besoin d'un virtual environment. Pour éviter cela, nous pouvons créer un exécutable en utilisant PyInstaller. Installez d'abord PyInstaller :

pip install pyinstaller

Créez ensuite l'exécutable à l'aide de la commande suivante :

pyinstaller --onefile backup.py

Cela créera un exécutable dans le répertoire './dist' :

dist/backup

Nous pouvons l'exécuter et placer cet exécutable dans notre chemin d'accès.

Résumé

Nous n'avions pas vraiment besoin du SDK Docker pour Python ici, nous aurions pu utiliser 'subprocess', mais en l'incluant nous pouvons faire beaucoup d'autres choses aussi.

Il n'y a pas beaucoup de différences entre le script Bash et le script Python . Il est facile d'écrire quelques lignes en Bash. Mais dès que l'on veut un peu plus de fonctionnalités et de contrôle, cela peut prendre beaucoup de temps. À moins d'être un gourou de Bash, il faut chercher des commandes, étudier comment écrire des fonctions, tester des choses. Lorsque l'on écrit un script Python , il faut toujours connaître les commandes Linux , mais on peut se limiter aux quelques fonctions dont on a besoin. Pour le reste, nous pouvons utiliser nos connaissances de Python .

Liens / crédits

Docker - docker cp
https://docs.docker.com/engine/reference/commandline/cp

Docker SDK for Python
https://docker-py.readthedocs.io

Dockerize & Backup A Postgres Database
https://dev.to/mattcale/dockerize-backup-a-postgres-database-2b1l

PostgreSQL - pg_dump
https://www.postgresql.org/docs/current/app-pgdump.html

PyInstaller
https://pyinstaller.org

Why is pg_restore segfaulting in Docker?
https://stackoverflow.com/questions/63934856/why-is-pg-restore-segfaulting-in-docker

En savoir plus...

Docker

Laissez un commentaire

Commentez anonymement ou connectez-vous pour commenter.

Commentaires

Laissez une réponse

Répondez de manière anonyme ou connectez-vous pour répondre.