- PVSM.RU - https://www.pvsm.ru -
В конце прошлого года компания Red Hat опубликовала [1] доклад с описанием принципов, которым должны соответствовать контейнеризированные приложения, стремящиеся к тому, чтобы стать органичной частью «облачного» мира: «Следование этим принципам обеспечит готовность приложений к автоматизируемости на таких платформах для облачных приложений, как Kubernetes», — считают в Red Hat. И мы, изучив этот документ, с их выводами согласны, а посему решили поделиться ими с русскоязычным ИТ-сообществом.

Обратите внимание, что эта статья является не дословным переводом оригинального документа (PDF [2]), подготовленного Bilgin Ibryam [3] — архитектором из Red Hat, активным участником нескольких проектов Apache и автором книг «Camel Design Patterns» и «Kubernetes Patterns», — а представляет основные его тезисы в довольно свободном изложении.
Как правило, с облачными (cloud native) приложениями можно предвидеть отказы, а их функционирование и масштабирование возможно даже тогда, когда нижележащая инфраструктура испытывает проблемы. Чтобы это стало возможным, платформы, предназначенные для запуска таких приложений, накладывают определённые обязательства и ограничения на запускаемые в них приложения. Если кратко, то приложение недостаточно просто поместить в контейнер и запустить — возможность его эффективной оркестровки в платформах вроде Kubernetes требует дополнительных усилий. Каковы же они?
Предлагаемые здесь идеи созданы под вдохновением различных других работ (например,
The Twelve-Factor App [4]), затрагивающих многие области: от управления исходным кодом до моделей масштабируемости приложений. Однако область применения рассматриваемых здесь принципов ограничена проектированием контейнеризированных приложений, основанных на микросервисах, для cloud native-платформ вроде Kubernetes.
В описании всех принципов в качестве основного примитива используется образ контейнера, а в качестве целевого окружения для его запуска — платформа оркестровки контейнеров. Следование этим принципам призвано гарантировать, что (подготовленные в соответствии с ними) контейнеры получат полноценную поддержку в большинстве движков оркестровки, т.е. будут обслуживаться планировщиком, масштабироваться и мониториться автоматизированно. Принципы перечислены в случайном (а не приоритетном) порядке.
Во многих смыслах SCP аналогичен принципу единственной ответственности (Single Responsibility Principle, SRP) в SOLID [5], говорящему о том, что каждый класс должен иметь одну ответственность. Стоящая за SRP мотивация — должна быть лишь одна причина, которая может привести к изменению класса.
Слово «concern» (переводится как «озабоченность», «беспокойство», «интерес», «задача») подчёркивает, что озабоченность — более высокий уровень абстракции, чем ответственность, который лучше описывает спектр задач контейнера (по сравнению с классом). Если главной мотивацией для SRP является единственная причина для изменения, то для SCP — возможность повторного использования образа контейнера и его заменяемость. Если вы создаёте контейнер, отвечающий за одну задачу, и он полностью её решает, вырастает вероятность повторного использования этого образа в других обстоятельствах.
В общем, принцип SCP гласит, что каждый контейнер должен решать единственную проблему и делать это хорошо (на ум сразу приходит классика из философии UNIX — DOTADIW [6], «Do one thing and do it well» — прим. перев.). Если же микросервису необходимо отвечать за множество проблем, можно использовать такие паттерны, как sidecar- и init-контейнеры, для объединения множества контейнеров в единую развёртываемую площадку (под), где каждый контейнер будет по-прежнему заниматься единственной задачей.

Контейнеры — унифицированный способ упаковывать и запускать приложения, превращающий их в «чёрный ящик». Однако любой контейнер должен предоставлять программные интерфейсы приложения (API) для окружения, в котором он исполняется, делая возможным мониторинг состояния и поведения контейнера. Это необходимое условие для возможности автоматизации обновлений контейнера и сопровождения его жизненного цикла.
С практической точки зрения, контейнеризированное приложение должно предоставлять хотя бы (как минимум!) API для различных проверок его состояния: liveness (работоспособность) и readiness (готовность к обслуживанию запросов). Ещё лучше, если предлагаются и другие способы отслеживать состояние приложения — в частности, логирование важных событий в STDERR и STDOUT для их последующей агрегации утилитами вроде Fluentd и Logstash, интеграции с инструментами для сборки метрик: OpenTracing, Prometheus и др.

Вывод таков: обращайтесь со своим приложением как с чёрным ящиком, но реализуйте все необходимые API, помогающие платформе следить за приложением и управлять им настолько хорошо, насколько это возможно.
Если HOP говорит о предоставлении API, из которых сможет «читать» платформа, то LCP — это обратная сторона: у вашего приложения должна быть возможность узнавать о событиях из платформы. И даже более того: не только узнавать о них, но и реагировать на них — отсюда происходит и название этого принципа («conformance» переводится как «соответствовать», «согласоваться», «подчиняться правилам»).
У управляющей платформы может быть множество событий, которые помогут в управлении жизненным циклом контейнера, но некоторые из них важнее других. Например, для корректного завершения работы процесса приложению необходимо получить сообщение с соответствующим сигналом (SIGTERM) во избежание срочного прекращения работы через SIGKILL. Бывают и другие значимые события — например, PostStart и PreStop, которые необходимы для «прогрева» приложения в начале его работы или, наоборот, освобождения ресурсов при завершении.
В контейнеризированных приложениях закладывается неизменность (immutability): их собирают один раз, после чего они запускаются без изменений в разных окружениях. Это подразумевает использование внешних инструментов для хранения используемых в их работе данных, а также создание/применение различных конфигураций для разных окружений. Любое изменение в контейнеризированном приложении должно приводить к сборке нового образа контейнера, которые будет использоваться во всех окружениях. Этот же принцип, известный как immutable infrastructure, используется для управления серверной инфраструктурой.

