Как стать автором
Обновить

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

Время на прочтение5 мин
Количество просмотров36K


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

Сегодня можно найти тысячи образов в 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

Спасибо за внимание.
Теги:
Хабы:
+18
Комментарии37

Публикации