PostgreSQL back-up met Docker SDK voor Python
Vermijd complexe Bash-scripts. Schrijf uw scripts in Python!
Dit is een kort bericht over de back-up van een Dockerized PostgreSQL database. Om toegang te krijgen tot de database draaien we meestal een Bash-script op de host, met commando's als:
docker exec -t <container> bash -c '<command>'
In deze post vervangen we ons Bash-script door een Python -script. Waarom? Omdat we Python kennen en programmeren in Bash tijdrovend kan zijn. Hoewel we hier 'subprocess' kunnen gebruiken om alle systeemcommando's uit te voeren, nemen we de Docker SDK voor Python op en krijgen we veel extra's. Zoals altijd draai ik dit op Ubuntu (22.04).
Back-up operatie
We zullen in de stappen een back-up uitvoeren:
- Back-up database naar een tijdelijk bestand binnen de container.
- Kopieer het back-up bestand buiten de container.
- Verwijder het tijdelijke bestand.
- Toon de tijd van de back-up operatie.
Het Bash back-up script
Hieronder staat het Bash back-up script. Het controleert op fouten tijdens de back-upoperatie en de kopieeroperatie. Het script begint met onze container en database parameters.
#!/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}"
Het Python back-up script
Voor het Python script installeren we eerst de Python SDK voor Docker (in een virtual environment):
pip install docker
Hieronder staat het Python back-up script. Het controleert op fouten tijdens de back-upoperatie en de kopieeroperatie.
# 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()
Draaien zonder virtual environment
Maar wacht, met Python hebben we een virtual environment nodig. Om dit te voorkomen, kunnen we een executable maken met PyInstaller. Installeer eerst PyInstaller:
pip install pyinstaller
Maak vervolgens de executable met het volgende commando:
pyinstaller --onefile backup.py
Hiermee wordt een executable gemaakt in de directory './dist':
dist/backup
We kunnen dit uitvoeren en deze executable in ons pad zetten.
Samenvatting
We hadden de Docker SDK voor Python hier niet echt nodig, we hadden ook 'subprocess' kunnen gebruiken, maar door het op te nemen kunnen we ook veel andere dingen doen.
Er is niet veel verschil tussen het Bash script en het Python script. Een paar regels schrijven in Bash is gemakkelijk. Maar zodra we wat meer functionaliteit en controle willen, kan het erg tijdrovend worden. Tenzij je een Bash-goeroe bent, moet je commando's opzoeken, bestuderen hoe je functies schrijft, dingen testen. Bij het schrijven van een Python script hebben we nog steeds kennis nodig van Linux commando's, maar we kunnen dit beperken tot de paar functies die we nodig hebben. Voor het overige kunnen we onze Python kennis gebruiken.
Links / credits
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
Lees meer
Docker
Recent
- Database UUID primaire sleutels van je webapplicatie verbergen
- Don't Repeat Yourself (DRY) met Jinja2
- SQLAlchemy, PostgreSQL, maximum aantal rijen per user
- Toon de waarden in SQLAlchemy dynamische filters
- Veilige gegevensoverdracht met Public Key versleuteling en pyNaCl
- rqlite: een alternatief voor SQLite met hoge beschikbaarheid en distributed
Meest bekeken
- Met behulp van Python's pyOpenSSL om SSL-certificaten die van een host zijn gedownload te controleren
- Gebruik van UUIDs in plaats van Integer Autoincrement Primary Keys met SQLAlchemy en MariaDb
- Maak verbinding met een dienst op een Docker host vanaf een Docker container
- PyInstaller en Cython gebruiken om een Python executable te maken
- SQLAlchemy: Gebruik van Cascade Deletes om verwante objecten te verwijderen
- Flask RESTful API verzoekparametervalidatie met Marshmallow-schema's