Из каких компонентов должен состоять образ контейнеризированного приложения
В этом учебнике рассмотрены следующие задачи.
Предварительные требования
Установите следующие необходимые компоненты:
Дерево папок будет выглядеть следующим образом:
Команда dotnet new создает папку с именем App и консольное приложение Hello World. Измените каталоги и перейдите в папку App из сеанса терминала. Используйте команду dotnet run , чтобы запустить приложение. Приложение запустится и выведет Hello World! под командой:
Шаблон по умолчанию создает приложение, которое выводит текст в терминал и затем завершает работу. В этом руководстве описано, как использовать приложение с бесконечным циклом выполнения. Откройте файл Program.cs в текстовом редакторе.
Если вы используете Visual Studio Code, в предыдущем сеансе терминала введите следующую команду:
Откроется папка App, которая содержит проект в Visual Studio Code.
Замените его кодом, который считает числа каждую секунду:
Сохраните файл и протестируйте программу еще раз с помощью команды dotnet run . Помните, что это приложение выполняется бесконечно. Остановите его с помощью команды отмены, нажав клавиши CTRL+C . Ниже представлен пример таких выходных данных:
Если приложению передать число в командной строке, оно досчитает до такого числа и завершит работу. Введите команду dotnet run -- 5 , чтобы приложение досчитало до пяти.
Все параметры после -- не передаются команде dotnet run , а передаются в приложение.
Эта команда компилирует приложение и помещает результат в папку publish. Путь к папке publish из рабочей папки должен быть таким: .\App\bin\Release\net5.0\publish\
Получите список файлов для папки publish из папки App, чтобы убедиться, что файл NetCore.Docker.dll создан.
Воспользуйтесь командой ls , чтобы получить список каталога и проверить, был ли создан файл NetCore.Docker.dll.
Создание файла Dockerfile
Файл Dockerfile используется командой docker build для создания образа контейнера. Это текстовый файл с именем Dockerfile, не имеющий расширения.
Сохраните файл Dockerfile. Структура каталогов рабочей папки должна выглядеть следующим образом. Некоторые файлы и папки на более глубоком уровне были опущены для экономии места в статье:
В терминале выполните следующую команду:
Docker обработает все строки файла Dockerfile. Символ . в команде docker build используется, чтобы выполнить с помощью Docker поиск файла Dockerfile в текущей папке. Эта команда создает образ и локальный репозиторий с именем counter-image, который указывает на такой образ. После завершения работы этой команды выполните команду docker images , чтобы просмотреть список установленных образов:
Обратите внимание, что два образа имеют одинаковое значение IMAGE ID. Это связано с тем, что единственная команда в файле Dockerfile создает новый образ на основе существующего. Добавим в файл Dockerfile еще три команды. Каждая команда (или инструкция)создает новый уровень образа —, а последняя команда представляет получившуюся точку входа в репозиторий counter-image.
Команда COPY предписывает Docker скопировать указанную папку на вашем компьютере в папку в контейнере. В этом примере папка publish копируется в папку с именем App в контейнере.
Команда WORKDIR изменяет текущий каталог в контейнере на App.
Следующая команда ENTRYPOINT используется, чтобы настроить с помощью Docker контейнер для запуска в качестве исполняемого файла. При запуске контейнера выполняется команда ENTRYPOINT . После выполнения команды контейнер автоматически остановится.
Для повышения безопасности вы можете отказаться от конвейера диагностики. Такой отказ позволяет контейнеру выполняться в режиме только для чтения. Для этого укажите переменную среды DOTNET_EnableDiagnostics как 0 (непосредственно перед шагом ENTRYPOINT ).
В окне терминала выполните команду docker build -t counter-image -f Dockerfile . , а после ее выполнения — команду docker images .
Каждая команда в файле Dockerfile создает уровень и экземпляр IMAGE ID. Последний экземпляр IMAGE ID (ваш идентификатор будет отличаться) имеет значение cd11c3df9b19. Теперь вы создадите контейнер на основе этого образа.
Создание контейнера
Теперь, когда у вас есть образ, содержащий приложение, вы можете создать контейнер. Контейнер можно создать двумя способами. Сначала создайте остановленный контейнер.
Команда docker create выше создает контейнер на основе образа counter-image. В выходных данных этой команды присутствует CONTAINER ID (ваш идентификатор будет отличаться) созданного контейнера. Чтобы просмотреть список всех контейнеров, воспользуйтесь командой docker ps -a :
Управление контейнером
Контейнер был создан с определенным именем core-counter . Для управления контейнером используется это имя. В следующем примере используется команда docker start для запуска контейнера, а затем — команда docker ps для отображения только запущенных контейнеров:
Аналогично, команда docker stop останавливает контейнер. В следующем примере используется команда docker stop для остановки контейнера, а затем — команда docker ps для подтверждения того, что контейнеры не запущены:
Подключение к контейнеру
После запуска контейнера вы можете подключиться к нему, чтобы просмотреть выходные данные. С помощью команд docker start и docker attach запустите контейнер и просмотрите поток вывода. В этом примере команда, вызываемая нажатием клавиш CTRL+C , используется для отключения от запущенного контейнера. Нажатие клавиш завершает процесс в контейнере, если не указано иное, что приведет к остановке контейнера. Параметр --sig-proxy=false гарантирует, что команда, вызываемая нажатием клавиш CTRL+C , не остановит процесс в контейнере.
После отключения от контейнера снова подключитесь к нему, чтобы убедиться в том, что он продолжает работать и считать числа.
Удаление контейнера
В рамках этого руководства предполагается, что вам не нужны контейнеры, которые запущены, но не выполняют полезные действия. Удалите созданный ранее контейнер. Если контейнер запущен, остановите его.
В примере ниже выводится список всех контейнеров, а затем используется команда docker rm для удаления контейнера. После этого выполняется повторная проверка наличия запущенных контейнеров.
Однократный запуск
Docker предоставляет единую команду docker run для создания и запуска контейнера. Она исключает необходимость в поочередном выполнении команд docker create и docker start . Вы также можете настроить ее для автоматического удаления контейнера при его остановке. Например, команда docker run -it --rm выполняет две операции. Сначала она автоматически подключается к контейнеру с помощью текущего терминала, а потом, после завершения работы контейнера, удаляет его:
Во время выполнения docker run -it команда, вызываемая нажатием клавиш CTRL+C , остановит процесс, запущенный в контейнере. А это, в свою очередь, приведет к остановке контейнера. Так как в команде указан параметр --rm , контейнер автоматически удалится после остановки процесса. Убедитесь, что он больше не существует:
Изменение команды ENTRYPOINT
Команда docker run также позволяет изменить команду ENTRYPOINT из файла Dockerfile для запуска другой программы, но только для соответствующего контейнера. Например, воспользуйтесь указанной ниже командой, чтобы запустить bash или cmd.exe . При необходимости измените команду.
В этом примере команда ENTRYPOINT изменена на cmd.exe . Нажав клавиши CTRL+C , вы можете завершить процесс и остановить контейнер.
В этом примере команда ENTRYPOINT изменена на bash . Команда exit позволяет завершить процесс и остановить контейнер.
Основные команды
В Docker есть множество различных команд, которые создают контейнеры и образы, управляют ими, а также взаимодействуют с ними. Для управления контейнерами в основном используются такие команды Docker:
Очистка ресурсов
В этом учебнике описано, как создать контейнеры и образы. При желании эти ресурсы можно удалить. Ниже представлены команды, которые позволяют сделать следующее:
Вывести список всех контейнеров.
Остановить запущенные контейнеры по имени.
С помощью команды docker images просмотрите список установленных образов.
Файлы образов могут иметь большой размер. Как правило, удаляются временные контейнеры, созданные в ходе тестирования и разработки приложения. При этом рекомендуется оставить базовые образы с установленной средой выполнения, если на ее основе вы планируете создавать другие образы.
В конце прошлого года компания Red Hat опубликовала доклад с описанием принципов, которым должны соответствовать контейнеризированные приложения, стремящиеся к тому, чтобы стать органичной частью «облачного» мира: «Следование этим принципам обеспечит готовность приложений к автоматизируемости на таких платформах для облачных приложений, как Kubernetes», — считают в Red Hat. И мы, изучив этот документ, с их выводами согласны, а посему решили поделиться ими с русскоязычным ИТ-сообществом.
Обратите внимание, что эта статья является не дословным переводом оригинального документа (PDF), подготовленного Bilgin Ibryam — архитектором из Red Hat, активным участником нескольких проектов Apache и автором книг «Camel Design Patterns» и «Kubernetes Patterns», — а представляет основные его тезисы в довольно свободном изложении.
Как правило, с облачными (cloud native) приложениями можно предвидеть отказы, а их функционирование и масштабирование возможно даже тогда, когда нижележащая инфраструктура испытывает проблемы. Чтобы это стало возможным, платформы, предназначенные для запуска таких приложений, накладывают определённые обязательства и ограничения на запускаемые в них приложения. Если кратко, то приложение недостаточно просто поместить в контейнер и запустить — возможность его эффективной оркестровки в платформах вроде Kubernetes требует дополнительных усилий. Каковы же они?
Подход Red Hat к приложениям cloud native
Предлагаемые здесь идеи созданы под вдохновением различных других работ (например,
The Twelve-Factor App), затрагивающих многие области: от управления исходным кодом до моделей масштабируемости приложений. Однако область применения рассматриваемых здесь принципов ограничена проектированием контейнеризированных приложений, основанных на микросервисах, для cloud native-платформ вроде Kubernetes.
В описании всех принципов в качестве основного примитива используется образ контейнера, а в качестве целевого окружения для его запуска — платформа оркестровки контейнеров. Следование этим принципам призвано гарантировать, что (подготовленные в соответствии с ними) контейнеры получат полноценную поддержку в большинстве движков оркестровки, т.е. будут обслуживаться планировщиком, масштабироваться и мониториться автоматизированно. Принципы перечислены в случайном (а не приоритетном) порядке.
1. Single Concern Principle (SCP)
Во многих смыслах SCP аналогичен принципу единственной ответственности (Single Responsibility Principle, SRP) в SOLID, говорящему о том, что каждый класс должен иметь одну ответственность. Стоящая за SRP мотивация — должна быть лишь одна причина, которая может привести к изменению класса.
Слово «concern» (переводится как «озабоченность», «беспокойство», «интерес», «задача») подчёркивает, что озабоченность — более высокий уровень абстракции, чем ответственность, который лучше описывает спектр задач контейнера (по сравнению с классом). Если главной мотивацией для SRP является единственная причина для изменения, то для SCP — возможность повторного использования образа контейнера и его заменяемость. Если вы создаёте контейнер, отвечающий за одну задачу, и он полностью её решает, вырастает вероятность повторного использования этого образа в других обстоятельствах.
В общем, принцип SCP гласит, что каждый контейнер должен решать единственную проблему и делать это хорошо (на ум сразу приходит классика из философии UNIX — DOTADIW, «Do one thing and do it well» — прим. перев.). Если же микросервису необходимо отвечать за множество проблем, можно использовать такие паттерны, как sidecar- и init-контейнеры, для объединения множества контейнеров в единую развёртываемую площадку (под), где каждый контейнер будет по-прежнему заниматься единственной задачей.
2. High Observability Principle (HOP)
Контейнеры — унифицированный способ упаковывать и запускать приложения, превращающий их в «чёрный ящик». Однако любой контейнер должен предоставлять программные интерфейсы приложения (API) для окружения, в котором он исполняется, делая возможным мониторинг состояния и поведения контейнера. Это необходимое условие для возможности автоматизации обновлений контейнера и сопровождения его жизненного цикла.
С практической точки зрения, контейнеризированное приложение должно предоставлять хотя бы (как минимум!) API для различных проверок его состояния: liveness (работоспособность) и readiness (готовность к обслуживанию запросов). Ещё лучше, если предлагаются и другие способы отслеживать состояние приложения — в частности, логирование важных событий в STDERR и STDOUT для их последующей агрегации утилитами вроде Fluentd и Logstash, интеграции с инструментами для сборки метрик: OpenTracing, Prometheus и др.
Вывод таков: обращайтесь со своим приложением как с чёрным ящиком, но реализуйте все необходимые API, помогающие платформе следить за приложением и управлять им настолько хорошо, насколько это возможно.
3. Life-cycle Conformance Principle (LCP)
Если HOP говорит о предоставлении API, из которых сможет «читать» платформа, то LCP — это обратная сторона: у вашего приложения должна быть возможность узнавать о событиях из платформы. И даже более того: не только узнавать о них, но и реагировать на них — отсюда происходит и название этого принципа («conformance» переводится как «соответствовать», «согласоваться», «подчиняться правилам»).
4. Image Immutability Principle (IIP)
В контейнеризированных приложениях закладывается неизменность (immutability): их собирают один раз, после чего они запускаются без изменений в разных окружениях. Это подразумевает использование внешних инструментов для хранения используемых в их работе данных, а также создание/применение различных конфигураций для разных окружений. Любое изменение в контейнеризированном приложении должно приводить к сборке нового образа контейнера, которые будет использоваться во всех окружениях. Этот же принцип, известный как immutable infrastructure, используется для управления серверной инфраструктурой.
5. Process Disposability Principle (PDP)
Одна из главных причин перехода на контейнеризированные приложения — контейнеры должны быть настолько недолговечными, насколько это возможно, и готовыми к замене другим контейнером в любой момент времени. Причин заменить контейнер может быть много: проверка состояния, обратное масштабирование (scale down) приложения, миграция на другой хост, нехватка ресурсов…
Поэтому контейнеризированным приложениям необходимо поддерживать своё состояние распределённым и избыточным. Кроме того, приложение должно быстро стартовать и останавливаться и даже быть готовым к внезапному (и полному) аппаратному сбою. Другая полезная практика в реализации этого принципа — создание маленьких контейнеров, т.к. контейнеры автоматически запускаются на разных хостах, и их меньший размер ускорит время запуска (поскольку предварительно их нужно физически скопировать на хостовую систему).
6. Self-Containment Principle (S-CP)
Контейнер должен содержать всё необходимое на момент сборки, полагаясь лишь на наличие ядра Linux (все дополнительные библиотеки «появляются» в момент сборки). Помимо библиотек это означает также необходимость содержать исполняемые среды языков программирования, платформу приложений (если используется) и любые другие зависимости для запуска контейнеризированного приложения. Единственное исключение здесь составляют конфигурации, которые будут разными в разных окружениях и должны предоставляться во время запуска (пример — ConfigMap в Kubernetes).
Некоторые приложения состоят из множества контейнеризированных компонентов. Например, контейнеризированное веб-приложение может требовать контейнера с базой данных. Этот принцип не предлагает объединять контейнеры: просто у контейнера с базой данных должно быть всё необходимое для её работы, а контейнера с веб-приложением — для работы веб-приложения (веб-сервер и т.д.).
7. Runtime Confinement Principle (RCP)
Принцип S-CP рассматривает контейнеры с перспективы времени сборки и результирующего бинарника с его содержимым, однако контейнер — это не одномерный чёрный ящик, лежащий на диске. Другие «измерения» контейнера появляются при его запуске — это «измерения» потребления памяти, процессора и других ресурсов.
Любой контейнер должен объявлять свои требования к ресурсам и передавать эту информацию платформе, поскольку его запросы на CPU, память, сеть, диск влияют на то, как платформа выполняет планирование, автомасштабирование, управление ресурсами, обеспечивает общий уровень SLA для контейнера. Кроме того, важно, чтобы приложение умещалось в выделенные ей ресурсы. В случае нехватки ресурсов платформа с меньшей вероятностью будет останавливать или мигрировать такие контейнеры.
Другие рекомендации
В дополнение к этим принципам предлагаются менее фундаментальные, но всё же тоже зачастую полезные практики, относящиеся к контейнерам:
- Стремитесь к маленьким образам. Удаляйте временные файлы и избегайте установки ненужных пакетов. Это сокращает не только размер контейнера, но и время сборки, а также время передачи данных по сети при копировании образов.
- Поддерживайте любые UID. Избегайте использования команды sudo или требования конкретного пользователя/UID для запуска контейнера.
- Отмечайте важные порты. Их обозначение с помощью команды EXPOSE упрощает использование образов и для людей, и для ПО.
- Используйте тома для постоянных данных (таких, что должны быть сохранены после уничтожения контейнера).
- Определяйте метаданные в образах — с помощью тегов, лейблов, аннотаций. Это упрощает их дальнейшее использование разработчиками.
- Синхронизируйте хост и образ. Некоторым контейнеризированным приложениям может требоваться синхронизация с хостом для определённых атрибутов (например, времени и идентификатора машины).
- Container Patterns(Matthias Luebken);
- Best practices for writing Dockerfiles(Docker);
- Container Best Practices(Project Atomic);
- OpenShift Enterprise 3.0 Creating Images Guidelines(Red Hat);
- Design patterns for container-based distributed systems(Brendan Burns, David Oppenheimer);
- Kubernetes Patterns(Bilgin Ibryam, Roland Huß);
- The Twelve-Factor App(Adam Wiggins).
P.S. от переводчика
Про некоторые из этих принципов — в частности, про Image Immutability Principle (IIP), который мы назвали как «One image to rule them all», и Self-Containment Principle (S-CP) — рассказывалось в нашем докладе «Лучшие практики CI/CD с Kubernetes и GitLab» (по ссылке — текстовая выжимка и полное видео).
Рассказываем, как контейнеры ускоряют разработку, помогают создавать и развертывать ПО быстрее и безопаснее.
Что такое контейнеризация
Контейнеризация - это виртуализация ресурсов на уровне операционной системы. Она позволяет запускать приложения в отдельных пользовательских пространствах — контейнерах.
При их запуске не нужно настраивать отдельную гостевую ОС для каждого контейнера. Все они используют ядро одной операционной системы. Оно способно поддерживать одновременную работу тысяч контейнеров и обеспечивает их полную изоляцию друг от друга.
Контейнеризация приложений
Если упрощенно, то контейнеризация позволяет писать приложения один раз и запускать их где угодно. Возможность переносить приложения на другие платформы важна для программистов — она ускоряет цикл разработки и упрощает отладку ПО. Вдобавок контейнеризация дает и другие преимущества: изоляцию ошибок, простоту управления и безопасность.
Контейнеры инкапсулируют приложение как единый исполняемый пакет программного обеспечения. Он объединяет код приложения вместе со всеми соответствующими файлами конфигурации, библиотеками и зависимостями, необходимыми для его работы.
Абстрагирование от операционной системы хоста делает контейнерные приложения переносимыми и способными работать без сбоев на любой платформе или в облаке. Контейнеры будут работать, если их перенести с ПК на виртуальную машину или c ОС Linux на Windows.
Контейнеризация позволяет разработчикам создавать и развертывать приложения быстрее и безопаснее, независимо от того, является ли приложение монолитом или модульным микросервисом. Кроме того, благодаря контейнерам можно
переупаковать уже существующие монолитные приложения в контейнерные микросервисы.
Преимущества контейнеров
Переносимость: контейнер создает исполняемый пакет программного обеспечения, который может работать на любой платформе или в облаке.
Скорость: контейнеры совместно используют ядро операционной системы. Это повышает эффективность сервера и ускоряет время запуска.
Изоляция ошибок: каждое контейнерное приложение изолировано и работает независимо. Выход из строя одного контейнера не влияет на работу остальных. Команды разработчиков могут выявлять и исправлять любые технические проблемы в одном контейнере не затрагивая работу всех прочих.
Простота управления: сервис оркестровки контейнеров автоматизирует установку, масштабирование и управление контейнерными рабочими нагрузками и службами.
Безопасность: изоляция приложений предотвращает проникновение вредоносного кода в другие контейнеры или хост-систему. Кроме того, можно настроить разрешения безопасности, чтобы автоматически блокировать попадание нежелательных компонентов в контейнеры или ограничить обмен данными с ненужными ресурсами.
Микросервисы и контейнеризация
Архитектура приложений может быть разной. Рассмотрим монолитную и микросервисную.
Монолит — это единое и неделимое приложение. Все компоненты в нем объединены в одну программу на одной платформе. Обычно монолитное приложение состоит из базы данных, пользовательского интерфейса и серверного приложения. Все части программного обеспечения унифицированы, и все его функции управляются в одном месте. Любые изменения монолитного приложения требуют пересборки и развертывания всего монолита.
Микросервисное приложение — это набор слабосвязанных сервисов. У каждого из них своя база данных и бизнес-логика. Между собой сервисы общаются при помощи API. Каждый микросервис способен обновляться и развертываться независимо от остальных. Это ускоряет разработку, тестирование и развертывание.
Концепции микросервисов и контейнеризации схожи — те и другие преобразуют приложения в наборы небольших сервисов или компонентов. Эти компоненты переносимы, масштабируемы и просты в управлении.
Mикросервисы и контейнеризация хорошо работают вместе. Контейнеры обеспечивают легкую инкапсуляцию любого приложения — монолита и модульного микросервиса. Микросервис, разработанный в контейнере, получает все преимущества контейнеризации: переносимость и совместимость с разными платформами, гибкость в разработке, изоляцию ошибок, масштабирование и безопасность.
Контейнеризация vs виртуализация
Контейнеризация — это изоляция на уровне процессов, а виртуализация — на уровне виртуальных машин. Изоляция процесса — это ограничение объема системных ресурсов, которые использует контейнер. То есть он не может расходовать CPU и память, зарезервированные для других процессов. Это как если бы под выполнение каждого процесса выделялась отдельная машина.
Обе технологии позволяют запускать несколько типов программного обеспечения в одной среде. Однако контейнеризация дает значительные преимущества и вот почему.
Виртуализация позволяет нескольким операционным системам и программным приложениям работать одновременно и совместно использовать ресурсы одного физического сервера. Каждое приложение и связанные с ним файлы, библиотеки и зависимости, включая копию ОС, упаковываются вместе как виртуальная машина.
Контейнеризация использует вычислительные ресурсы эффективнее. Контейнер создает единый исполняемый пакет программного обеспечения. Он занимает меньше места и благодаря этому несколько контейнеров могут работать на той же вычислительной мощности, что и одна виртуальная машина. Именно поэтому контейнеры и называют легковесными.
Контейнеризация и Yandex.Cloud
Платформа предоставляет доступ к Yandex Container Registry — сервису для управления образами и контейнерами Docker и Yandex Managed Service for Kubernetes — сервису для управления кластерами контейнеров Kubernetes.
Разработчики получают гибкие, готовые к работе сервисы для разработки, тестирования и развертывания, а платят только за фактически использованные ресурсы.
Если статья оказалась полезной, ставьте 👍
Не забывайте подписываться на наши соцсети: Вконтакте , Facebook , Telegram , VC , YouTube .
Цитируя разработчиков Docker, «контейнер — это стандартная единица программного обеспечения, в которую упаковано приложение со всеми необходимыми для его работы зависимостями — кодом приложения, средой запуска, системными инструментами, библиотеками и настройками».
Контейнеры используются уже более десяти лет и на сегодняшний день примерно четверть компаний-лидеров в сфере IT задействуют контейнерные решения в продакшене, а ещё столько же, согласно опросам, планировали приступить к этому в 2019-м году.
На рынке существует немало решений, представляющих среды запуска контейнеров и оркестрации, таких как CoreOS rkt, LXC, OpenVZ, containerd, Apache Mesos и Docker Swarm. Однако более 4/5 контейнеров запускается в среде Docker, а для оркестрации более половины пользователей выбрали Kubernetes. Об этих системах мы и поговорим.
Чем полезны контейнеры
Легковесность, быстродействие и возможность работать на высоком уровне абстракции, делегируя проблемы с железом и ОС провайдеру, — это преимущества контейнеров, позволяющие снизить операционные расходы, связанные с разработкой и эксплуатацией приложений, делающих решения на их базе столь привлекательными для бизнеса.
Техническим же специалистам контейнеры прежде всего полюбились за возможность упаковать приложение вместе с его средой запуска, решая тем самым проблему зависимостей в разных окружениях. Например, различие версий языковых библиотек на ноутбуке разработчика и в последующих окружениях рано или поздно приведёт к сбоям, и нужно будет как минимум потратить время на их анализ, а как максимум — решать проблему проникших в продакшен багов. Использование контейнеров устраняет проблему «А на моей машине все работало! ¯\_(ツ)_/¯».
Также контейнеры позволяют сократить время разработки приложения и упрощают управление им в продакшене благодаря лёгкости в настройке и изменении конфигурации, возможности версионировать её вместе с кодом приложения и удобным инструментам оркестрирования, позволяющим быстро масштабировать инфраструктуру. Кроме того, фактическое отсутствие привязки контейнеров к хостинговой платформе даёт огромную гибкость при выборе или смене провайдера — вы можете запускать их без принципиальных отличий в конечном результате на личном компьютере, bare metal серверах и в облачных сервисах.
Чем контейнеры отличаются от виртуальных машин
Наиболее частым вопросом при выборе среды запуска приложения является вопрос о различии между контейнерами и виртуальными машинами — двумя самыми популярными опциями на текущий момент. Между ними есть принципиальная разница. Контейнер, в сущности, является ограниченным внутри ОС пространством, использующим для доступа к аппаратным ресурсам ядро host-системы. ВМ представляет собой машину целиком со всеми необходимыми для её работы устройствами. Из этого образуются отличия, имеющие практическое значение:
- Контейнеры требуют значительно меньше ресурсов для своей работы, что положительно сказывается на производительности и бюджете.
- Контейнеры можно запускать только в той же операционной системе, что стоит на host-системе — то есть запустить Windows-контейнер на host-системе с Linux не получится (на персональных устройствах это ограничение обходится с помощью технологии виртуализации). Однако это не относится к разным дистрибутивам одной и той же ОС, например Ubuntu и Alpine Linux.
- Контейнеры предоставляют меньшую степень изоляции, поскольку используют ядро host-системы, что потенциально создаёт бóльшие риски в эксплуатации при небрежном отношении к безопасности.
Основные принципы контейнеризации приложений
Для эффективной работы приложения в контейнерах недостаточно просто создать образ контейнера и запустить его. Нужно позаботиться о том, чтобы архитектура приложения и контейнера соответствовала базовым принципам контейнеризации, которые хорошо изложила компания RedHat.
1 контейнер — 1 сервис
Контейнер должен выполнять только одну функцию — не следует помещать в него все сущности, от которых зависит приложение. Следование этому принципу позволяет добиться большей переиспользуемости образов и, что самое главное, позволяет более тонко масштабировать приложение — узким местом вашего сервиса может оказаться только какая-то часть используемого стэка технологий, и разведение всех его частей по разным контейнерам позволит точечно увеличивать производительность вашего сервиса.
Неизменность образа
Все изменения внутри контейнера должны вноситься на стадии сборки образа — соблюдение этого принципа страхует вас от утраты данных при уничтожении контейнера. Неизменность контейнера также даёт возможность выполнять параллельные задачи в CI/CD системах — например, можно одновременно запустить разного рода тестирования, ускоряя тем самым процесс разработки продукта.
Утилизируемость контейнеров
Этот принцип являет собой яркий пример современной концепции «Обращайся с инфраструктурой как со скотом, не как с питомцами». Это значит, что любой контейнер может быть в любой момент уничтожен и заменён на другой без остановки обслуживания. Конфигурация контейнера в виде его образа сущностно отделена от непосредственно выполняющего работу экземпляра контейнера, что позволяет «пускать под нож» экземпляры, когда потребуется — при сбое проверки состояния контейнера, масштабировании на понижение и т. д. Соответствие этому принципу означает, что выход контейнеров из строя не должен быть новостью для вашего приложения: ротация контейнеров должна стать одним из требований к разработке.
Отчётность
Контейнер должен иметь точки проверки состояния его готовности (readiness probe) и жизнеспособности (liveness probe), предоставлять логи для отслеживания состояния запущенного в нём приложения.
Управляемость
Приложение в контейнере должно иметь возможность взаимодействовать с контролирующим его процессом — например для корректного завершения своей работы по команде извне. Это позволит аккуратно закрывать транзакции, препятствуя потере пользовательских данных в результате остановки или уничтожения контейнера.
Самодостаточность
Образ с приложением должен обладать всеми необходимыми зависимостями для работы — библиотеками, конфигами и прочим. Сервисы же к этим зависимостям не относятся, иначе это противоречило бы принципу «1 контейнер — 1 сервис». Связность контейнеров, зависящих друг от друга, можно определить с помощью инструментов оркестрирования, о чём будет рассказано ниже.
Лимитирование ресурсов
К лучшим практикам эксплуатации контейнеров относится настройка ресурсных лимитов (CPU и RAM): следование этой практике позволяет сохранять внимательное отношение к экономии ресурсов и вовремя реагировать на их избыточное потребление.
Docker
Когда мы говорим о контейнерах в современных IT-системах, прежде всего мы подразумеваем Docker — open-source-технологию, благодаря своей популярности ставшую в IT синонимом слова «контейнер».
Основные сущности Docker
Dockerfile
Текстовый файл, используемый для создания образа контейнера. Содержит в себе ссылку на базовый образ, служащий отправной точкой при формировании нового образа и набор инструкций для сборки, таких как установка зависимостей, компиляция приложения и копирование конфигов. Также он содержит точку входа в контейнер — команду, выполняемую при его запуске.
Image
Готовая файловая система, сформированная по инструкциям из Dockerfile и служащая прообразом для запускаемых контейнеров.
Instance
Запущенный экземпляр образа, минимальная единица деплоя в Docker.
Volume
Подключаемая к контейнерам файловая система, не являющаяся их неотъемлемой частью и существующая независимо от образа. С помощью объектов Volume решается проблема сохранности данных, записанных в процессе работы контейнеров в локальной файловой системе после их уничтожения.
Registry
Репозиторий, используемый для хранения Docker-образов. Registry может быть как публичным, так и приватным, защищённым механизмом аутентификации.
Процесс разработки в среде Docker
Типичный процесс разработки в среде Docker выглядит следующим образом: разработчики устанавливают на свои машины Docker, загружают собранный заранее образ с установленной средой сборки и выполнения приложения, а затем запускают контейнер командой, которая также пробросит в него директорию с исходниками. Для установки Docker не требуется особого железа — он может быть установлен на вполне заурядной машине. Однако у него имеются ограничения по версиям ОС — Windows 7 64bit или выше для ПК с поддержкой Hyper-V, macOS Sierra 10.12 для устройств от Apple и версией ядра 3.10 для систем с Linux. Контейнеры Docker являются родной технологией для ОС Linux и запускаются на других с помощью виртуальных машин под её управлением.
Инструкции по установке Docker на разных платформах: Windows, Mac, Linux Ubuntu.
Чтобы запустить ваш первый контейнер на Docker, после его установки введите в командной строке docker run hello-world — эта команда загрузит образ hello-world с Docker hub’а (публично доступный Docker registry), создаст контейнер, используя этот образ, и выдаст приветственную фразу:
Hello from Docker!
This message shows that your installation appears to be working correctly.
.
Оркестрирование контейнеров: Kubernetes
Оркестрирование — это в высокой степени автоматизированный процесс управления связанными сущностями, такими как группы виртуальных машин или контейнеров.
Kubernetes (также встречается в виде акронима K8s) — это совокупность сервисов, реализующих контейнерный кластер и его оркестрирование. Kubernetes не заменяет Docker — он серьёзно расширяет его возможности, упрощая управление развертыванием, сетевой маршрутизацией, расходом ресурсов, балансировкой нагрузки и отказоустойчивостью запускаемых приложений.
NetApp Kubernetes Service позволит создать cloud-agnostic кластер с уже реализованными механизмами деплоя и управления жизненным циклом приложения, автоматическим масштабированием инфраструктуры, интеграцией с сервисами хранилищ данных и многим другим, снизив затраты на конфигурацию и поддержку сложной инфраструктуры.
Основные сущности, которыми оперирует Kubernetes
Node (master и slave)
Узлы, из которых состоит кластер Kubernetes. Master-нода осуществляет контроль над кластером через планировщик и менеджер контроллеров, обеспечивает интерфейс взаимодействия с пользователями посредством API-сервера и содержит хранилище etcd, где находится конфигурация кластера, статусы его объектов и метаданные. Slave-нода предназначена исключительно для запуска контейнеров, для этого на ней установлены два сервиса Kubernetes — сетевой маршрутизатор и агент планировщика.
Namespace
Структурный объект, позволяющий разграничивать ресурсы кластера между пользователями и командами.
Минимальная единица развёртывания в Kubernetes, группа из одного или более контейнеров, собранных для совместного деплоя на ноде. Группировать контейнеры разного вида в Pod имеет смысл, когда они зависят друг от друга и потому должны быть запущены на одной ноде, чтобы сократить время отклика при их взаимодействии. Пример — контейнеры с веб-приложением и кэширующим его сервисом.
ReplicaSet
Объект, описывающий и контролирующий соответствие запущенного на кластере количества реплик Pod’ов. Установка количества реплик больше одной требуется для повышения отказоустойчивости и масштабирования приложения. Общепринято создавать ReplicaSet с помощью Deployment.
Deployment
Объект, декларативно описывающий Pod’ы, количество реплик и стратегию их замены при обновлении параметров.
StatefulSet
Действует по тому же принципу, что и ReplicaSet, однако дополнительно позволяет описывать и сохранять при перезапуске уникальный сетевой адрес Pod’ов или их дисковое хранилище.
DaemonSet
Объект, обеспечивающий контроль за тем, что на каждой ноде (или нескольких выбранных) будет запущено по экземпляру указанного Pod’а.
Job и CronJob
Объекты, запускающие соответственно однократно и регулярно по расписанию указанный Pod и отслеживающие результат завершения его работы.
Label и Selector
Метки, позволяющие маркировать ресурсы и тем самым упрощать групповые манипуляции, связанные с ними.
Service
Инструмент для публикации приложения в качестве сетевого сервиса, в том числе реализующий балансировку нагрузки между Pod’ами приложения.
Если сравнить объекты Docker и Kubernetes, то станет понятна разница в задачах, решаемых этими инструментами: можно сказать, что Docker управляет контейнерами, в то время как Kubernetes управляет самим Docker.
Управление конфигурацией (Configuration/Complexity Management)
Развитие IT-систем ведёт ко всё большему их усложнению, и это порождает проблемы управления — даже на небольшом количестве серверов или контейнеров ручное управление приложением превращает практически любое изменение конфигурации в трудовой подвиг, а на десятках или сотнях делает его абсолютно невозможным. К счастью, новые проблемы ведут и к новым решениям — в этом разделе мы расскажем о некоторых инструментах управления конфигурацией (configuration management) или, как ещё принято говорить, управления сложностью (complexity management). Они используются для установки, управления и обновления приложений Kubernetes: например, с их помощью можно описать приложение, состоящее из фронтенда, бэкенда и всех необходимых для их работы сервисов, таких как объекты Kubernetes, контейнеры с веб-серверами, базами данных, серверами очередей и т. д.
Перечисленные в этом разделе инструменты обладают обширными сообществами и множеством готовых пользовательских конфигураций, что поможет сэкономить массу времени при начальной настройке сервисов, переиспользуя и адаптируя уже написанный другими пользователями код.
Kustomize
Благодаря популярности у пользователей и своей простоте, начиная с версии Kubernetes 1.14, Kustomize является встроенным инструментом управления конфигурацией. Для описания приложений использует чистый язык разметки YAML без возможности шаблонизации и использования параметров, что является одновременно его сильной и слабой сторонами, упрощая процесс настройки и вместе с тем сильно его ограничивая.
Ansible
Управляйте хранилищами данных в автоматизированном режиме с помощью модулей интеграции Ansible NetApp.
Jsonnet
Также как и Ansible, Jsonnet не является чем-то специфичным для Kubernetes, однако многие знакомы с ним именно благодаря K8s. Jsonnet описывает объекты с помощью расширенного JSON, включающего комментарии, текстовые блоки, параметры, переменные, условные включения и функции. Очень мощный и гибкий инструмент.
Пакетный менеджер приложений Kubernetes. Этот инструмент описывает приложения в виде декларативных диаграмм (charts), создающихся с помощью языка разметки YAML и шаблонов Golang. Helm обладает широкой базой готовых диаграмм, даёт возможность версионировать конфигурации и переключаться между версиями релизов, т. е. откатывать конфигурацию. Из всех приведенных здесь инструментов является наиболее функциональным в отношении управления приложениями Kubernetes и одновременно обладает наиболее сложным способом описания конфигурации из-за шаблонов Golang, весьма требователен к пользовательским навыкам.
Платформы для хостинга контейнеров
Одним из основных моментов при выводе в продакшен контейнерного приложения является выбор того, где же в конечном счёте это приложение будет запущено. В этой части статьи мы постараемся помочь вам, описав два основных на сегодняшний день варианта, их сильные и слабые стороны.
Своё железо (bare metal)
Этот подход можно назвать консервативным — выбрав его, вы покупаете или арендуете серверы, нанимаете специалистов и строите свой собственный кластер. Очевидные его плюсы — тонкая настройка, возможность полного контроля над инфраструктурой и, как следствие, превосходящая производительность с более высокой отдачей от бюджета в долгой перспективе. Из минусов можно отметить большие финансовые (в том числе капитальные) и временные затраты на конфигурацию и поддержку, а также зависимость от специалистов по её поддержке. Как правило, этот вариант выбирают компании, имеющие выверенные долгосрочные планы, уже обеспеченные солидным бюджетом.
Облачные решения (SaaS)
Крупные облачные провайдеры предоставляют своим клиентам сервисы для запуска контейнеров — готовые среды, обёрнутые удобным интерфейсом управления. Построенные по такому принципу решения называются Software as a Service (SaaS). Они не требуют никаких капитальных затрат, поскольку вся инфраструктура арендуется, а конфигурация создаётся в минимальном объёме, относящемуся исключительно к запускаемому приложению. Сами же кластеры настраиваются провайдером по указанному пользователем небольшому объему параметров. SaaS-решения — оптимальный вариант для стартап-компаний, небольших и развивающихся проектов. Самым большим минусом этого решения можно назвать повышенную в сравнении с bare metal стоимость при условии многолетнего использования.
Тремя наиболее популярными облачными платформами на сегодня являются Amazon Web Services, Microsoft Azure и Google Cloud. Все три провайдера имеют доступный в виде сервиса Kubernetes, и все три из них предлагают пробный период пользования сервисами, выдавая депозит на сумму 200–300 $. Чтобы оценить удобство и качество облачных решений и сравнить друг с другом их поставщиков, вам даже не придется тратить свои деньги.
Хотите обеспечить максимальную сохранность данных, создаваемых и используемых в контейнерах? NetApp Trident позволит легко интегрировать в вашу контейнерную инфраструктуру надежное и функциональное хранилище данных enterprise-уровня.
Заключение
Ещё недавно не смолкали дебаты на тему оправданности использования контейнеров в продакшене, то и дело были слышны обвинения в их ненадежности. Однако время не стоит на месте, индустрия оценила их перспективность, сделала свой выбор, и инвестиции в контейнерные решения потекли широкой рекой, с каждым днем делая решения на их базе всё удобнее и привлекательнее. На сегодняшний день примитивную настройку кластера и деплой приложений в него можно выполнить используя один лишь веб-интерфейс, предварительно прочитав несколько страниц документации — настолько это стало просто. А их низкая по сравнению с виртуальными машинами стоимость откусывает у ВМ всё большую долю рынка, забирая то, что не требует для своей работы специфики устройства ВМ. Безусловно, контейнеры зарекомендовали себя как жизне- и конкурентоспособное решение, сокращающее время вывода продукта на рынок, стоимость его разработки и эксплуатации.
Читайте также: