[Перевод] Создаём и расширяем образы Docker с функциями middleware

Для переноса IT-среды в облако используют технологии контейнеризации, в первую очередь Docker. Такой подход помогает оптимизировать потребление ресурсов и ещё больше автоматизировать операционные процессы. Однако для поддержки энтерпрайз-приложений требуются дополнительные службы, которые называют функциями промежуточной обработки (middleware functions). Такие службы применяют для интеграции, обмена сообщениями, размещения приложений в контролируемой серверной среде и др. В статье рассказываем, как создавать и расширять образы Docker, содержащие функции промежуточной обработки. 

be0a95b4bdbc21273b9bd4d942d6f05e.jpg

Заглянем внутрь образа Docker

Образ — главный элемент в контейнеризации Docker. В нём содержатся процессы и зависимости, необходимые для нормальной работы приложения. На базовый образ Docker накладываются доступные для чтения слои, которые образуются после любых изменений. Каждый новый слой представляет собой актуальную версию образа. А финальный образ — это объединение всех слоев в один. Подробно писали об этом здесь.

При развёртывании нового контейнера Docker вы указываете имя и тег образа, который хотите использовать. Хост Docker проверяет, доступен ли этот образ локально. Если недоступен — загружает все слои, необходимые для конкретного образа. 

Важно: слои никогда не загружаются дважды. Предположим, вы хотите запустить один контейнер с WordPress и другой с MySQL. Оба контейнера основаны на базовом образе Ubuntu, и слои, составляющие его, загружаются только один раз. Такое разделение упрощает доступ, поскольку образ Docker не является одним большим монолитным файлом.

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

Хотя образ Docker может содержать только те файлы, которые необходимы для запуска операционной системы, следует избегать развёртывания »пустого образа». Ожидается, что контейнер представляет приложение или, по крайней мере, часть приложения (например, микросервис). Нельзя развёртывать контейнер, на котором работает Ubuntu, а затем пытаться удалённо войти в него, чтобы вручную установить другое программное обеспечение. Образ Ubuntu на Docker hub был и остаётся одним из самых популярных. Однако обычно он используется в качестве основы для других образов, несущих в себе функции более высокого уровня.

«Docker для админов и разработчиков»

Аналогично, контейнер Docker должен запускать только один процесс. Каждый образ Docker определяет команду, которая выполняется при запуске контейнера. Эта команда запускает приложение или сервис, предлагаемые контейнером. Вы можете указать дополнительные параметры для этой команды или определить свою собственную команду при запуске контейнера (описано в разделе «Docker image customization»). 

Dockerfile

Новые образы Docker создаются с использованием Dockerfile, где указаны следующие элементы:

  • базовый образ, на котором основан новый образ;

  • файлы, которые необходимо скопировать в новый образ;

  • сетевые порты, которые должен открыть новый образ;

  • команды, которые следует выполнять при сборке нового образа;

  • переменные среды, которые можно установить, когда образ используется для запуска контейнера;

  • команда, которая выполняется при запуске контейнера

Dockerfile передаётся команде docker build, которая создаёт новый образ. В примере показан простой Dockerfile, который описывает образ, основанный на Ubuntu, предоставляет порт 80 и запускает пользовательский сценарий оболочки:


FROM ubuntu
EXPOSE 80
COPY myscript.sh /usr/local/bin/
CMD ["myscript.sh"]

Более реалистичные примеры образов вы можете найти на GitHub. Рекомендации по созданию Dockerfile на Docker и Project Atomic. 

С помощью Dockerfile вы можете добавлять в образ точки расширения, которые позволяют настраивать контейнер без необходимости каждый раз создавать новый образ. 

Запускаем middleware в контейнерах

Образ Docker существует с минимальным набором библиотек и представляет приложение или службу, а не операционную систему. Пример — образ Ubuntu, который представляет собой базовый образ, предназначенный для расширения, а не для запуска в контейнере.

