PostgreSQL backup avec Docker SDK pour Python
Évitez les scripts Bash complexes. Écrivez vos scripts en Python !

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
Récent
- Collecter et bloquer IP addresses avec ipset et Python
- Comment annuler des tâches avec Python Asynchronous IO (AsyncIO)
- Exécuter une commande Docker dans un conteneur Cron Docker
- Création d'un Captcha avec Flask, WTForms, SQLAlchemy, SQLite
- Multiprocessing, verrouillage des fichiers, SQLite et tests
- Envoi de messages à Slack à l'aide de chat_postMessage
Les plus consultés
- Flask RESTful API validation des paramètres de la requête avec les schémas Marshmallow
- Utiliser UUIDs au lieu de Integer Autoincrement Primary Keys avec SQLAlchemy et MariaDb
- Utilisation des Python's pyOpenSSL pour vérifier les certificats SSL téléchargés d'un hôte
- Connexion à un service sur un hôte Docker à partir d'un conteneur Docker
- Utiliser PyInstaller et Cython pour créer un exécutable Python
- SQLAlchemy : Utilisation de Cascade Deletes pour supprimer des objets connexes