Занурюємося в Docker: Dockerfile і комунікація між контейнерами

У минулій статті ми розповіли, що таке Docker і як з його допомогою можна обійти Vendor-lock. У цій статті ми поговоримо про Dockerfile як про правильному способі підготовки образів для Docker. Також ми розглянемо ситуацію, коли контейнерів потрібно взаємодіяти один з одним.


InfoboxCloud ми зробили готовий образ Ubuntu 14.04 з Docker. Не забудьте поставити галочку «Дозволити управління ядром ОС» при створенні сервера, це потрібно для роботи Docker.

Dockerfile

Підхід docker commit, описаний у попередній статті, не є рекомендованим для Docker. Його плюс полягає в тому, що ми налаштовуємо контейнер практично так, як звикли налаштовувати стандартний сервер.

Замість цього підходу ми рекомендуємо використовувати підхід Dockerfile пункт docker build. Dockerfile використовує звичайний DSL з інструкціями для побудови образів Docker. Після цього виконується команда docker build для побудови нового образу з інструкціями Dockerfile.

Написання Dockerfile
Давайте створимо простий спосіб з веб-сервером за допомогою Dockerfile. Для початку створимо директорію і сам Dockerfile.
mkdir static_web
cd static_web
touch Dockerfile

Створена директорія — білд-оточення, в якій Docker викликає контекст або будує контекст. Docker завантажить контекст у папці в процесі роботи Docker-демона, коли буде запущена збірка образу. Таким чином буде можливо для Docker-демона отримати доступ до будь кодом, файлів або іншими даними, які ви захочете включити в образ.

Додамо в Dockerfile інформацію щодо побудови образу:
# Version: 0.0.1
FROM ubuntu:14.04
MAINTAINER Yuri Trukhin <trukhinyuri@infoboxcloud.com>
RUN apt-get update
RUN apt-get install-y nginx
RUN echo 'Hi, I am in your container' \
>/usr/share/nginx/html/index.html
EXPOSE 80

Dockerfile містить набір інструкцій з аргументами. Кожна інструкція пишеться великими літерами (наприклад FROM). Інструкції оброблюються зверху вниз. Кожна інструкція додає новий шар в образ і коммитит зміни. Docker виконує інструкції, слідуючи процесу:
  • Запуск контейнера з образу
  • Виконання інструкції та внесення змін в контейнер
  • Запуск еквівалента docker commit для запису змін в новий шар способу
  • Запуск нового контейнера з нового образу
  • Виконання наступної інструкції в файлі і повторення кроків процесу.
Це означає, що якщо виконання Dockerfile зупиниться з якоїсь причини (наприклад інструкція не зможе завершитися), ви зможете використовувати образ до цієї стадії. Це дуже корисно при налагодженні: ви можете запустити контейнер з образу інтерактивно і дізнатися, чому інструкція не виконалася, використовуючи останній створений образ.

Також Dockerfile підтримує коментарі. Будь-яка рядок, що починається з # означає коментар.

Перша інструкція в Dockerfile завжди повинна бути FROM, що вказує, з якого способу потрібно побудувати образ. У нашому прикладі ми будуємо образ з базового образу ubuntu версії 14:04.

Далі ми вказуємо інструкцію MAINTAINER, повідомляє Docker образу автора і його email. Це корисно, щоб користувачі способу могли зв'язатися з автором при необхідності.

Інструкція RUN виконує команду в конкретному образі. У нашому прикладі за допомогою її ми оновлюємо APT репозиторії і встановлюємо пакет з NGINX, потім створюємо файл /usr/share/nginx/html/index.html.

За замовчуванням інструкція RUN виповнюється всередині оболонки з використанням обгортки команд /bin/sh-c. Якщо ви запускаєте інструкцію на платформі без оболонки або просто хочете виконати інструкцію без оболонки, ви можете вказати формат виконання:
RUN ["apt-get", "install", "-y", "nginx"]

Ми використовуємо цей формат для вказівки масиву, що містить команду для виконання і параметри команди.

Далі ми вказуємо інструкцію EXPOSE, яка говорить Docker, що додаток в контейнері повинно використовувати певний порт в контейнері. Це не означає, що ви можете автоматично отримувати доступ до сервісу, запущеного на порту контейнера (в нашому прикладі порт 80). З міркувань безпеки Docker не відкриває порт автоматично, але очікує, коли це зробить користувач в команді docker run. Ви можете вказати безліч інструкцій EXPOSE для вказівки, які порти повинні бути відкриті. Також інструкція EXPOSE корисна для перекидання портів між контейнерами.