Между операционной системой и приложением находится middleware — промежуточное или связующее программное обеспечение. Наиболее распространенными примерами промежуточного программного обеспечения считаются серверы приложений или среды обмена сообщениями. Скажем, чтобы использовать промежуточное ПО в контексте среды Dockerized, вы должны создать образы Docker, содержащие это промежуточное ПО в качестве дополнительных слоев. Однако эти образы не предназначены для окончательного развёртывания в контейнере. Поэтому вы должны создавать их таким образом, чтобы упростить расширение middleware с помощью реального приложения.

На рисунке показано, как контейнеры используют образы Docker, состоящие из нескольких слоев, охватывающих части операционной системы и middleware:

93aa8b576060564386325bc098efd922.png

Некоторые образы промежуточного ПО Docker служат лишь основой для контейнера, добавляющего приложение. Следовательно, можно определить две роли:

  • поставщик промежуточного ПО, который добавляет соответствующие программные пакеты в базовый образ и, возможно, выполняет некоторую предварительную настройку;

  • поставщик приложения, который добавляет к образу ПО промежуточного слоя, устанавливая приложение и выполняя дополнительную настройку.

В конце концов, образы Docker для промежуточного ПО должны предоставлять точки расширения, упрощающие добавление приложения.

Чтобы продемонстрировать идею повторного использования и расширения образов, рассмотрим серию образов, которые строятся один на другом, переходя от промежуточного программного обеспечения к образу. На рисунке показаны примеры IBM WebSphere Liberty и Tradelite. В этом сценарии образ Docker — актив с добавленной стоимостью, который передаётся командам или организациям, которые затем дополнительно настраивают образ, прежде чем передать его другой команде:

f2202a1aa5c3ab7ef96f2d43c8a9ccfc.png

Образы, которые используются в примере:

  • WebSphere Liberty — образ был создан группой IBM и предоставляет middleware для сервера приложения. Является расширением базового образа, который содержит необходимые файловые системы операционной системы. 

  • Tradelite — образ создан независимым поставщиком решений. Обеспечивает основные функции приложения, оставаясь при этом нейтральным к облаку.

  • Tradelite4MyCo — приложение для образа, который относится к вымышленному облаку MyCo.

При создании образа вы должны учитывать его дальнейшее использование и целевую аудиторию. Жизненный цикл образа Docker имеет несколько точек пересечения: сборка образа, создание контейнера и среда выполнения контейнера.

Настраиваем образ Docker

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

Переменные среды

Переменные среды — наиболее распространенные и гибкие средства предоставления значений параметров, которые можно переопределить в слоях образа или при создании контейнера.

Чтобы определить переменную среды во время сборки образа, используйте инструкцию ENV Dockerfile:

ENV myName="John Doe" myDog=Rex\ The\ Dog 
    myCat=fluffy

Если та же переменная окружения указана позже в файле Dockerfile или в расширяемом Dockerfile, значение будет перезаписано. Устанавливается новое значение среды, которое сохраняется в образе. Переменная среды также может быть установлена при создании контейнера:

docker run –e "myName=\"Bob\"

Переменные среды имеют несколько ограничений. Во-первых, этот механизм лучше всего работает для простых значений данных и может стать проблемой при передаче структурированных данных. Возможный обходной путь — сопоставить простые имена переменных со структурированным содержимым в контейнере. Второе ограничение заключается в том, что переменные среды могут быть предоставлены во время сборки образа или создания контейнера, но ими нельзя управлять после создания контейнера. Если вы меняете переменную среды во время выполнения, вы должны уничтожить контейнер и воссоздать его с другим значением. Также переменные среды могут отображаться в логах. Следовательно, они не лучший выбор для передачи конфиденциальных данных контейнеру.

Аргументы командной строки

