Docker entrypoint что это

Написание кода в docker окружении

В компании, где я работаю — большинство сервисов запускаются и работают в docker-контейнерах.

В связи с этим, у моих коллег-новичков-в-докере часто возникает вопрос — а как писать код и запускать его в этом чёртовом контейнере.

Docker entrypoint что это. Смотреть фото Docker entrypoint что это. Смотреть картинку Docker entrypoint что это. Картинка про Docker entrypoint что это. Фото Docker entrypoint что это

Для человека, написавшего около сотни docker-образов и запускающего их несколько раз в день — такой вопрос уже не стоит, но когда я разбирался с докером в давние времена — мысль «Как же писать код в докере? Это же сверхнеудобно!» долго была актуальной.

В статье я опишу свои практики работы с образами docker, которые позволяют писать код «как у себя в home», и даже лучше.

Итак, что такое готовый docker-образ?

Это слепок готового сервиса, который настраивается небольшим числом переменных окружения и готов к работе сразу после старта. С docker-образом не требуется устанавливать зависимости приложения и библиотеки разработчика себе локально в систему, замусоривая её.

Запуск готового образа

Для начала разберём, как запускать образ. Предполагается, что название образа нам известно.

И это всё — имя образа.

В простейшем случае образ запускается так:

Часто образ нужно сконфигурировать переменными окружения. Откуда их брать?

Примеры подсмотренных переменных окружения:

Docker entrypoint что это. Смотреть фото Docker entrypoint что это. Смотреть картинку Docker entrypoint что это. Картинка про Docker entrypoint что это. Фото Docker entrypoint что это

Docker entrypoint что это. Смотреть фото Docker entrypoint что это. Смотреть картинку Docker entrypoint что это. Картинка про Docker entrypoint что это. Фото Docker entrypoint что это

Запускаем контейнер с переменными окружения

Если переменных несколько

В итоге, мы получили работающий и сконфигурированный сервис.

Попадаем внутрь контейнера

Для упрощения программирования «внутри» контейнера — нам нужно в него попасть. При описанном выше запуске — запускается сам сервис, но мы сами не попадаем «внутрь».

Для попадания «внутрь» — нужно переопределить команду старта образа.

Это делается указанием имени shell-оболочки после имени образа

Shell-оболочка зависит от дистрибутива, на котором построен образ.

Попробуйте один из вариантов и не ошибётесь.

После подобного запуска мы оказываемся в консоли внутри контейнера.

Инициализация сервиса

После того, как мы попали в консоль внутри контейнера — мы попали в «голый» образ. Часто образы, после запуска, проводят первичную инициализацию (формируют файлы настроек и т.д.) и после этого запускают сам сервис.

Инициализация делается через команду старта, которую мы выше заменили на shell-оболочку. Поэтому, инициализацию нужно запустить вручную. Для этого, открываем Dockerfile и смотрим содержимое инструкции CMD.

И именно его и запускаем.

Сервис инициализировался и запустился, теперь мы можем нажать Ctrl+C и снова попасть в консоль, имея контейнер, готовый к повторному запуску сервиса.

Написание кода внутри контейнера

Когда сервис запускается внутри контейнера — он использует те скрипты/бинарные файлы, которые уже находятся внутри. Как нам их редактировать?

Элементарно. Нужно редактировать их извне, в любимом редакторе, в своей домашней папке, а потом просто скопировать в контейнер и запустить.

Даём доступ контейнеру к своей домашней папке используя опцию

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

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

В зависимости от ваших нужд — скрипты/файлы можно не копировать в контейнер, а запускать их сразу из /d/my-repo.

Граничные случаи и лайфхаки

ENTRYPOINT

Некоторые образы (довольно редко) используют команду старта в виде ENTRYPOINT. Что это такое — можно посмотреть в Dockerfile reference. Нам же нужно только помнить, что перезапись команды старта для таких образов выглядит иначе

«Облачные» окружения

Если сервисы работают в Consul, docker swarm, kubernetes окружении, то они могут использовать такие переменные окружения, которые доступны только контейнеру, запущенному в этом облаке и не будут доступны контейнеру, запущенному на компьютере разработчика.

Docker entrypoint что это. Смотреть фото Docker entrypoint что это. Смотреть картинку Docker entrypoint что это. Картинка про Docker entrypoint что это. Фото Docker entrypoint что это

В указанном примере используются «облачные» адреса в переменных CONSUL_HTTP_ADDR и VAULT_ADDR. В таких случаях вам нужно использовать внешние адреса данных сервисов.

Повторные запуски

Писать каждый раз полностью команду docker run — излишне. Всю команду старта с переменными удобно сохранить с sh файл. Который потом достаточно просто запускать.

Переиспользование переменных окружения

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

Запуск без sudo

В локальной разработке запускать контейнеры с sudo — утомительно. Для исправления — добавляем своего пользователя в группу docker. После этого, вместо

Источник

Изучаем Docker, часть 3: файлы Dockerfile

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

Docker entrypoint что это. Смотреть фото Docker entrypoint что это. Смотреть картинку Docker entrypoint что это. Картинка про Docker entrypoint что это. Фото Docker entrypoint что это

Образы Docker

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

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

Контейнеры, как мы выяснили в первом материале этой серии, состоят из слоёв. Каждый слой, кроме последнего, находящегося поверх всех остальных, предназначен только для чтения. Dockerfile сообщает системе Docker о том, какие слои и в каком порядке надо добавить в образ.

Каждый слой, на самом деле, это всего лишь файл, который описывает изменение состояния образа в сравнении с тем состоянием, в котором он пребывал после добавления предыдущего слоя. В Unix, кстати, практически всё что угодно — это файл.

Базовый образ — это то, что является исходным слоем (или слоями) создаваемого образа. Базовый образ ещё называют родительским образом.

Docker entrypoint что это. Смотреть фото Docker entrypoint что это. Смотреть картинку Docker entrypoint что это. Картинка про Docker entrypoint что это. Фото Docker entrypoint что это

Базовый образ — это то, с чего начинается образ Docker

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

Файлы Dockerfile

В файлах Dockerfile содержатся инструкции по созданию образа. С них, набранных заглавными буквами, начинаются строки этого файла. После инструкций идут их аргументы. Инструкции, при сборке образа, обрабатываются сверху вниз. Вот как это выглядит:

Здесь мы исходим из предположения, в соответствии с которым используется образ Docker, основанный на Unix-подобной ОС. Конечно, тут можно воспользоваться и образом, основанным на Windows, но использование Windows — это менее распространённая практика, работать с такими образами сложнее. В результате, если у вас есть такая возможность, пользуйтесь Unix.

Для начала приведём список инструкций Dockerfile с краткими комментариями.

Дюжина инструкций Dockerfile

Инструкции и примеры их использования

▍Простой Dockerfile

Dockerfile может быть чрезвычайно простым и коротким. Например — таким:

▍Инструкция FROM

Ключевое слово FROM сообщает Docker о том, чтобы при сборке образа использовался бы базовый образ, который соответствует предоставленному имени и тегу. Базовый образ, кроме того, ещё называют родительским образом.

В этом примере базовый образ хранится в репозитории ubuntu. Ubuntu — это название официального репозитория Docker, предоставляющего базовую версию популярной ОС семейства Linux, которая называется Ubuntu.

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

Docker entrypoint что это. Смотреть фото Docker entrypoint что это. Смотреть картинку Docker entrypoint что это. Картинка про Docker entrypoint что это. Фото Docker entrypoint что это

Структура контейнера (взято из документации)

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

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

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

▍Более сложный Dockerfile

Хотя файл Dockerfile, который мы только что рассмотрели, получился аккуратным и понятным, он устроен слишком просто, в нём используется всего одна инструкция. Кроме того, там нет инструкций, вызываемых во время выполнения контейнера. Взглянем на ещё один файл, который собирает маленький образ. В нём имеются механизмы, определяющие команды, вызываемые во время выполнения контейнера.

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

Базой этого образа является официальный образ Python с тегом 3.7.2-alpine3.8. Проанализировав этот код можно увидеть, что данный базовый образ включает в себя Linux, Python, и, по большому счёту, этим его состав и ограничивается. Образы ОС Alpine весьма популярны в мире Docker. Дело в том, что они отличаются маленькими размерами, высокой скоростью работы и безопасностью. Однако образы Alpine не отличаются широкими возможностями, характерными для обычных операционных систем. Поэтому для того, чтобы собрать на основе такого образа что-то полезное, создателю образа нужно установить в него необходимые ему пакеты.

