Книга: Паттерны Kubernetes: Шаблоны разработки собственных облачных приложений
Назад: Часть III. Структурные паттерны
Дальше: Глава 15. Паттерн Sidecar

Глава 14. Init-контейнеры

Паттерн Init Containers (Init-контейнеры) позволяет разделить задачи и организовать жизненный цикл для задач, связанных с инициализацией, отдельный от основных контейнеров приложения. В этой главе мы подробно рассмотрим эту основополагающую идею Kubernetes, которая используется во многих других паттернах, когда требуется определить отдельную логику инициализации.

Задача

Задача инициализации широко распространена во многих языках программирования. В некоторых случаях она является частью языка, а в некоторых для обозначения конструкций инициализации используются соглашения об именах и паттерны. Например, в Java, чтобы создать экземпляр объекта, который требует дополнительной настройки, используется конструктор (или, в некоторых необычных ситуациях, статические блоки). Конструкторы гарантированно запускаются средой выполнения на этапе создания экземпляра объекта и только один раз (это только пример; мы не будем вдаваться в подробности, характерные для разных языков и ситуаций). Кроме того, конструктор можно использовать для проверки предварительных условий, например наличия обязательных параметров. Также конструкторы могут использоваться для инициализации полей экземпляра значениями входных аргументов или значениями по умолчанию.

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

Решение

Паттерн Init Containers (Init-контейнеры) в Kubernetes является частью определения пода и делит все контейнеры в поде на две группы: собственно init-контейнеры и контейнеры приложения. Все init-контейнеры выполняются последовательно, друг за другом, и все они должны завершиться с признаком успеха, прежде чем Kubernetes приступит к запуску контейнеров приложения. В этом смысле init-контейнеры похожи на конструкторы классов в Java, которые помогают инициализировать объекты. Контейнеры приложения, напротив, запускаются параллельно, в произвольном порядке. Описанный процесс показан на рис. 14.1.

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

586486.png 

Рис. 14.1. Init-контейнеры и контейнеры приложения в поде

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

Init-контейнеры также влияют на результаты расчетов требований к ресурсам для пода при планировании, автоматическом масштабировании и управлении квотами. Учитывая, что контейнеры в поде выполняются по порядку (сначала последовательно запускаются init-контейнеры, а затем параллельно запускаются все контейнеры приложения), в качестве действующих значений для запросов и лимитов выбираются наибольшие из следующих двух групп:

• наибольшее значение запроса/лимита для init-контейнера;

• сумма всех значений запроса/лимита для контейнера приложения.

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

Кроме того, init-контейнеры позволяют разделить задачи и сделать контейнеры более специализированными. Прикладной программист может создавать контейнеры, сосредоточенные только на логике приложений. Инженер по развертыванию может создавать init-контейнеры, сосредоточенные только на задачах настройки и инициализации. Мы продемонстрируем это в листинге 14.1, где определяется один контейнер приложения на основе HTTP-сервера, обслуживающий файлы.

Контейнер реализует универсальную функцию обслуживания HTTP-клиентов и не делает никаких предположений о том, откуда берутся файлы для разных клиентов. Init-контейнер в том же поде реализует клиента Git с единственной целью клонирования репозитория. Поскольку оба контейнера являются частью одного пода, они имеют доступ к одному и тому же тому для хранения общих данных. Мы используем тот же механизм для передачи клонированных файлов из init-контейнера в контейнер приложения.

Листинг 14.1 содержит определение init-контейнера, который копирует данные в пустой том.

Листинг 14.1. Init-контейнер

apiVersion: v1

kind: Pod

metadata:

  name: www

  labels:

    app: www

spec:

  initContainers:

  - name: download

    image: axeclbr/git

    command:                              

    - git

    - clone

    - https://github.com/mdn/beginner-html-site-scripted

    - /var/lib/data

    volumeMounts:                         

    - mountPath: /var/lib/data

      name: source

  containers:

  - name: run

    image: docker.io/centos/httpd

    ports:

    - containerPort: 80

    volumeMounts:                         

    - mountPath: /var/www/html

      name: source

  volumes:                                

  - emptyDir: {}

    name: source

Клонирование внешнего репозитория Git в смонтированный каталог.

Общий том, используемый обоими контейнерами.

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

Того же эффекта можно добиться с помощью ConfigMap или Per­si­stentVolume, но здесь я хотел показать, как работают init-контейнеры. Этот пример иллюстрирует типичный подход к совместному использованию тома init-контейнером и основным контейнером приложения.

