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

от автора

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 или просто подключится к консоле контейнера лучше использовать bash 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

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

ссылка на оригинал статьи http://habrahabr.ru/post/260305/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *