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

PostgreSQL резервное копирование с Docker SDK для Python

Избегайте сложных сценариев Bash. Пишите свои сценарии на языке Python!

9 марта 2023
post main image
https://www.pexels.com/nl-nl/@pixabay/

Это небольшая заметка о резервном копировании базы данных Dockerized PostgreSQL . Для доступа к базе данных мы обычно запускаем на хосте сценарий Bash с такими командами, как:

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

В этом посте мы заменим наш сценарий Bash на сценарий Python . Почему? Потому что мы знаем Python и программирование на Bash может отнимать много времени. Хотя мы можем использовать 'subprocess' для запуска всех системных команд здесь, мы включаем Docker SDK для Python и получаем много дополнительных возможностей. Как всегда, я запускаю это на Ubuntu (22.04).

Операция резервного копирования

Мы будем выполнять резервное копирование по шагам:

  • Резервное копирование базы данных во временный файл внутри контейнера.
  • Скопируйте файл резервной копии за пределы контейнера.
  • Удалите временный файл.
  • Показать время выполнения операции резервного копирования.

Сценарий резервного копирования Bash

Ниже приведен сценарий резервного копирования Bash. Он проверяет наличие ошибок во время операции резервного копирования и операции копирования. Сценарий начинается с параметров нашего контейнера и базы данных.

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

Сценарий резервного копирования Python

Для сценария Python мы сначала устанавливаем Python SDK для Docker (в virtual environment):

pip install docker

Ниже приведен сценарий резервного копирования Python . Он проверяет наличие ошибок во время операции резервного копирования и операции копирования.

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

Запуск без virtual environment

Но подождите, с Python нам нужен virtual environment. Чтобы избежать этого, мы можем создать исполняемый файл с помощью PyInstaller. Сначала установите PyInstaller:

pip install pyinstaller

Затем создайте исполняемый файл с помощью следующей команды:

pyinstaller --onefile backup.py

Это создаст исполняемый файл в каталоге './dist':

dist/backup

Мы можем запустить его и поместить этот исполняемый файл в наш путь.

Резюме

Нам не очень нужен был здесь Docker SDK для Python , мы могли бы использовать 'subprocess', но включив его, мы можем сделать много других вещей.

Разница между сценарием Bash и сценарием Python невелика. Написать несколько строк в Bash очень просто. Но как только мы захотим немного больше функциональности и контроля, это может стать очень трудоемким. Если вы не гуру Bash, вам придется искать команды, изучать, как писать функции, тестировать. При написании скрипта Python нам все еще нужны знания о командах Linux , но мы можем ограничиться несколькими нужными нам функциями. Для остальных мы можем использовать наши знания Python .

Ссылки / кредиты

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

Подробнее

Docker

Оставить комментарий

Комментируйте анонимно или войдите в систему, чтобы прокомментировать.

Комментарии

Оставьте ответ

Ответьте анонимно или войдите в систему, чтобы ответить.