PostgreSQL z PgAdmin4 w Dockerze
Czerwiec 30, 2024 | #docker , #postgresql , #db
000011
100000
000000
PostgreSQL jest jedną z najpopularniejszych baz danych często wykorzystywaną w projektach. Żeby praca z PostgreSQL-em w kontenerze Dockera była przyjemna i bezproblemowa warto zaopatrzyć go o kilka przydatnych narzędzi.
Cookiecutter to narzędzie open source, które umożliwia tworzenie szablonów projektów programistycznych. Jest szczególnie popularne w społeczności Pythona, choć można go używać z różnymi językami programowania i typami projektów. Jest przydatne zarówno dla doświadczonych, jak i początkujących programistów ponieważ ułatwia odpalenie nowego projektu w ustandaryzowany sposób. Jakość szablonów cookiecuttera jest różna, ale ten dla projektu Django jest przemyślany, a jedną z jego zalet jest między innymi dobrze przygotowany Dockerfile dla PostgreSQL-a, który dodaje do obrazu skrypty do zarządzania backup-ami . Od kiedy ich przydatność sprawdziłem w boju, niemal wszystkie używane przeze mnie kontenery z bazą danych są wyposażone w ich odpowiedniki.
Zazwyczaj z uwagi na wymagania projektu piszę własną wersję tych skryptów choćby po to aby backup tworzył się według innych reguł, by spełnić wymagania licencyjne, albo tworzę ich odpowiedniki dla innych silników baz danych np. MySQL. Na potrzeby tego artykułu posłużę, się oryginalnymi skryptami z wyżej wspomnianego projektu nieco tylko uproszczonymi w celu ograniczenia ilości przytaczanego kodu.
Dodam też od siebie banalny, choć przydatny skrypt ułatwiający zalogowanie się do psql-a w kontenerze Dockera bez konieczności podawania hasła, czy bazy danych, które to dane będą przechowywane w formie zmiennych środowiskowych w pliku .env
. Dzięki temu szybki wgląd w dane czy to na localhoście, czy też na dowolnym innych środowisku nie będzie od developera wymagało uprzedniego i uciążliwego sprawdzenia credentiali.
Nie każdy, tak jak ja lubi używać CLI (Command-Line Interface) i nie zawsze też jest możliwość skorzystania z zewnętrznego GUI do bazy danych, dlatego kolejnym narzędziem, jakim niekiedy warto wzbogacić konfigurację projektu jest jakiś graficzny menadżer baz danych, który także da się zamknąć w kontenerze Dockera. PgAdmin 4 udostępnia intuicyjny webowy interface dla PostgreSQL-a, udostępnia zaawansowane funkcje i świetnie sprawdzi się nie tylko na localhoście, ale też w środowiskach testowych czy developerskich. Zaproponowana przeze mnie konfiguracja także nastawiona jest na wygodę pracy - czyli brak konieczności autoryzowania się użytkownikiem i hasłem do bazy danych, dlatego też jej użycie wymaga zwrócenia uwagi na to czy dostęp do PgAdmina 4 - na środowisku innym niż localhost - nie jest ograniczony w jakiś inny sposób - VPN, htpasswd czy cokolwiek innego.
Nikt nie tworzy konfiguracji projektu w docker-compose.yml
złożonego z samej bazy danych, gdyż stanowi ona raczej zaplecze dla innego kontenera, jednak w tym artykule pominę kontenery aplikacji zakładając, że wiesz jak połączyć je z bazą danych w Dockerze, a skupię się jedynie na aprowizacji samego obrazu PostgreSQL-a i customizacji obrazu PgAdmina. Traktuj zatem poniższą strukturę jako wycinek całego projektu
Struktura omawianych plików wygląda następująco.
├── backups
├── docker
│ ├── pgadmin
│ │ ├── Dockerfile
│ │ └── entrypoint.sh
│ └── postgres
│ ├── maintenance
│ │ ├── _sourced
│ │ │ ├── constants.sh
│ │ │ └── messages.sh
│ │ ├── backup
│ │ ├── backups
│ │ ├── cli
│ │ ├── restore
│ │ └── rmbackup
│ └── Dockerfile
├── .env
└── docker-compose.yml
Plik konfiguracyjny Docker Compose docker-compose.yml
definiuje dwa serwisy: postgres
i pgadmin
.
version: '3.8'
services:
postgres:
build:
dockerfile: ./docker/postgres/Dockerfile
context: .
command: postgres -c log_statement=all
volumes:
- postgres-volume:/var/lib/postgresql/data
- ./backups:/backups
ports:
- "5433:5432"
env_file: ./.env
pgadmin:
build:
dockerfile: ./docker/pgadmin/Dockerfile
context: .
image: sls.pgadmin4:latest
ports:
- 8081:80
depends_on:
- postgres
volumes:
- pgadmin-volume:/var/lib/pgadmin
environment:
- PGADMIN_DEFAULT_EMAIL=postgres@example.com
- PGADMIN_DEFAULT_PASSWORD=postgres
- PGADMIN_CONFIG_SERVER_MODE=False
- PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED=False
env_file: ./.env
volumes:
postgres-volume:
pgadmin-volume:
PgAdmin 4 wymaga do działania PostgreSQL-a co obrazuje parametr depends_on
. Oba serwisy współdzielą zmienne środowiskowe zdefiniowane w pliku .env
przechowujące w tym wypadku dane definiujące bazę danych oraz dostęp do niej.
# PostgreSQL
# ---------------------------
POSTGRES_DB=dbname
POSTGRES_USER=CHANGE_ME
POSTGRES_PASSWORD=CHANGE_ME
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
Oba serwisy mają zdefiniowane wolumeny nazwane przeznaczone na przechowywane dane. Serwis postgres
ma ponadto podmontowany katalog hosta backups
, który jest miejscem przeznaczonym na przechowywanie kopi zapasowych, do których niedługo przejdziemy.
Oba serwisy budowane są na podstawie plików Dockerfile
Dockerfile dla PostgreSQL
Plik Dockera przeznaczony dla PostgreSQL ./docker/postgres/Dockerfile
jest prosty i nie zmienia nic w domyślnym obrazie poza dodaniem kilku przydatnych skryptów.
FROM docker.io/postgres:16.1-alpine3.19
COPY ./docker/postgres/maintenance /usr/local/bin/maintenance
RUN chmod +x /usr/local/bin/maintenance/* \
&& cp -r /usr/local/bin/maintenance/* /usr/local/bin/ \
&& rm -rf /usr/local/bin/maintenance
Skrypty do zarządzania kopiami bazy danych
Jak już wspomniałem wyżej, skrypty do zarządzania kopiami bazy danych zaczerpnięte są z jednego z szablonów cookiecutter-a. Usunąłem z nich część komentarzy i zmieniłem hashbang z #!/usr/bin/env bash
na #!/usr/bin/env sh
, jako że użyłem obrazu Dockera w wersji alpine, która nie zawiera Bash-a.
Skrypty pomocnicze
Na początek dwa skrypty pomocnicze importowane i używane we właściwych skryptach. Nie są wywoływane bezpośrednio.
Plik ./docker/postgres/maintenance/_sourced/constants.sh
- zawiera definicje dwóch stałych
#!/usr/bin/env sh
BACKUP_DIR_PATH='/backups'
BACKUP_FILE_PREFIX='backup'
Plik ./docker/postgres/maintenance/_sourced/messages.sh
- zawiera z kolei funkcje pomocnicze służące do wyświetlania odpowiednio sformatowanych komunikatów.
#!/usr/bin/env sh
message_newline() {
echo
}
message_debug()
{
echo -e "DEBUG: ${@}"
}
message_welcome()
{
echo -e "\e[1m${@}\e[0m"
}
message_warning()
{
echo -e "\e[33mWARNING\e[0m: ${@}"
}
message_error()
{
echo -e "\e[31mERROR\e[0m: ${@}"
}
message_info()
{
echo -e "\e[37mINFO\e[0m: ${@}"
}
message_suggestion()
{
echo -e "\e[33mSUGGESTION\e[0m: ${@}"
}
message_success()
{
echo -e "\e[32mSUCCESS\e[0m: ${@}"
}
Skrypt do tworzenia kopii zapasowej bazy danych.
Plik ./docker/postgres/maintenance/backup
służy do tworzenia kopi zapasowej bazy danych. Nie przyjmuje żadnych parametrów, a wynikiem jego wywołania powinno być utworzenie pliku kopi bazy danych w formacie sql i zarchiwizowanej gzip-em w katalogu ./backups
np. ./backups/backup_2024_06_30T13_43_59.sql.gz
#!/usr/bin/env sh
set -o errexit
set -o pipefail
set -o nounset
working_dir="$(dirname ${0})"
source "${working_dir}/_sourced/constants.sh"
source "${working_dir}/_sourced/messages.sh"
message_welcome "Backing up the '${POSTGRES_DB}' database..."
if [[ "${POSTGRES_USER}" == "postgres" ]]; then
message_error "Backing up as 'postgres' user is not supported. Assign 'POSTGRES_USER' env with another one and try again."
exit 1
fi
export PGHOST="${POSTGRES_HOST}"
export PGPORT="${POSTGRES_PORT}"
export PGUSER="${POSTGRES_USER}"
export PGPASSWORD="${POSTGRES_PASSWORD}"
export PGDATABASE="${POSTGRES_DB}"
backup_filename="${BACKUP_FILE_PREFIX}_$(date +'%Y_%m_%dT%H_%M_%S').sql.gz"
pg_dump | gzip > "${BACKUP_DIR_PATH}/${backup_filename}"
message_success "'${POSTGRES_DB}' database backup '${backup_filename}' has been created and placed in '${BACKUP_DIR_PATH}'."
Wywołanie wymaga uruchomionego serwera baz danych więc wywołanie tylko docker compose run --rm postgres backup
nie zadziała ponieważ komenda backup
zastąpi domyślnie wywoływaną postgres -c log_statement=all
(patrz docker-compose.yml
- na marginesie dodam też, że wyświetlanie wszystkich logów PostgreSQL-a w trakcie dewelopmentu czasem się przydaje). W pierwszej kolejności należy uruchomić bazę danych (np. docker compose up -d postgres
) i wykonać tworzenie kopi zapasowej z poziomu wcześniej uruchomionego kontenera
docker compose exec -it postgres backup
Skrypt do wyświetlenia listy utworzonych kopii zapasowych bazy danych
Plik ./docker/postgres/maintenance/backups
służy do wyświetlania listy utworzonych backupów.
#!/usr/bin/env sh
set -o errexit
set -o pipefail
set -o nounset
working_dir="$(dirname ${0})"
source "${working_dir}/_sourced/constants.sh"
source "${working_dir}/_sourced/messages.sh"
message_welcome "These are the backups you have got:"
ls -lht "${BACKUP_DIR_PATH}"
Skrypt nie wymaga uruchomienia serwera bazy danych ponieważ wyświetla po prostu zawartość katalogu ./backups
docker compose run --rm postgres backups
# or
docker compose exec -it postgres backups
Skrypt do odtwarzania bazy danych z kopii zapasowej
Plik ./docker/postgres/maintenance/restore
umożliwia przywrócenie bazy danych z kopii zapasowej
#!/usr/bin/env sh
set -o errexit
set -o pipefail
set -o nounset
working_dir="$(dirname ${0})"
source "${working_dir}/_sourced/constants.sh"
source "${working_dir}/_sourced/messages.sh"
if [[ -z ${1+x} ]]; then
message_error "Backup filename is not specified yet it is a required parameter. Make sure you provide one and try again."
exit 1
fi
backup_filename="${BACKUP_DIR_PATH}/${1}"
if [[ ! -f "${backup_filename}" ]]; then
message_error "No backup with the specified filename found. Check out the 'backups' maintenance script output to see if there is one and try again."
exit 1
fi
message_welcome "Restoring the '${POSTGRES_DB}' database from the '${backup_filename}' backup..."
if [[ "${POSTGRES_USER}" == "postgres" ]]; then
message_error "Restoring as 'postgres' user is not supported. Assign 'POSTGRES_USER' env with another one and try again."
exit 1
fi
export PGHOST="${POSTGRES_HOST}"
export PGPORT="${POSTGRES_PORT}"
export PGUSER="${POSTGRES_USER}"
export PGPASSWORD="${POSTGRES_PASSWORD}"
export PGDATABASE="${POSTGRES_DB}"
message_info "Dropping the database..."
dropdb "${PGDATABASE}"
message_info "Creating a new database..."
createdb --owner="${POSTGRES_USER}"
message_info "Applying the backup to the new database..."
gunzip -c "${backup_filename}" | psql "${POSTGRES_DB}"
message_success "The '${POSTGRES_DB}' database has been restored from the '${backup_filename}' backup."
Podobnie jak w przypadku tworzenia zrzutu bazy danych, odtworzenie wymaga uruchomionego serwera bazy danych i wskazania, który backup ma zostać użyty do odtworzenia.
docker compose exec -it postgres restore <file_name>
# eg.
# docker compose exec -it postgres restore backup_2024_06_30T13_43_59.sql.gz
Skrypt usuwający kopię bazy danych
Plik ./docker/postgres/maintenance/rmbackup
ułatwia usunięcie pliku kopii bazy danych. Napisałem ułatwia ponieważ plik w podmontowanym katalogu ./backups
można po prostu usunąć jak każdy inny plik.
#!/usr/bin/env sh
set -o errexit
set -o pipefail
set -o nounset
working_dir="$(dirname ${0})"
source "${working_dir}/_sourced/constants.sh"
source "${working_dir}/_sourced/messages.sh"
if [[ -z ${1+x} ]]; then
message_error "Backup filename is not specified yet it is a required parameter. Make sure you provide one and try again."
exit 1
fi
backup_filename="${BACKUP_DIR_PATH}/${1}"
if [[ ! -f "${backup_filename}" ]]; then
message_error "No backup with the specified filename found. Check out the 'backups' maintenance script output to see if there is one and try again."
exit 1
fi
message_welcome "Removing the '${backup_filename}' backup file..."
rm -r "${backup_filename}"
message_success "The '${backup_filename}' database backup has been removed."
Wywołanie
docker compose run --rm postgres rmbackup <file_name>
# or
docker compose exec -it postgres rmbackup <file_name>
# eg.
# docker compose run --rm postgres rmbackup backup_2024_06_30T13_43_59.sql.gz
Skrypt ułatwiający logowanie do konsoli psql
Pliku ./docker/postgres/maintenance/cli
nie znajdziecie w cookiecutterze - przynajmniej na czas pisania tego artykułu, a ułatwia on życie bo zamiast wywoływać:
docker compose exec -it postgres psql -U <user_name> -d <db_name>
wystarczy wywołać:
docker compose exec -it postgres cli
Niby drobna zmiana, ale jak wchodzisz na jeden z kilku serwerów testowych, na których są losowo generowane credentiale i chcesz coś szybko sprawdzić w BD i oczywiście nie pamiętasz jak się nazywa użyty akurat tu user i nazwa bazy, to - wierz mi - docenisz to usprawnienie.
#!/usr/bin/env sh
set -o errexit
set -o pipefail
set -o nounset
psql -U "${POSTGRES_USER}" -d "${POSTGRES_DB}"
Dockerfile dla PgAdmin 4
Dodanie PgAdmin 4, Adminer-a itp. do konfiguracji Docker Compose to prosta sprawa i znajdziesz w internecie wiele przepisów na to jak to zrobić prosto, ale zwykle są to wskazówki ograniczające się do absolutnego minimum. W efekcie dostajesz narzędzie do którego musisz się za każdym razem logować zwykle wskazując serwer i użytkownika bazy danych, a w przypadku Adminer-a dodatkowo jeszcze silnik bazy danych. Naturalnie jeśli Ci to nie przeszkadza to nie ma o czym mówić, ale jeśli na localhoście uważasz to za zbędne operacje i chciałbyś mieć szybki wgląd w to co się dzieje w twojej roboczej bazie danych to zapoznaj się z zaproponowanym przeze mnie plikiem ./docker/pgadmin/Dockerfile
:
FROM dpage/pgadmin4:8.9
USER root
RUN mv /entrypoint.sh /entrypoint.orig.sh
COPY ./docker/pgadmin/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh \
&& touch /pgadmin4/servers.json \
&& chown pgadmin:root /pgadmin4/servers.json \
&& touch /pgpass
&& chown pgadmin:root /pgpass
USER pgadmin
ENTRYPOINT [ "/entrypoint.sh" ]
Dockerfile zmienia nazwę oryginalnemu skryptowi startowemu, po czym kopiuje w jego miejsce nowy skrypt ./docker/pgadmin/entrypoint.sh
.
#!/bin/sh
cat > /pgadmin4/servers.json <<EOF
{
"Servers": {
"1": {
"Group": "Servers",
"Name": "Local Postgres",
"Host": "${POSTGRES_HOST}",
"Port": 5432,
"MaintenanceDB": "${POSTGRES_DB}",
"Username": "${POSTGRES_USER}",
"PassFile": "/pgpass",
"SSLMode": "prefer"
}
}
}
EOF
echo "${POSTGRES_HOST}:${POSTGRES_PORT}:${POSTGRES_DB}:${POSTGRES_USER}:${POSTGRES_PASSWORD}" > /pgpass
chmod 600 /pgpass
sh /entrypoint.orig.sh
Skrypt ten musi zostać wywołany przed odpaleniem tego pierwotnego entrypoint.orig.sh
, ponieważ generuje on konfigurację, która pozwoli uruchamiać PdAdmin-a bez autoryzacji i z automatycznym logowaniem się do roboczej bazy danych PostgreSQL.
Nowy skrypt startowy wymaga istnienia dwóch plików /pgadmin4/servers.json
i /pgpass
więc są one tworzone i są im nadawane stosowne uprawnienia w chwili budowania obrazu.
Plik servers.json
w PgAdmin 4 służy do przechowywania konfiguracji serwerów PostgreSQL, do których można się łączyć za pomocą tej aplikacji. Wykorzystywany jest przez PgAdmin do załadowania listy serwerów, które są dostępne dla użytkownika, gdy uruchamia aplikację. Dzięki temu użytkownik nie musi ręcznie konfigurować połączeń za każdym razem, gdy uruchamia PgAdmin. Plik servers.json
może być edytowany ręcznie, ale zazwyczaj jest tworzony i aktualizowany przez samą aplikację PgAdmin, gdy użytkownik dodaje lub modyfikuje konfiguracje serwerów w interfejsie użytkownika.
Plik pgpass
w PostgreSQL służy do automatycznego zarządzania hasłami do baz danych, aby użytkownik nie musiał ich podawać ręcznie przy każdym połączeniu. Jest to plik tekstowy, który zawiera informacje o połączeniach, takie jak host, port, nazwa bazy danych, nazwa użytkownika oraz hasło. Kiedy klient PostgreSQL, taki jak psql
, pg_dump
, czy jak w tym przypadku PgAdmin 4 łączący się z bazą danych PostgreSQL, próbuje nawiązać połączenie, sprawdza ten plik, aby znaleźć odpowiednie hasło dla danego połączenia. Jeśli odpowiednie dane znajdują się w pgpass
, połączenie zostanie nawiązane bez potrzeby ręcznego wprowadzania hasła.
Na koniec trzeba jeszcze wyjaśnić znaczenie przekazywanych do kontenera PgAdmin-a za pośrednictwem pliku docker-compose.yml
zmiennych środowiskowych.
- PGADMIN_DEFAULT_EMAIL i PGADMIN_DEFAULT_PASSWORD: Używane są do automatycznego tworzenia konta administratora przy pierwszym uruchomieniu, ale w naszym przypadku nie mają większego znaczenia poza tym, że muszą być zdefiniowane bo inaczej kontener z PgAdminem się wysypie przy inicjalizacji.
- PGADMIN_CONFIG_SERVER_MODE: Określa tryb działania PgAdmin 4 (lokalny lub serwerowy). Ustawienie tej zmiennej na
False
powoduje zmianę trybu działania PgAdmina na lokalny co wyłącza szereg mechanizmów autoryzacji i rozróżniania użytkowników gdyż, zakłada się, że w tym trybie z PgAdmina korzysta tylko jeden, lokalny użytkownik. Pomimo tego ustawienia wciąż konieczne jest zdefiniowanie pozostałych wymienionych na tej liście zmiennych środowiskowych - PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: Decyduje, czy PgAdmin wymaga konfiguracji hasła głównego. Nawet jak ta zmienna zostanie ustawiona na
False
i tak trzeba podać PGADMIN_DEFAULT_EMAIL i PGADMIN_DEFAULT_PASSWORD
PgAdmin 4 przy pierwszym uruchomieniu zapisuje całą swoją konfigurację w wolumenie co zajmuje trochę czasu. Przy kolejnych uruchomieniach jest już dużo szybciej. Po zmianie konfiguracji najlepiej usunąć wolumen gdyż samo przebudowanie kontenera nie wystarczy.
Cały kod zaprezentowany w artykule dostępny jest do pobrania z repozytorium github PostgreSQL with PgAdmin 4