▍Инструкция LABEL

Docker entrypoint что это. Смотреть фото Docker entrypoint что это. Смотреть картинку Docker entrypoint что это. Картинка про Docker entrypoint что это. Фото Docker entrypoint что это

Инструкция LABEL (метка) позволяет добавлять в образ метаданные. В случае с рассматриваемым сейчас файлом, она включает в себя контактные сведения создателя образа. Объявление меток не замедляет процесс сборки образа и не увеличивает его размер. Они лишь содержат в себе полезную информацию об образе Docker, поэтому их рекомендуется включать в файл. Подробности о работе с метаданными в Dockerfile можно прочитать здесь.

▍Инструкция ENV

Docker entrypoint что это. Смотреть фото Docker entrypoint что это. Смотреть картинку Docker entrypoint что это. Картинка про Docker entrypoint что это. Фото Docker entrypoint что это

Инструкция ENV хорошо подходит для задания констант. Если вы используете некое значение в Dockerfile несколько раз, скажем, при описании команд, выполняющихся в контейнере, и подозреваете, что, возможно, вам когда-нибудь придётся сменить его на другое, его имеет смысл записать в подобную константу.

▍Инструкция RUN

Docker entrypoint что это. Смотреть фото Docker entrypoint что это. Смотреть картинку Docker entrypoint что это. Картинка про Docker entrypoint что это. Фото Docker entrypoint что это

▍Инструкция COPY

Docker entrypoint что это. Смотреть фото Docker entrypoint что это. Смотреть картинку Docker entrypoint что это. Картинка про Docker entrypoint что это. Фото Docker entrypoint что это

▍Инструкция ADD

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

▍Инструкция CMD

Docker entrypoint что это. Смотреть фото Docker entrypoint что это. Смотреть картинку Docker entrypoint что это. Картинка про Docker entrypoint что это. Фото Docker entrypoint что это

Инструкция CMD предоставляет Docker команду, которую нужно выполнить при запуске контейнера. Результаты выполнения этой команды не добавляются в образ во время его сборки. В нашем примере с помощью этой команды запускается скрипт my_script.py во время выполнения контейнера.

Вот ещё кое-что, что нужно знать об инструкции CMD :

▍Ещё более сложный Dockerfile

Рассмотрим ещё один файл Dockerfile, в котором будут использованы некоторые новые команды.

Кроме того, пакеты Python в образ можно устанавливать с помощью pip, wheel и conda. Если речь идёт не о Python, а о других языках программирования, то при подготовке соответствующих образов могут использоваться и другие менеджеры пакетов.

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

▍Инструкция WORKDIR

Docker entrypoint что это. Смотреть фото Docker entrypoint что это. Смотреть картинку Docker entrypoint что это. Картинка про Docker entrypoint что это. Фото Docker entrypoint что это

▍Инструкция ARG

▍Инструкция ENTRYPOINT

Docker entrypoint что это. Смотреть фото Docker entrypoint что это. Смотреть картинку Docker entrypoint что это. Картинка про Docker entrypoint что это. Фото Docker entrypoint что это

Пункт перехода в какое-то место

▍Инструкция EXPOSE

Docker entrypoint что это. Смотреть фото Docker entrypoint что это. Смотреть картинку Docker entrypoint что это. Картинка про Docker entrypoint что это. Фото Docker entrypoint что это

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

▍Инструкция VOLUME

Docker entrypoint что это. Смотреть фото Docker entrypoint что это. Смотреть картинку Docker entrypoint что это. Картинка про Docker entrypoint что это. Фото Docker entrypoint что это

Инструкция VOLUME позволяет указать место, которое контейнер будет использовать для постоянного хранения файлов и для работы с такими файлами. Об этом мы ещё поговорим.

Итоги

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

Уважаемые читатели! Если вы пользуетесь Docker на практике, просим рассказать о том, как вы пишете Docker-файлы.

Источник

Погружаемся в Docker: Dockerfile и коммуникация между контейнерами