Одна из главных причин перехода на контейнеризированные приложения — контейнеры должны быть настолько недолговечными, насколько это возможно, и готовыми к замене другим контейнером в любой момент времени. Причин заменить контейнер может быть много: проверка состояния, обратное масштабирование (scale down) приложения, миграция на другой хост, нехватка ресурсов…
Поэтому контейнеризированным приложениям необходимо поддерживать своё состояние распределённым и избыточным. Кроме того, приложение должно быстро стартовать и останавливаться и даже быть готовым к внезапному (и полному) аппаратному сбою. Другая полезная практика в реализации этого принципа — создание маленьких контейнеров, т.к. контейнеры автоматически запускаются на разных хостах, и их меньший размер ускорит время запуска (поскольку предварительно их нужно физически скопировать на хостовую систему).
Контейнер должен содержать всё необходимое на момент сборки, полагаясь лишь на наличие ядра Linux (все дополнительные библиотеки «появляются» в момент сборки). Помимо библиотек это означает также необходимость содержать исполняемые среды языков программирования, платформу приложений (если используется) и любые другие зависимости для запуска контейнеризированного приложения. Единственное исключение здесь составляют конфигурации, которые будут разными в разных окружениях и должны предоставляться во время запуска (пример — ConfigMap в Kubernetes).

Некоторые приложения состоят из множества контейнеризированных компонентов. Например, контейнеризированное веб-приложение может требовать контейнера с базой данных. Этот принцип не предлагает объединять контейнеры: просто у контейнера с базой данных должно быть всё необходимое для её работы, а контейнера с веб-приложением — для работы веб-приложения (веб-сервер и т.д.).
Принцип S-CP рассматривает контейнеры с перспективы времени сборки и результирующего бинарника с его содержимым, однако контейнер — это не одномерный чёрный ящик, лежащий на диске. Другие «измерения» контейнера появляются при его запуске — это «измерения» потребления памяти, процессора и других ресурсов.

Любой контейнер должен объявлять свои требования к ресурсам и передавать эту информацию платформе, поскольку его запросы на CPU, память, сеть, диск влияют на то, как платформа выполняет планирование, автомасштабирование, управление ресурсами, обеспечивает общий уровень SLA для контейнера. Кроме того, важно, чтобы приложение умещалось в выделенные ей ресурсы. В случае нехватки ресурсов платформа с меньшей вероятностью будет останавливать или мигрировать такие контейнеры.
В дополнение к этим принципам предлагаются менее фундаментальные, но всё же тоже зачастую полезные практики, относящиеся к контейнерам:
EXPOSE упрощает использование образов и для людей, и для ПО.Ссылки на дополнительные ресурсы о паттернах и лучших практиках по теме:
Про некоторые из этих принципов — в частности, про Image Immutability Principle (IIP), который мы назвали как «One image to rule them all», и Self-Containment Principle (S-CP) — рассказывалось в нашем докладе «Лучшие практики CI/CD с Kubernetes и GitLab [13]» (по ссылке — текстовая выжимка и полное видео).
Читайте также в нашем блоге:
Автор: Дмитрий Шурупов
Источник [18]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/red-hat/277731
Ссылки в тексте:
[1] опубликовала: https://www.redhat.com/en/resources/cloud-native-container-design-whitepaper
[2] PDF: https://www.redhat.com/cms/managed-files/cl-cloud-native-container-design-whitepaper-f8808kc-201710-v3-en.pdf
[3] Bilgin Ibryam: https://developers.redhat.com/blog/author/bibryam/
[4] The Twelve-Factor App: https://12factor.net/
[5] SOLID: https://ru.wikipedia.org/wiki/SOLID_(%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)
[6] DOTADIW: https://en.wikipedia.org/wiki/Unix_philosophy#Do_One_Thing_and_Do_It_Well
[7] Container Patterns: https://www.slideshare.net/luebken/container-patterns
[8] Best practices for writing Dockerfiles: https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices
[9] Container Best Practices: http://docs.projectatomic.io/container-best-practices
[10] OpenShift Enterprise 3.0 Creating Images Guidelines: https://docs.openshift.com/enterprise/3.0/creating_images/guidelines.html
[11] Design patterns for container-based distributed systems: https://www.usenix.org/system/files/conference/hotcloud16/hotcloud16_burns.pdf
[12] Kubernetes Patterns: https://leanpub.com/k8spatterns/
[13] Лучшие практики CI/CD с Kubernetes и GitLab: https://habrahabr.ru/company/flant/blog/345116/
[14] Смерть микросервисного безумия в 2018 году: https://habrahabr.ru/company/flant/blog/347518/
[15] Путеводитель CNCF по решениям Open Source (и не только) для cloud native: https://habrahabr.ru/company/flant/blog/350928/
[16] Сколько разработчиков думают, что Continuous Integration не нужна?: https://habrahabr.ru/company/flant/blog/346418/
[17] Наш опыт с Kubernetes в небольших проектах: https://habrahabr.ru/company/flant/blog/331188/
[18] Источник: https://habrahabr.ru/post/353272/?utm_source=habrahabr&utm_medium=rss&utm_campaign=353272
Нажмите здесь для печати.