Docker разбивает то, что традиционно считается командной строкой процесса, на две части: точку входа и команду. Точка входа представляет процесс, который нужно запустить, а команда состоит из аргументов, переданных этому процессу. Например, общая точка входа в образ базовой операционной системы — /bin/bash –c, , а командная строка может быть ls. Результатом запуска такого контейнера считается запуск процесса:

/bin/bash –c ls

Аналогично переменным среды, точка входа и параметры команды могут быть предоставлены инструкцией Dockerfile или при создании контейнера. Однако интерфейс командной строки (CLI) для конкретного процесса обычно чётко определён. Следовательно, его наиболее распространенное использование — изменение характеристик целевого процесса. 

Файлы и тома

Контейнер имеет многоуровневую файловую систему. Вы можете добавлять файлы или редактировать содержимое в слое образа или в запущенном инстансе контейнера.

Процесс сборки Docker предлагает два механизма добавления файлов в файловую систему контейнера: ADD и COPY. ADD остаётся в наборе инструкций, которые понимает сборка Docker. Однако COPY считается более предпочтительной, поскольку её легче понять.

Чтобы использовать COPY, укажите источник и место назначения в Dockerfile:

COPY [source] [destination]

Также Docker предлагает тома данных, жизненный цикл которых отличается от контейнера. Они не используют многоуровневую файловую систему, что выгодно только для данных, которые часто считываются и записываются. 

Вы можете объявить том из Dockerfile с помощью инструкции VOLUME. Это позволит сделать так, чтобы при создании контейнера данные копировались из многоуровневой файловой системы во вновь созданный том.

Что даёт использование инструкции VOLUME:

  • Тома не удаляются после создания. Если том не имеет смысла для следующего образа расширения, просто отложите его создание на более позднее время.

  • Сборка Docker создаёт новый контейнер для каждой инструкции в Dockerfile. Результатом является перенос данных из файловой системы многоуровневого контейнера в новый том, что может снизить производительность сборки.

  • Многоуровневая файловая система становится недоступной. Любые файлы, которые вы добавляете или изменяете в каталогах тома, не сохраняются.

Вы также можете создавать тома с помощью команды volume или переключателя среды выполнения –v:

docker run –v [volume]:[path]

Применяем настройки

К этому моменту у вас уже должно появиться представление, как Docker использует переменные среды, аргументы командной строки и уровни образов. Теперь разберём, как применять эти знания на практике на примере Tradelite — приложения, которому требуется сервер для предоставления необходимых функций.

Один файл Dockerfile может поддерживать весь программный стек от базовой операционной системы до приложения, но это сложно поддерживать. Гораздо предпочтительнее разбить общий набор инструкций Dockerfile на блоки.

Выбор базового образа

Контейнер Docker — просто процесс, который запускается на хосте, но с приписываемым ему особым поведением. Одним из вариантов поведения считается видимая файловая система. Поскольку контейнер запускается после монтирования файловой системы образа, эта система должна содержать целевой файл, который, в свою очередь, может содержать дополнительные файлы.

Для WebSphere Liberty процесс начальной загрузки запускается сценарием оболочки и запускает среду выполнения Java (JRE). Чтобы поддерживать оболочку, нужно выбрать базовый образ, содержащий среду оболочки, а также предоставить общие библиотеки Linux (libpthread, libc и ld-linux).

В нашем примере используется образ Liberty Docker, который можно найти в Docker hub или IBM Containers. Учитывая, что контейнер в основном использует библиотеки, поддерживаемые ядром Linux и совместимые с дистрибутивами Linux, нам не нужно пересобирать образ, чтобы выбрать другую базовую операционную систему. Если этот образ повторно используется в других приложениях Java EE в целевой среде, результатом будет большая согласованность между образами Java EE, а также более эффективное использование памяти и пропускной способности передачи.

Объявление томов

WebSphere Liberty image имеет широкую аудиторию. Важной характеристикой этого образа считается то, что любые выполняемые в нём действия, могут быть отменены или заменены в следующем Dockerfile. 