В прошлой статье мы рассказали, что такое Docker и как с его помощью можно обойти Vendor–lock. В этой статье мы поговорим о Dockerfile как о правильном способе подготовки образов для Docker. Также мы рассмотрим ситуацию, когда контейнерам нужно взаимодействовать друг с другом.

Docker entrypoint что это. Смотреть фото Docker entrypoint что это. Смотреть картинку Docker entrypoint что это. Картинка про Docker entrypoint что это. Фото Docker entrypoint что это
В InfoboxCloud мы сделали готовый образ Ubuntu 14.04 с Docker. Не забудьте поставить галочку «Разрешить управление ядром ОС» при создании сервера, это требуется для работы Docker.

Dockerfile

Подход docker commit, описанный в предыдущей статье, не является рекомендованным для Docker. Его плюс состоит в том, что мы настраиваем контейнер практически так, как привыкли настраивать стандартный сервер.

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

Написание Dockerfile

Давайте создадим простой образ с веб-сервером с помощью Dockerfile. Для начала создадим директорию и сам Dockerfile.

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

Добавим в Dockerfile информацию по построению образа:

Также Dockerfile поддерживает комментарии. Любая строчка, начинающаяся с # означает комментарий.

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

Далее мы указываем инструкцию MAINTAINER, сообщающую Docker автора образа и его email. Это полезно, чтобы пользователи образа могли связаться с автором при необходимости.

Инструкция RUN исполняет команду в конкретном образе. В нашем примере с помощью ее мы обновляем APT репозитории и устанавливаем пакет с NGINX, затем создаем файл /usr/share/nginx/html/index.html.

Мы используем этот формат для указания массива, содержащего команду для исполнения и параметры команды.

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

Строим образ из нашего файла

, где trukhinyuri – название репозитория, где будет храниться образ, nginx – имя образа. Последний параметр — путь к папке с Dockerfile. Если вы не укажете название образа, он автоматически получит название latest. Также вы можете указать git репозиторий, где находится Dockerfile.

В данном примере мы строим образ из Dockerfile, расположенном в корневой директории Docker.

Что произойдет, если инструкция не исполнится?

Давайте переименуем в Dockerfile nginx в ngin и посмотрим.

Docker entrypoint что это. Смотреть фото Docker entrypoint что это. Смотреть картинку Docker entrypoint что это. Картинка про Docker entrypoint что это. Фото Docker entrypoint что это

Использования кеша сборок для шаблонизации

Используя кеш сборок можно строить образы из Dockerfile в форме простых шаблонов. Например шаблон для обновления APT-кеша в Ubuntu:

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

Инструкции Dockerfile

Давайте рассмотрим и другие инструкции Dockerfile. Полный список можно посмотреть тут.

Инструкция CMD указывает, какую команду необходимо запустить, когда контейнер запущен. В отличие от команды RUN указанная команда исполняется не во время построения образа, а во время запуска контейнера.

ENTRYPOINT

Часто команду CMD путают с ENTRYPOINT. Разница в том, что вы не можете перегружать ENTRYPOINT при запуске контейнера.

При запуске контейнера параметры передаются команде, указанной в ENTRYPOINT.

Можно комбинировать ENTRYPOINT и CMD.

WORKDIR

С помощью WORKDIR можно установить рабочую директорию, откуда будут запускаться команды ENTRYPOINT и CMD.

Специфицирует пользователя, под которым должен быть запущен образ. Мы можем указать имя пользователя или UID и группу или GID.

VOLUME

Инструкция VOLUME добавляет тома в образ. Том — папка в одном или более контейнерах или папка хоста, проброшенная через Union File System (UFS).
Тома могут быть расшарены или повторно использованы между контейнерами. Это позволяет добавлять и изменять данные без коммита в образ.

В примере выше создается точка монтирования /opt/project для любого контейнера, созданного из образа. Таким образом вы можете указывать и несколько томов в массиве.

Инструкция ADD добавляет файлы или папки из нашего билд-окружения в образ, что полезно например при установке приложения.

Источником может быть URL, имя файла или директория.

В последнем примере архив tar.gz будет распакован в /var/www/wordpress. Если путь назначения не указан — будет использован полный путь включая директории.

Инструкция COPY отличается от ADD тем, что предназначена для копирования локальных файлов из билд-контекста и не поддерживает распаковки файлов:

