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

PostgreSQL copia de seguridad con Docker SDK para Python

Evite los scripts Bash complejos. ¡Escribe tus scripts en Python!

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

Este es un breve post sobre la copia de seguridad de una base de datos Dockerized PostgreSQL . Para acceder a la base de datos, normalmente ejecutamos un script Bash en el host, con comandos como:

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

En este post sustituimos nuestro script Bash por un script Python . ¿Por qué? Porque conocemos Python y programar en Bash puede llevarnos mucho tiempo. Aunque podemos usar 'subprocess' para ejecutar todos los comandos del sistema aquí, incluimos el SDK Docker para Python y obtenemos un montón de extras. Como siempre, estoy ejecutando esto en Ubuntu (22.04).

Operación de copia de seguridad

Vamos a realizar una copia de seguridad en los pasos:

  • Copia de seguridad de la base de datos a un archivo temporal dentro del contenedor.
  • Copiar el fichero de copia de seguridad fuera del contenedor.
  • Eliminar el fichero temporal.
  • Mostrar la hora de la operación de copia de seguridad.

El script Bash de copia de seguridad

A continuación se muestra el script Bash de copia de seguridad. Comprueba si hay errores durante la operación de copia de seguridad y la operación de copia. El script comienza con nuestros parámetros de contenedor y base de datos.

#!/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}"

El script de copia de seguridad de Python

Para el script Python primero instalamos el SDK Python para Docker (en un virtual environment):

pip install docker

A continuación se muestra el script de copia de seguridad de Python . Comprueba si hay errores durante la operación de copia de seguridad y la operación de copia.

# 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()

Ejecutando sin virtual environment

Pero espera, con Python necesitamos un virtual environment. Para evitar esto, podemos crear un ejecutable utilizando PyInstaller. Primero instala PyInstaller:

pip install pyinstaller

Luego crea el ejecutable con el siguiente comando:

pyinstaller --onefile backup.py

Esto creará un ejecutable en el directorio './dist':

dist/backup

Podemos ejecutarlo y poner este ejecutable en nuestra ruta.

Resumen

Realmente no necesitamos el SDK Docker para Python aquí, podríamos haber usado 'subprocess', pero incluyéndolo podemos hacer muchas otras cosas también.

No hay mucha diferencia entre el script Bash y el script Python . Escribir algunas líneas en Bash es fácil. Pero tan pronto como queremos un poco más de funcionalidad y control, puede llegar a consumir mucho tiempo. A menos que seas un gurú de Bash, tienes que buscar comandos, estudiar cómo escribir funciones, probar cosas. Al escribir un script Python , todavía necesitamos conocimientos sobre los comandos Linux pero podemos limitar esto a las pocas funciones que necesitamos. Para el resto, podemos utilizar nuestros conocimientos de Python .

Enlaces / créditos

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

Leer más

Docker

Deje un comentario

Comente de forma anónima o inicie sesión para comentar.

Comentarios

Deje una respuesta.

Responda de forma anónima o inicie sesión para responder.