Про использование Docker
и Docker-compose
последнее время написано очень много, например рекомендую недавнюю статью на Хабре, если вы до сих пор не прониклись. Это действительно очень удобно, а в связке в ansible особенно. И я его использую везде. От разработки, до автоматического интеграционного тестирования на CI
. Про использование в тестировании, тоже писали. Это здорово и удобно. Однако, для локальной разработки, для траблешутинга данных "как в продакшене" или тестирование производительности, на "объёмах близких в продакшену", хочется иметь под рукой образ, содержащий базу, "как в продакшене"!
Соответственно, хочется, чтобы каждый разработчик, приступая к работе над проектом, мог запустить его одной командой, например:
./gradlew dockerRun
и приложение поднялось бы сразу со всеми необходимыми связанными контейнерами? А главное чтобы в нём уже были бы данные для большинства кейсов разработки и багфиксинга, стандартные пользователи и большинство работающих сервисов, над которыми сразу можно было бы приступить работать, не тратя времени на экспорт-импорт каких-то там образов или демоданных!
Как приятный бонус, ну разве не здорово иметь базу данных в несколько гигабайт и возможность откатиться к её исходному (или любому другому коммиту) состоянию в течении пары секунд?
Разумеется мы поговорим о написании Dockerfile
для такого образа с данными, и некоторых подводных камнях этого процесса.
Вот на этом и сосредоточимся. В нашем приложении мы активно используем Postgres
, поэтому повествование и примеры будут о контейнере именно с ним, но это касается лишь примеров, суть изложения применима к любой другой реляционной или модной NoSQL
базе данных.
Для начала определим проблему подробнее. Мы готовим образ, с данными, с которыми сможет работать каждый, кто работает с нашим приложением:
CI
Я не буду начинать с того что такое Dockerfile
, надеюсь вы уже знакомы с этим. Тех же, кто хочет получить представление, отсылаю к статье, ну или официальной документации.
Стоит отметить что официальный docker образ postgres уже имеет несколько точек расширения:
POSTGRES_*
переменные/docker-entrypoint-initdb.d
куда можно положить sh скрипты или sql
файлы, которые будут выполнены на старте. Это очень удобно, если вы скажем хотите создать дополнительных пользователей или базы данных, установить права, проинициализировать расширения.Однако, для наших целей этого мало:
entrypoint
, и увидеть приватные данные, которые видеть не должен--max_prepared_transactions=110
, но мы не можем легко положить их в образ, и сделать стандартнымиfsync
)Я наверное сразу покажу прототип файла (вырезаны лишь некоторые незначащие части чтобы он стал меньше, например много места занимает включение расширения pg_hint_plan
, которое ставится на Debian
из RPM
, потому что отсутствует в Deb
и официальных репозиториях):
FROM postgres:9.6
MAINTAINER Pavel Alexeev
# Do NOT use /var/lib/postgresql/data/ because its declared as volume in base image and can't be undeclared but we want persist data in image
ENV PGDATA /var/lib/pgsql/data/
ENV pgsql 'psql -U postgres -nxq -v ON_ERROR_STOP=on --dbname egais'
ENV DB_DUMP_URL 'ftp://user:password@ftp.taskdata.com/desired_db_backup/egais17qa_dump-2017-02-21-16_55_01.sql.gz'
COPY docker-entrypoint-initdb.d/* /docker-entrypoint-initdb.d/
COPY init.sql/* /init.sql/
# Later in RUN we hack config to include conf.d parts.
COPY postgres.conf.d/* /etc/postgres/conf.d/
# Unfortunately Debian /bin/sh is dash shell instead of bash (https://wiki.ubuntu.com/DashAsBinSh) and some handy options like pipefaile is unavailable
# Separate RUN to next will be in bash instead of dash. Change /bin/sh symlink as it is hardcoded https://github.com/docker/docker/issues/8100
RUN ln -sb /bin/bash /bin/sh
RUN set -euo pipefail && echo '1) Install required packages' `# https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#apt-get` && apt-get update && apt-get install -y curl postgresql-plperl-9.6 && echo '3) Run postgres DB internally for init cluster:' `# Example how to run instance of service: http://stackoverflow.com/questions/25920029/setting-up-mysql-and-importing-dump-within-dockerfile` && bash -c '/docker-entrypoint.sh postgres --autovacuum=off &' && sleep 10 && echo '4.1) Configure postgres: use conf.d directory:' && sed -i "s@#include_dir = 'conf.d'@include_dir = '/etc/postgres/conf.d/'@" "$PGDATA/postgresql.conf" && echo '4.2) Configure postgres: Do NOT chown and chmod each time on start PGDATA directory (speedup on start especially on Windows):' && sed -i 's@chmod 700 "$PGDATA"@#chmod 700 "$PGDATA"@g;s@chown -R postgres "$PGDATA"@#chown -R postgres "$PGDATA"@g' /docker-entrypoint.sh && echo '4.3) RERun postgres DB for work in new configuration:' && gosu postgres pg_ctl -D "$PGDATA" -m fast -w stop && sleep 10 && bash -c '/docker-entrypoint.sh postgres --autovacuum=off --max_wal_size=3GB &' && sleep 10 && echo '5) Populate DB data: Restore DB backup:' && time curl "$DB_DUMP_URL" | gzip --decompress | grep -Pv '^((DROP|CREATE|ALTER) DATABASE|\\connect)' | $pgsql && echo '6) Execute build-time sql scripts:' && for f in /init.sql/*; do echo "Process [$f]"; $pgsql -f "$f"; rm -f "$f"; done && echo '7) Update DB to current migrations state:' && time java -jar target/egais-db-updater-*.jar -f flyway.url=jdbc:postgresql://localhost:5432/egais -f flyway.user=postgres -f flyway.password=postgres && echo '8) Vacuum full and analyze (no reindex need then):' && time vacuumdb -U postgres --full --all --analyze --freeze && echo '9) Stop postgres:' && gosu postgres pg_ctl -D "$PGDATA" -m fast -w stop && sleep 10 && echo '10) Cleanup pg_xlog required to do not include it in image!:' `# Command inspired by http://www.hivelogik.com/blog/?p=513` && gosu postgres pg_resetxlog -o $( LANG=C pg_controldata $PGDATA | grep -oP '(?<=NextOID:\s{10})\d+' ) -x $( LANG=C pg_controldata $PGDATA | grep -oP '(?<=NextXID:\s{10}0[/:])\d+' ) -f $PGDATA && echo '11(pair to 1)) Apt clean:' && apt-get autoremove -y curl && rm -rf /var/lib/apt/lists/*
Как видите, я постарался добавить комментарии прямо в файл, возможно они даже более чем исчерпывающие, но всё-таки остановимся на паре моментов подробнее.
ENV PGDATA /var/lib/pgsql/data/
. Это ключевой момент. т.к. мы хотим, чтобы заполненные данные во время билда были включены в образ, мы не должны класть их в стандартное место, определённое как volume. DB_DUMP_URL
определена просто для удобства последующего редактирования. При желании её можно передавать извне, во время билда.Postgres
прямо во время процесса билда: bash -c '/docker-entrypoint.sh postgres --autovacuum=off &'
для того чтобы провести некоторые нехитрые конфигурации:sed
включаем в основном конфиге postgres.conf
использование include_dir
. Нам это нужно для того чтобы свести к минимуму такие манипуляции с конфигом, иначе их будет очень сложно поддерживать, но зато мы обеспечили неограниченную расширяемость конфигурации! Обратите внимание, немного выше мы используем директиву COPY postgres.conf.d/* /etc/postgres/conf.d/
чтобы положить куски конфигов, специфичные именно для нашего билда.chown
и chmod
, поскольку т.к. база проинициализирована, то файлы будут уже иметь в образе верных пользователей и права, но опытным путём выяснилось, что на версии docker под Windows эта операция почему-то может занимать весьма продолжительное время, вплоть до десятков минут.Postgres
, а только потом его пробовать настроить! Иначе мы получим ошибку на старте что директория для инициализации кластера не пуста!Postgres
чтобы перечитать конфиги, которые мы подкладывали и настраивали читать. Строго говоря, этот шаг вовсе не обязательный. Однако по умолчанию он имеет очень консервативные настройки по памяти вроде shared_buffers = 128MB
, и работа со сколь-либо значительными затягивается на часы./init.sql/*
применит все SQL
скрипты из этой директории, во время создания образа (в противовес скриптам стандартного расширения). Именно здесь мы делаем необходимую обфускацию данных, сэмплирование, очистку, добавление тестовых пользователей и т.п.Postgres
с отключенным автовакуумом (--autovacuum=off
)pg_resetxlog
чтобы сбросить и не включать накопившиеся WAL. А при запуске использую --max_wal_size=3GB
чтобы увеличить размер файла и не ротэйтить их лишний раз.Готовому образу остаётся только присвоить тэг и запушить в репозиторий. Чаще всего, конечно, это будет приватный репозиторий, если вы не работает над каким-то публичным сэмплом данных.
Я буду очень рад, если это поможет кому-то сделать его процесс подготовки создания тестовых образов с данными хоть чуточку проще.
К сожалению, не доступен сервер mySQL