ONBUILD

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

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

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

Проброс портов

Такой способ связи уже был показан ранее. Посмотрим на варианты проброса портов чуть шире.
Когда мы используем EXPOSE в Dockerfile или параметр -p номер_порта – порт контейнера привязывается к произвольному порту хоста. Посмотреть этот порт можно командой docker ps или docker port имя_контейнера номер_порта_в_контейнере. В момент создания образа мы можем не знать, какой порт будет свободен на машине в момент запуска контейнера.

Можно привязать UDP порты, указав /udp:

Линковка контейнеров

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

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

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

Заключение

В этой статье мы научились использовать Dockerfile и организовывать связь между контейнерами. Это только вершина айсберга, очень многое осталось за кадром и будет рассмотрено в будущем. Для дополнительного чтения рекомендуем книгу The Docker Book.

Готовый образ с Docker доступен в облаке InfoboxCloud.

В случае, если вы не можете задавать вопросы на Хабре, можно задать в Сообществе InfoboxCloud.
Если вы обнаружили ошибку в статье, автор ее с удовольствием исправит. Пожалуйста напишите в ЛС или на почту о ней.

Источник

Docker: правильный запуск процессов в контейнере с PID 1

Введение

Данная статья посвящена корректному написанию Dockerfile по части запуска процессов внутри контейнера. Казалось бы, что всё описано в документации. Но порой бывают неоднозначные случаи и в целом хорошие практики, о которых будет рассказано на примерах с пояснением.

Основы ENTRYPOINT && CMD

Перед началом нужно кратко вспомнить, что это такое и чем отличается. Эти две директивы в Dockerfile наверняка всем известны – обе запускают процессы в контейнере. Как правило, в entrypoint используется какой-либо скрипт или команда для запуска процесса внутри контейнера, а в cmd – параметры, которые передаются в entrypoint.

Если указывать параметры в квадратных скобках, т.е. в формате exec, то внутри контейнера будет выполняться лишь процесс, который указан для запуска, и никаких оболочек запущено не будет. Это значит, что замена (substitution) переменных и их обработка невозможны – это также предпочтительный вариант для простого запуска какого-либо процесса. О более сложных моментах будет рассказано далее.

Грубый пример Dockerfile для демонстрации работы exec формы:

После сборки образа с именем “ping” и дальнейшего запуска контейнера из него будет выполняться команда “/bin/ping it-lux.ru”. Домен можно переопределить, указав его при запуске контейнера, например:

Тем самым выполняется переопределение значения из параметра CMD в Dockerfile, что очень удобно – из одного образа можно запускать команды с различными аргументами. Такой способ я использовал для выполнения крон-задач в отдельном докер-контейнере, указывая на хосте в crontab через аргументы путь к скриптам при запуске docker run, которые принимал для выполнения php.

PID 1 и остановка контейнера

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

Допустим, что для запуска приложения в контейнере понадобилось выполнять какие-то подготовительные действия перед запуском финальной команды “ping ya.ru”. Для этого обычно пишется docker-entrypoint.sh и уже этот скрипт запускается в контейнере. После запуска можно увидеть, что теперь процесс внутри контейнера уже не “ping ya.ru”, а /bin/bash /usr/bin/docker-entrypoint.sh и процесс с PID 1 уже принадлежит bash, внутри которого выполняется ping:

docker-entrypoint.sh и запуск процессов

Возникает логичный вопрос: если принято использовать docker-entrypoint.sh для запуска всех процессов, то как сделать корректный PID в контейнере, если в самом начале всё равно запускается баш? Ответ прост – в том же bash есть уже команда exec, которая принимает в качестве аргумента ваш процесс, убивает текущую оболочку и запускает то, что передали через аргументы. Простой пример:

При запуске такого контейнера, если посмотреть в список процессов, можно увидеть, что теперь PID 1 принадлежит основной команде ping:

Смысл docker-entrypoint.sh в выполнении каких-то начальных действий, коих некое множество – для этого баш подходит идеально, а когда надо будет запустить уже основной процесс для работы в контейнере, текущая оболочка “отпадает”, что также очень удобно и вполне корректно.