Будуємо образ з нашого файлу
docker build-t trukhinyuri/nginx ~/static_web

, де trukhinyuri — назва репозиторію, де буде зберігатися образ, nginx — ім'я образу. Останній параметр — шлях до папки з Dockerfile. Якщо ви не вкажете назва образу, він автоматично отримає назву останніми. Також ви можете вказати git репозиторій, де знаходиться Dockerfile.
docker build-t trukhinyuri/nginx \ git@github.com:trukhinyuri/docker-static_web

У даному прикладі ми будуємо образ з Dockerfile, розташованому в кореневій директорії Docker.

Якщо в корені білд контексту є файл .dockerignore — він інтерпретується як список патернів винятків.

Що станеться, якщо інструкція не виповниться?
Давайте перейменуємо в Dockerfile nginx в ngin і подивимося.



Ми можемо створити контейнер з передостаннього кроку з ID образу 066b799ea548
docker run-i-t 066b799ea548 /bin/bash
і налагодити виконання.

За замовчуванням Docker кешує кожен крок і формуючи кеш збірок. Щоб відключити кеш, наприклад для використання останнього apt-get update, використовуйте прапор --no-cache.
docker build --no-cache-t trukhinyuri/nginx


Використання кеша збірок для шаблонізації
Використовуючи кеш збірок можна будувати образи з Dockerfile у формі простих шаблонів. Наприклад шаблон для оновлення APT-кеша в Ubuntu:
FROM ubuntu:14.04
MAINTAINER Yuri Trukhin <trukhinyuri@infoboxcloud.com>
ENV REFRESHED_AT 2014-10-16
RUN apt-get update qq

Інструкція ENV встановлює змінні оточення в образі. В даному випадку ми вказуємо, коли шаблон був оновлений. Коли необхідно оновити побудований образ, просто потрібно змінити дату ENV. Docker скине кеш і версії пакетів в образі будуть останніми.

Інструкції Dockerfile
Давайте розглянемо і інші інструкції Dockerfile. Повний список можна подивитися тут.

CMD
Інструкція CMD вказує, яку команду необхідно запустити, коли контейнер запущений. На відміну від команди RUN вказана команда не виконується під час побудови образу, а під час запуску контейнера.
CMD ["/bin/bash", "-l"]

В даному випадку ми запускаємо bash і передаємо йому параметр у вигляді масиву. Якщо ми задаємо команду не у вигляді масиву — вона буде виконуватися в /bin/sh-c. Важливо пам'ятати, що ви можете перевантажити CMD, використовуючи docker run.

ENTRYPOINT
Часто команду CMD плутають з ENTRYPOINT. Різниця в тому, що ви не можете перевантажувати ENTRYPOINT при запуску контейнера.
ENTRYPOINT ["/usr/sbin/nginx"]

При запуску контейнера параметри передаються команді, зазначеної в ENTRYPOINT.
docker run-d trukhinyuri/static_web-g "daemon off"

Можна комбінувати ENTRYPOINT CMD.
ENTRYPOINT ["/usr/sbin/nginx"]
CMD ["-h"]

У цьому випадку команда в ENTRYPOINT виконається в будь-якому випадку, а команда в CMD виконається, якщо не передано іншої команди при запуску контейнера. Якщо ви все-таки можете перевантажити команду ENTRYPOINT з допомогою прапора --entrypoint.

WORKDIR
З допомогою WORKDIR можна встановити робочий каталог, звідки будуть запускатися команди ENTRYPOINT CMD.
WORKDIR /opt/webapp/db
RUN bundle install
WORKDIR /opt/webapp
ENTRYPOINT ["rackup"]

Ви можете перевантажити робочу директорію контейнера в рантайме з допомогою прапора-w.

USER
Специфікує користувача, під яким повинен бути запущений образ. Ми можемо вказати ім'я користувача або UID і групи або GID.
USER user
USER user:group
USER uid
USER uid:gid
USER user:gid
USER uid:group

Ви можете перевантажити цю команду, використовуючи глагов-u при запуску контейнера. Якщо користувач не вказано, використовується root за замовчуванням.

VOLUME
Інструкція VOLUME додає тома в образ. Тому — папка в одному або більше контейнерах або папка хоста, проброшенная через Union File System (UFS).
Тому можуть бути расшарены або повторно використані між контейнерами. Це дозволяє додавати і змінювати дані без коміта в образ.
VOLUME ["/opt/project"]

