Docker. Best practices на примере образа Oracle xe 11g

f9977396f04e4f328bf13b56420ff70f.jpgDocker за последнее время стал очень популярен за счет своей производительности, отказоустойчивости и, главное, простоты.

Сегодня можно найти тысячи образов в hub.docker.com. За счет своей простоты в создании образов, буквально за пол часа можно начать вносить свой вклад.

Но многие забывают о best practices, и за счет этого docker hub наполнился огромным количеством не самых лучших образов.

В этой статье я хочу описать на сколько просто и полезно создавать образы используя Best Practices на примере.В качестве примера я выбрал нетривиальный образ с oracle 11g xe GitHub docker-hub.

В исходном проекте можно определить слабые места и недоработки, отсортированные по основным пунктам с best practices:

Использование .dockerignoreОчень полезный функционал, но, к сожалению, многие о нем не знают и не пользуются.В данном примере за счет добавления исключений в .dockerfile скорость сборки образа сократилась и, что самое главное, размер образа стал меньше более чем на 2GbКонечно понятно, что в git хранить тяжелые бинарные файлы совсем не best practice, но пока упустим этот момент, как модно говорить «Работает — не трогай», или как любят говорить в Британии «Так исторически сложилось».

В итоге 3 простые строчки существенно облегчили образ. Также я настоятельно рекомендую .git вносить в .dockerignore, т.к. в этой папке хранится ввесь репозиторий, в этом случае та-же копия больших бинарных файлов.

oracle-xe_11.2.0–1.0_amd64.debaa oracle-xe_11.2.0–1.0_amd64.debab oracle-xe_11.2.0–1.0_amd64.debac .git .gitignore Запускать только один процесс на контейнер Это довольно распространенная ошибка, и допускается за счет того, что люди не до конца понимают принципы работы и риски.В первую очередь в глаза кидается SSHD и не очень правильная инструкция CMD CMD sed -i -E «s/HOST = [^)]+/HOST = $HOSTNAME/g» /u01/app/oracle/product/11.2.0/xe/network/admin/listener.ora; \ service oracle-xe start; \ /usr/sbin/sshd -D Минусы использования подобного подхода можно обсуждать очень долго, особенно если пользователь захочет «кастомизировать» входящую команду.В первую очередь удаляем SSHD так как он нам не нужен, даже если нам будет необходимо выполнить debug или просто подключится к консоле контейнера лучше использовать docker exec -it ${CONTAINER_ID} /bin/bash

Также очевидно, что при остановке контейнера Gracefully останавливается только SSHD, в то время, как сама база останавливается по TERM сигналу как процесс без паррента, что не есть хорошо, особенно для базы данных, особенно для Oracle DB.

по «sed» и «service start» уже можно предположить, что просто не будет, и разумно будет перенести ввесь описанный функционал в entrypoint.sh

При подготовке ENTRYPOINT был вынужден использовать несколько костылей workarounds (в дальнейшем немного полит-корректней). Подробнее ENTRYPOINT разберем немного ниже, т.к. он затрагивает сразу несколько пунктов

Минимизация количества слоев Этот пункт очень прост, но в то же время очень важен, так как Docker работает по наслоению инкрементальных изменений в ФС по одной на каждую инструкцию, вот пример рационального использования, главное стараться оставлять код читабельным и вместить все изменения в одну RUN инструкцию # Prepare to install Oracle RUN apt-get update && apt-get install -y -q libaio1 net-tools bc curl && \ apt-get clean && \ rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/* &&\ ln -s /usr/bin/awk /bin/awk &&\ mkdir /var/lock/subsys &&\ chmod 755 /sbin/chkconfig &&\ /oracle-install.sh Функционал по установке oracle перенесен в sh скрипт в пользу читабельности.Избегать установки лишних не самых необходимых пакетов Помимо отказа от установки лишних пакетов так же крайне важно очищать после себя установочные файлы, кеши и прочее в одной инструкции, чтобы исключить ненужные наслоения инкрементальных слоев, иначе образ будет вдвойне тяжелей. apt-get clean && \ rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/* /download/directory Контейнер должен быть эфемерный Это один из самых сложных и важных моментов. Под понятием «Эфемерный» подразумевается, что при старте контейнера, а затем его остановки с удалением, следующий запуск должен быть способным продолжать работу предыдущего с минимальной конфигурацией.В нашем случае это файлы базы данных (принцип работы как холодный бэкап) с возможностью его использования при старте нового контейнера.

Данный подход и Docker позволяет нам легко и быстро создавать резервную копию, моментально откатится и клонировать всю базу.

Также не мало важно вынести базовые параметры как конфигурирование через ENV переменные.

В итоге у меня получился вот такой ENTRYPOINT

#!/bin/bash

# Prevent owner issues on mounted folders chown -R oracle: dba /u01/app/oracle rm -f /u01/app/oracle/product ln -s /u01/app/oracle-product /u01/app/oracle/product # Update hostname sed -i -E «s/HOST = [^)]+/HOST = $HOSTNAME/g» /u01/app/oracle/product/11.2.0/xe/network/admin/listener.ora sed -i -E «s/PORT = [^)]+/PORT = 1521/g» /u01/app/oracle/product/11.2.0/xe/network/admin/listener.ora echo «export ORACLE_HOME=/u01/app/oracle/product/11.2.0/xe» > /etc/profile.d/oracle-xe.sh echo «export PATH=\$ORACLE_HOME/bin:\$PATH» >> /etc/profile.d/oracle-xe.sh echo «export ORACLE_SID=XE» >> /etc/profile.d/oracle-xe.sh . /etc/profile

case »$1» in '') #Check for mounted database files if [ »$(ls -A /u01/app/oracle/oradata)» ]; then echo «found files in /u01/app/oracle/oradata Using them instead of initial database» echo «XE:$ORACLE_HOME: N» >> /etc/oratab chown oracle: dba /etc/oratab chown 664 /etc/oratab printf «ORACLE_DBENABLED=false\nLISTENER_PORT=1521\nHTTP_PORT=8080\nCONFIGURE_RUN=true\n» > /etc/default/oracle-xe rm -rf /u01/app/oracle-product/11.2.0/xe/dbs ln -s /u01/app/oracle/dbs /u01/app/oracle-product/11.2.0/xe/dbs else echo «Database not initialized. Initializing database.»

printf «Setting up:\nprocesses=$processes\nsessions=$sessions\ntransactions=$transactions\n» echo «If you want to use different parameters set processes, sessions, transactions env variables and consider this formula:» printf «processes=x\nsessions=x*1.1+5\ntransactions=sessions*1.1\n»

mv /u01/app/oracle-product/11.2.0/xe/dbs /u01/app/oracle/dbs ln -s /u01/app/oracle/dbs /u01/app/oracle-product/11.2.0/xe/dbs

#Setting up processes, sessions, transactions. sed -i -E «s/processes=[^)]+/processes=$processes/g» /u01/app/oracle/product/11.2.0/xe/config/scripts/init.ora sed -i -E «s/processes=[^)]+/processes=$processes/g» /u01/app/oracle/product/11.2.0/xe/config/scripts/initXETemp.ora sed -i -E «s/sessions=[^)]+/sessions=$sessions/g» /u01/app/oracle/product/11.2.0/xe/config/scripts/init.ora sed -i -E «s/sessions=[^)]+/sessions=$sessions/g» /u01/app/oracle/product/11.2.0/xe/config/scripts/initXETemp.ora

sed -i -E «s/transactions=[^)]+/transactions=$transactions/g» /u01/app/oracle/product/11.2.0/xe/config/scripts/init.ora sed -i -E «s/transactions=[^)]+/transactions=$transactions/g» /u01/app/oracle/product/11.2.0/xe/config/scripts/initXETemp.ora

printf 8080\\n1521\\noracle\\noracle\\ny\\n | /etc/init.d/oracle-xe configure

echo «Database initialized. Please visit http://#containeer:8080/apex to proceed with configuration» fi

/etc/init.d/oracle-xe start echo «Database ready to use. Enjoy! ;)»

## ## Workaround for graceful shutdown. oracle… ‿(́ ̵ _-`)‿ ## while [ »$END» == '' ]; do sleep 1 trap »/etc/init.d/oracle-xe stop && END=1» INT TERM done ;;

*) echo «Database is not configured. Please run /etc/init.d/oracle-xe configure if needed.» $1 ;; esac Резюме В итоге, следуя Best Practices мы получили целый ряд преимуществ:

Размер образа уменьшился на 3GB (с 3.8Gb до 825Mb) Поддержка монтирования и повторного использования дата-файлов Graceful остановка сервиса Возможности для более тонкой настройке базы через параметры при старте контейнера Результаты работы и детали решения проблем вы можете найти на github и hub.docker.com

Спасибо за внимание.

© Habrahabr.ru