Статьи

Контейнерные Мнения Приложения

Контейнерирование внутреннего приложения может быть сложным; отличный ресурс для руководства этим процессом — список лучших рекомендаций Dockerfile .

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

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

В этом посте будут представлены основные рекомендации по контейнеризации с акцентом на сторонние приложения. Для справки об этом руководстве, посмотрите мой доклад от Dockercon EU 2014 .

Конфигурация без файлов

Многие приложения требуют загрузки конфигурации через файл. Тем не менее, конфигурация контейнера намного проще через среду.

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

Confd

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

Использование confd предполагает предоставление данных конфигурации через сервер хранилища ключей (etcd / Consul) или непосредственно в качестве переменных среды в контейнере, а затем выполнение confd как часть запуска вашего контейнера перед запуском основного приложения. Это самый простой метод преобразования сложного метода конфигурации в нечто, легко автоматизируемое внутри вашей контейнерной инфраструктуры. Пример такого метода настройки см. В моем репозитории на сервере GMod .

Конфигурация через монтирование или добавление

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

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

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

Нажмите, чтобы чирикать

Этот метод настройки не исключает использование etcd или Consul . Скорее, он просто абстрагирует логику выполнения любой используемой интеграции конфигурации, будь то etcd, Consul, Chef или простое извлечение файла с удаленного сервера. Тонкое преимущество этого метода заключается в том, что вы можете протестировать свой контейнер, подключив предварительно сгенерированную конфигурацию из локальной папки. В вашей производственной среде вы можете заменить его на специальный контейнер.

Чтобы увидеть пример монтирования конфигурации, проверьте любой репозиторий контейнера, монтирующий том или добавляющий файл для конфигурации, такой как tutum / mysql . При этом используется простой набор команд ADD для файлов.

1
2
3
# Add MySQL configuration
 
ADD my.cnf /etc/mysql/conf.d/my.cnf ADD mysqld_charset.cnf /etc/mysql/conf.d/mysqld_charset.cnf

Разделение задач

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

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

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

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

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

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

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

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

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

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

Нажмите, чтобы чирикать

Ваше окончательное развертывание может выглядеть примерно так:

1
2
3
4
5
6
# start a primary app container exposing volumes for /opt/logs and exposing /opt/tmp/app.socket via a port
docker run -v /opt/data:/opt/data --name=myapp1 foo/myapp
# start a monitor container, polling /opt/logs for events
docker run --volumes-from myapp1 --name=myapp1-monitor foo/mypp-monitor
# start a temporary container to use an exposed port from myapp-1 to interact with the running application instance, and execute a create user command
docker run -it --rm --link myapp-1:myapp --name=myapp1-controller foo/myapp-controller create user test

Посмотрите на bfosberry / TeamSpeak, а также на соответствующий наблюдатель и контроллер.

Разделите все хранилища данных

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

Некоторое обнаружение может быть задействовано, чтобы определить, какие компоненты приложения требуют извлечения. Однако тома хранения имеют тенденцию быть универсально совместимыми для этой цели. Рассмотрим монтирование постоянных данных в контейнере Postgres через монтирование тома.

1
2
VOLUME ["/var/lib/postgresql"]
VOLUME ["/run/postgresql"]

Не демонизировать

Легкая ошибка — написать простой Dockerfile, например так:

1
2
3
FROM ubuntu
RUN apt-get install -y mysql-server
CMD start.sh

где start.sh выглядит примерно так:

1
/etc/init.d mysql start && tail -f /var/log/mysql.log

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

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

Вот пример запуска Apache на переднем плане из tutum / apache-php .

1
2
3
source /etc/apache2/envvars
tail -F /var/log/apache2/* &
exec apache2 -D FOREGROUND

Вход в инфраструктуру

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

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

Следуйте рекомендациям CMD

Довольно простой выигрыш при разработке набора контейнеров состоит в том, чтобы придерживаться [передового опыта использования ENTRYPOINT и CMD .

Используя общую точку входа для всех ваших контейнеров, используя разные команды в зависимости от контекста использования, вы можете повторно использовать один и тот же образ контейнера в различных частях развертывания приложения. Это уменьшит размер вашего хранилища и изображения. В тех случаях, когда мое основное приложение имеет большой размер (4 ГБ и больше), я стараюсь избегать этого, чтобы уменьшить количество контейнеров, использующих большие изображения. Для примера этого проверьте mysql / mysql-server .

Барьеры для контейнеризации заявлений

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

Самостоятельное обнаружение может не поддерживать динамические порты

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

Простейшим способом решения этой проблемы является выделение внешних портов в вашей инфраструктуре и использование прямого статического сопоставления в контейнере приложения. В этом случае ваше приложение должно быть настроено на использование динамического внутреннего порта через некоторый параметр конфигурации. Вы можете увидеть пример этого в моем репозитории на GMod Server .

Некоторые порты могут не настраиваться

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

Запрос динамической привязки для 12345 и 12345 / udp может обеспечить сопоставление с двумя разными номерами внутренних портов. Статическое сопоставление портов — самое простое решение этой проблемы.

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

Некоторые приложения не подходят для динамического развертывания

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

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

Одно из решений состоит в том, чтобы оставить «патч» на месте в старом контейнере для пересылки трафика со старого хоста на новый, динамически сопоставляя порты. С некоторыми из более продвинутых решений SDN, доступных для контейнерных развертываний, эту проблему можно решить с помощью маршрутизируемых IP-адресов. Это позволяет нам рассматривать многие статически развернутые приложения как высокодоступный динамический ресурс.

Примеры

Вы можете найти ряд примеров этих концепций в рамках проекта схемы GSCS .

«Контейнерные приложения» через @codeship

Нажмите, чтобы чирикать

Ссылка: Контейнерные приложения с отзывами от нашего партнера JCG Брендана Фосберри в блоге Codeship Blog .