Но нельзя забывать про директиву CMD. Она по-прежнему полезна, хотя в последних примерах и не используется. Её польза состоит также в передаче аргументов, но уже в docker-entrypoint.sh, где они обрабатываются. Например:

Здесь появилось пару новых элементов. Это по-прежнему всё тот же скрипт, в котором в начале выполняются подготовительные действия. Но используется переменная “$@” – она содержит в себе все аргументы, которые будут переданы при запуске контейнера, т.е. какой-либо домен в данном случае. И используется команда set с аргументом из двух тире, которая подставляет команду ping перед последними аргументами, т.е. перед доменом, который надо пинговать.

В итоге получается, что при запуске контейнера с неким аргументом, он записывается в переменную “$@” и сразу же передается команде ping также аргументом. А для выполнения используется уже знакомая форма exec. Напоминаю, что “-d ping ” – это название образа, а “google.com” – аргумент к команде ping из entrypoint-скрипта.

На первый взгляд это кажется чем-то сложным и совсем непонятным. Зачем это всё, когда можно просто явно указать какую команду выполнять? Это же всё усложняет!

Да, порой так и бывает, а потому это вряд ли пригодится в написании простых Dockerfile. Но с ростом логики, которую необходимо обработать в docker-entrypoint.sh, и вышеописанными командами, появляется прекрасная возможность очень гибко оперировать ходом событий в скрипте. Похожие методы обычно применяются во многих других Dockerfile и docker-entrypoint.sh для популярного софта – zabbix, например. В них можно более детально изучить применение подобных функций для полноценного понимания.

tini и запуск процессов

Теперь, когда вроде бы понятно, что основной процесс в контейнере должен иметь PID 1 и это не должен быть bash, т.е. используется команда exec, может возникнуть ещё один неочевидный момент.

Приложение запускается и начинает рождать дочерние процессы. Например, это тот же Zabbix server, Apache или Jenkins. Основной процесс запущен с PID 1 и всё, казалось бы, хорошо. Но возникает такой момент, когда процесс с PID 1 перестаёт корректно реагировать на сигналы, посылаемые демоном докера при остановке контейнера. Это случается, например, по причине “осиротевших” дочерних процессов, когда родительский процесс не подчистил за собой хвосты. Т.е. остались зомби-процессы.

Появление таких процессов, опять же, казалось бы, должно быть на совести разработчиков ПО, но не всё так однозначно. Например, Jenkins позволяет запускать код, который написан не разработчиками Jenkins, т.е. выполняются сценарии сборки. В zabbix тоже есть возможность запуска некоторых скриптов. И вот из всего этого и могут остаться “висящие” процессы.

Здесь в игру и вступает tini (init наоборот) – это легковесная современная утилита, разработанная специально для контейнеров, которая умеет проводить “чистку” за зомби-процессами.

Но постойте… То есть получается, что нужно запускать tini с PID 1 в контейнере? Да! Но ведь изначально же был уже bash с PID 1 и это было не кошерно неправильно, не так ли? Абсолютно верно!

Первая проблема с баш заключается в следующем – если запущен bash с PID 1, внутри которого крутится всё тот же Jenkins, то при остановке контейнера посылаемые сигналы могут не дойти до Jenkins, они будут получены башем и в нём пропадут. А далее передавать он их не умеет, в отличие от tini.

Второй проблемой здесь является принцип работы процесса с PID 1 в Linux. PID 1 не имеет обработчиков сигналов по умолчанию. Это значит, что если софт не умеет явно обрабатывать сигналы, то он их просто проигнорирует.

Теперь становится понятно, что tini является неким брокером сигналов – он просто их передает дочерним процессам, которые уже должны будут на них среагировать так или иначе.

Заключение

В статье были рассмотрены некоторые основные и неочевидные моменты по работе с Docker-контейнерами и процессами внутри. Большая часть из примеров может показаться непонятным на первых этапах – я соглашусь с тем, что без всего вышеперечисленного можно обойтись и всё и так будет работать. Заморочки со скриптами в entrypoint – можно сломать глаза, посмотрев, что туда пишут. Докер наоборот должен всё упрощать, а не усложнять. Использование какого-то tini, когда проблема должна решаться на уровне кода? Абсолютно верно!

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

По ссылкам ниже можно ознакомиться с англоязычными первоисточниками.

Источник

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

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