Как упоминалось ранее, вы не можете удалить том после его создания. Поэтому команда WebSphere Liberty решила не объявлять никаких томов, оставив выбор за расширителем или оператором. 

Добавление двоичных файлов программного обеспечения и данных конфигурации

Каждому Dockerfile из нашего примера может потребоваться добавление нескольких двоичных файлов ПО. Мы можем добавить их к образу с помощью инструкций ADD или COPY. 

Добавление двоичных файлов обычно не вызывает трудностей. Каждый уровень имеет тенденцию добавлять больше двоичных файлов или заменять существующий двоичный файл. Поэтому вам не нужно редактировать двоичный файл или напрямую влиять на его содержимое на диске во время выполнения. Достаточно встраивать удалённое извлечение двоичных файлов ПО в образ. Запущенный контейнер не должен загружать двоичные обновления, но вместо этого должен пытаться сохранить двоичное содержимое идемпотентным. Если двоичный файл необходимо обновить — подготовьте для этого новый образ.

В нашем примере большая часть содержимого конфигурации связана с файлом WebSphere Liberty server.xml и соответствующим ему набором файлов. Часть этого контента должна быть предоставлена в следующих слоях. Например, нам может потребоваться объявить файл EAR и связанные с ним атрибуты развёртывания. При этом другой контент может быть связан со средой развёртывания.

Оператору немного проще определить переменные среды. Поэтому оператор должен определять данные с помощью переменных среды. В качестве альтернативы элементы развёртывания приложения должны предоставляться одновременно с двоичными файлами. Эти файлы настраиваются путем редактирования или предоставления содержимого server.xml в сборке Docker. 

Затем с помощью Dockerfile добавляется пользовательский файл server.xml:  

COPY ./server.xml /opt/ibm/wlp/usr/servers/defaultServer/server.xml

Файл server.xml конфигурирует файл EAR и показывает переменные среды оператора для базы данных:



    
        
    
    
    
        
    

Файл server.xml показывает оператору следующие переменные среды:

  • DB2_USER;

  • DB2_PASS;

  • DB2_PORT;

  • DB2_HOST;

Хотя переменные среды не идеальны для передачи конфиденциальных данных, в конкретном случае они имеют преимущество с точки зрения приоритизации параллелизма между другими параметрами конфигурации.

Про расположение environment-specific конфигурации в образе

Механизм переменных среды хорошо работает для простых значений, но как насчёт сложной конфигурации структурных данных? Примеры данных такого типа — LDAP (протокол, используемый для хранения и извлечения данных из иерархической структуры каталогов) и TLS (протокол, поддерживающий защищённую передачу данных между клиентом и сервисом). Можно попытаться сопоставить некоторые из этих данных с переменными среды, однако почти всегда их сертификаты передаются через файловый механизм.

Безусловно, самый простой способ работы с содержимым файла — добавить его непосредственно к образу. Однако это может нарушить переносимость образа приложения в облако и повысить уязвимости. Реестры Docker не подходят для хранения секретных данных, поэтому их нужно предоставлять непосредственно во время выполнения.

В каждой ситуации нужно определить, следует ли предоставлять сложные данные конфигурации с помощью механизма расширения образа. Достичь точки расширения образа Docker вы можете независимо от выбранного подхода. Важно не привязывать образ к конкретному использованию, чтобы для каждого рабочего экземпляра не требовался отдельный образ:

f24ca657f47968034cb7886552e75054.png

Коротко о главном

Образы, созданные в контексте middleware, должны быть расширяемыми, поскольку они всегда служат промежуточным форматом, в который вы можете добавлять приложения. Существует несколько механизмов расширения образа Docker. Например, вы можете использовать Dockerfile для создания нового образа поверх существующего или выбирать различные способы воздействия на полученный контейнер Docker. В идеале вы всегда будете использовать сочетание этих механизмов, основываясь на практиках и рекомендациях, представленных в этой статье.

«Docker для админов и разработчиков»

© Habrahabr.ru