У наведеному вище прикладі створюється точка монтування /opt/project для будь-якого контейнера, створеного образу. Таким чином ви можете вказувати і кілька томів у масиві.

ADD
Інструкція ADD додає файли або папки з нашого білд-оточення в образ, що корисно наприклад при установці програми.
ADD software.lic /opt/application/software.lic

Джерелом може бути URL, ім'я файлу або теки.
ADD http://wordpress.org/latest.zip /root/wordpress.zip

ADD latest.tar.gz /var/www/wordpress/

В останньому прикладі архів tar.gz буде розпакований в /var/www/wordpress. Якщо шлях призначення не вказано, буде використано повний шлях включаючи директорії.

COPY
Інструкція COPY відрізняється від ADD тим, що призначена для копіювання локальних файлів з білд-контексту і не підтримує розпакування файлів:
COPY conf.d/ /etc/apache2/


ONBUILD
Інструкція ONBUILD додає тригери в образи. Тригер виконується, коли образ використовується як базовий для іншого образу, наприклад, коли вихідний код, потрібний для образу ще не доступний, але вимагає для роботи конкретного оточення.
ONBUILD ADD . /app/src
ONBUILD RUN cd /app/src && make


Комунікація між контейнерами

У попередній статті було показано, як запускати ізольовані контейнери Docker і як прокидати файлову систему в них. Але що, якщо додатків потрібно зв'язуватися один з одним. Є 2 способи: зв'язок через кидок портів і лінковку контейнерів.

Кидок портів
Такий спосіб зв'язку вже був показаний раніше. Подивимося на варіанти прокидання портів трохи ширше.
Коли ми використовуємо EXPOSE в Dockerfile або параметр -p номер_порта — порт контейнера прив'язується до безпідставного порту хоста. Подивитися цей порт можна командою docker ps або docker port имя_контейнера номер_порта_в_контейнере. В момент створення образу ми можемо не знати, який порт буде вільний на машині в момент запуску контейнера.

Вказати, на який конкретний порт хоста ми прив'яжемо порт контейнера можна параметром docker run-p порт_хоста: порт_контейнера
За замовчуванням порт використовується на всіх інтерфейсах машини. Можна, наприклад, прив'язати до localhost:
docker run-p 127.0.0.1:80:80

Можна прив'язати UDP порти, вказавши /udp:
docker run-p 80:80/udp


Лінковка контейнерів
Зв'язок через мережеві порти — лише один спосіб комунікації. Docker надає систему лінкування, що дозволяє зв'язати безліч контейнерів разом і відправляти інформацію про з'єднання від одного контейнера іншому.

Для встановлення зв'язку потрібно використовувати імена контейнерів. Як було показано раніше, ви можете дати ім'я контейнера при створенні з допомогою прапора --name.

Припустимо у вас є 2 контейнера: web і db. Щоб створити зв'язок, видалите контейнер web і пересоздайте з використанням команди-link name:alias.
docker run-d-P --name web --link db:db trukhinyuri/webapp python app.py

Використовуючи docker-ps можна побачити пов'язані контейнери.

Що насправді відбувається при лінкування? Створюється контейнер, який надає інформацію про себе контейнера-одержувачу. Це відбувається двома способами:
  • Через змінні оточення
  • Через /etc/hosts
Змінні оточення можна подивитися, виконавши команду env:
$ sudo docker run --rm --name web2 --link db:db training/webapp env
. . .
DB_NAME=/web2/db
DB_PORT=tcp://172.17.0.5:5432
DB_PORT_5432_TCP=tcp://172.17.0.5:5432
DB_PORT_5432_TCP_PROTO=tcp
DB_PORT_5432_TCP_PORT=5432
DB_PORT_5432_TCP_ADDR=172.17.0.5

Префікс DB_ був узятий з alias контейнера.

Можна просто використовувати інформацію з hosts, наприклад команда ping db (де db — alias) буде працювати.

Висновок

У цій статті ми навчилися використовувати Dockerfile і організовувати зв'язок між контейнерами. Це тільки вершина айсберга, дуже багато чого залишилося за кадром і буде розглянуто в майбутньому. Для додаткового читання рекомендуємо книгу The Docker Book.

Готовий образ з Docker доступний в хмарі InfoboxCloud.

У разі, якщо ви не можете задавати питання на Хабре, можна задати Співтоваристві InfoboxCloud.
Якщо ви виявили помилку у статті, автор її з задоволенням виправить. Будь ласка напишіть в ЛС або на пошту.

Успішного використання Docker!

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.