lemur.tif

Задержка запуска пода

Для отладки результатов init-контейнеров бывает полезно временно заменить запуск контейнера приложения фиктивной командой sleep, чтобы получить время на изучение ситуации. Этот прием может особенно пригодиться, если init-контейнер терпит неудачу и приложение не запускается из-за отсутствия должной конфигурации окружения. Следующая команда в определении пода дает вам час для отладки смонтированных томов после входа в под с помощью команды kubectl exec -it <под> sh:

command:

- /bin/sh

- "-c"

- "sleep 3600"

Другие приемы инициализации

Как вы уже видели, init-контейнер — это конструкция уровня пода, которая активируется после его запуска. В Kubernetes есть еще несколько методов, используемых для инициализации ресурсов, не связанных с init-контейнерами, и я перечислю их для полноты картины:

Контроллеры доступа

Это набор плагинов, которые перехватывают все запросы, поступающие в Kubernetes API Server перед сохранением объекта, и могут изменять или проверять его. Существует множество контроллеров, которые выполняют разные проверки, применяют ограничения и настраивают значения по умолчанию, но все они скомпилированы в двоичный файл kube-apiserver и настраиваются администратором кластера при запуске API Server. Система плагинов не отличается гибкостью, поэтому в Kubernetes были добавлены веб-обработчики доступа.

Веб-обработчики доступа

Эти компоненты являются внешними контроллерами доступа, которые выполняют обратные вызовы HTTP для любого соответствующего запроса. Существует два типа веб-обработчиков доступа: изменяющие (способные изменять ресурсы для применения пользовательских настроек) и проверяющие (способные отклонять ресурсы в соответствии с пользовательскими политиками доступа). Идея внешних контроллеров позволяет разрабатывать веб-обработчики доступа вне Kubernetes и настраивать их во время выполнения.

Инициализаторы

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

Предварительные настройки PodPreset

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

Существует много способов инициализации ресурсов в Kubernetes. Но они отличаются от веб-обработчиков доступа тем, что проверяют и изменяют ресурсы во время создания. Эти методы можно использовать, например, чтобы вставить init-контейнер в любой под, где такого контейнера еще нет. Паттерн Init Container (Init-контейнер), обсуждаемый в этой главе, напротив, активируется и выполняет свою работу во время запуска пода. Но самое существенное отличие — init-контейнеры предназначены для разработчиков, развертывающих свои приложения в Kubernetes, а описанные здесь методы помогают администраторам контролировать и управлять процессом инициализации контейнеров.

Похожий эффект можно получить при использовании паттерна Sidecar (Прицеп), описанного далее в главе 15, где контейнер с HTTP-сервером и контейнер с клиентом Git работают вместе, как контейнеры приложения. Но паттерн Sidecar (Прицеп) не позволяет узнать, какой контейнер запустится первым, и предназначен для случаев, когда контейнеры постоянно работают вместе (как в листинге 15.1, где контейнер синхронизации с репозиторием Git постоянно обновляет содержимое локальной папки). Паттерны Sidecar (Прицеп) и Init Container (Init-контейнер) также можно использовать в паре, если требуются гарантированная инициализация и постоянное обновление данных.

Пояснение

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

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

Дополнительная информация

• Пример init-контейнера (http://bit.ly/2TW7ckN).

• Init-контейнеры (http://bit.ly/2TR7OsD).

• Настройка инициализации пода (http://bit.ly/2TWMEbL).

• Паттерн Initializer (Инициализатор) в JavaScript (http://bit.ly/2TYF14G).

• Инициализация объектов в Swift (https://apple.co/2FdSLPN).

• Использование контроллеров доступа (http://bit.ly/2ztKrJM).

• Динамические контроллеры доступа (http://bit.ly/2DwR2Y3).

• Как работают инициализаторы в Kubernetes (http://bit.ly/2TeYz0k).

• Наборы предварительных настроек подов PodPreset (https://kubernetes.io/docs/concepts/workloads/pods/podpreset/).

• Внедрение информации в поды с помощью PodPreset (http://bit.ly/2Fh7QzV).

• Руководство по инициализаторам в Kubernetes (http://bit.ly/2FfEu4W).

Назад: Часть III. Структурные паттерны
Дальше: Глава 15. Паттерн Sidecar