Пайплайны и базы данных в Kubernetes: как сделать развёртывание умным
Если вам нужно запустить несколько batch-задач по цепочке или дождаться, пока внешняя система создаст нужный Secret, обычный YAML не поможет. Статья-перевод разбирает реальные сценарии и показывает, как описать их через код с конкретными примерами.
От переводчика: в статье разбирается, почему современные инструменты развёртывания (Helm, Kustomize, Argo CD) не справляются с оркестрацией сложных приложений, где важны порядок действий и взаимозависимости компонентов. В ней будут конкретные примеры проблем и способы их решения с полным рабочим кодом. Статья является переводом материала Дэвида Демаре-Мишо (David Desmarais-Michaud) «Kubernetes Orchestration is More Than a Bag of YAML».
Эту статью будет проще читать, если вы знакомы с основами Yoke и Air Traffic Controller.
Если вкратце, Yoke позволяет определять пакеты как распространяемые программы, скомпилированные в WASM, которые называются Flights («рейсы»). Air Traffic Controller («диспетчер воздушного движения») расширяет API Kubernetes через Airways («воздушные трассы») — специальный ресурс, который создаёт CRD и связывает его с конкретным Flight.
Этот материал как раз о том, как Air Traffic Controller открывает простор для мощной и гибкой оркестрации ресурсов.
За пределами плоского YAML: как выглядит реальная оркестрация в Kubernetes
Если вам доводилось управлять современными сложными приложениями в Kubernetes, то наверняка знакомо чувство тревоги, когда ждёшь, пока все ресурсы перейдут в healthy-статус. Например, база данных должна быть готова до запуска приложения, серия пакетных задач (batch jobs) должна выполниться в строгой последовательности, а сервис может зависеть от секрета, которым управляет совершенно другая система. Как описать эти взаимоотношения? Сегодня, по большому счёту, никак.
Годами Helm и Kustomize были основными инструментами для управления Kubernetes-приложениями. Но они работают как генераторы манифестов: вы описываете список YAML-файлов, отправляете их в API и надеетесь, что всё сработает. Для простых stateless-приложений это нормально, но когда нужны порядок выполнения, координация между компонентами или управление состоянием — такой подход не годится.
Вот тут и встаёт вопрос оркестрации: способности осмысленно управлять жизненным циклом компонентов приложения, а не просто создавать их, надеясь на удачу.
Пробел в оркестрации
Причина этого пробела кроется в истории. Инструменты вроде Helm и Kustomize — это, по сути, шаблонизаторы на стороне клиента. Они генерируют манифесты, и на этом их работа заканчивается. Даже серверные GitOps-решения вроде Argo CD или FluxCD придерживаются той же концепции, ведь они преимущественно используют те же инструменты для генерации манифестов, которые потом синхронизируют за вас.
Конечно, есть обходные пути. Вы наверняка сталкивались с хуками pre/post-install в Helm или волнами синхронизации (sync waves) в Argo CD. И хотя это полезные фичи, проблема решается лишь частично. Они срабатывают только при установке или обновлении, но не работают на протяжении всей жизни приложения.
Пара слов о YAML и итоговой согласованности
Мы осознаем, что предложение отойти от использования YAML воспринимается неоднозначно. Для многих в сообществе «голые» манифесты — единственный «правильный» способ взаимодействия с API Kubernetes. Такая привязанность привела к тому, что для решения сложных проблем с зависимостями все стали активно полагаться на один базовый паттерн — итоговую согласованность (eventual consistency).
Идея в том, чтобы выкатить всё разом и просто надеяться, что рано или поздно контроллеры всё приведут к желаемому состоянию, зависимости подтянутся и кластер сам… заведётся. Все мы видели это на практике: под уходит в CrashLoopBackOff и висит в нём, пока какая-нибудь другая система наконец не создаст нужный ему ConfigMap или Secret.
Сразу поясним: итоговая согласованность — это фундаментальная часть Kubernetes и очень мощная идея. Проблема в том, что мы слишком долго использовали её как единственный инструмент для управления комплексными воркфлоу. Нам приходилось на неё полагаться, потому что имеющиеся инструменты не давали иного выбора: что имеем, тем и пользуемся. Наши YAML-ориентированные утилиты могли описать лишь то, что мы хотим получить в итоге, но не то, как к этому прийти.
Дилемма оператора
Классический ответ на запрос о создании умных и гибких стратегий развёртывания всегда был один: «Пишите свой оператор».
И хотя создание оператора — мощный и правильный подход, это ещё и масштабная задача. Здесь и высокий порог входа, и постоянные расходы на разработку и поддержку, которые далеко не всегда оправданы. Неужели нет золотой середины?
Новая модель: логика приложения как код
Вот тут-то на сцену и выходят Yoke и Air Traffic Controller (ATC) с новым образом мышления. Вместо того чтобы генерировать статичный набор YAML-файлов, Yoke описывает пакеты в виде исполняемого кода.
Это позволяет сфокусироваться на логике оркестрации приложения, написав простую [WASM] программу, работающую по знакомой схеме:
- Прочитать входные параметры из кастомного ресурса.
- Принять решения, основываясь на текущем состоянии кластера.
- Обновить статус кастомного ресурса, указав прогресс или другую информацию.
- Создать те ресурсы, которые должны быть в кластере прямо сейчас.
И да, это очень похоже на классический цикл согласования (reconciliation loop) в контроллере Kubernetes — так и задумано.
ATC — это контроллер, чья единственная работа — приводить ресурсы в кластере к желаемому состоянию. WASM-модуль представляет собой ядро логики и выступает посредником для цикла согласования, управляя желаемым состоянием (ресурсами) пакетов и избавляя вас от необходимости создавать и поддерживать собственный оператор с нуля.
Как это работает на практике
По своей сути, «Flight» в Yoke — это просто программа (скомпилированная в WASM; аналог Helm-чартов), которая считывает кастомный ресурс (Custom Resource) из stdin и записывает желаемое состояние системы в stdout.
Но поскольку ATC перезапускает её в рамках цикла управления (control loop), она становится динамической и реагирующей. Код выполняется каждый раз, когда:
- ресурс, которым он управляет (например, Job или Deployment), обновляется или удаляется;
- внешний ресурс, который он отслеживает (например, Secret от другого инструмента), создаётся, обновляется или удаляется.
Так код реагирует на изменения в кластере в реальном времени.
Примеры
Пример 1: пайплайн для последовательного запуска задач
Предположим, вам нужно создать ресурс Pipeline для запуска трёх задач друг за другом: job-one, job-two, job-three. При этом каждая следующая задача должна стартовать только после успешного завершения предыдущей.
С Yoke эту логику можно описать прямо в коде.
Сначала определим Go-тип для кастомного ресурса Pipeline:
Затем добавим соответствующее определение Airway, которое указывает ATC, как им управлять:
Основная магия содержится в WASM-модуле. Рассмотрим её пошагово:
Эта простая Go-программа как раз и реализует логику оркестрации. Она создаёт задачу, проверяет её статус и движется дальше, только когда та завершится. А цикл управления от ATC берёт на себя всю рутину по «ожиданию» и «перезапускам».
Пример 2: координация с внешними ресурсами
Рассмотрим другой распространённый сценарий: приложению требуется база данных. Вы используете Crossplane для инициализации CloudSQLInstance, который в итоге создаёт Secret, содержащий данные для подключения. Deployment не запустится до тех пор, пока этот Secret не будет существовать.
Сегодня вы, скорее всего, просто выкатываете всё сразу и ждёте, когда появится секрет (под приложения находится в состоянии CrashLoopBackOff). Но можно поступить умнее.
Давайте опишем ресурс App, который будет управлять этим процессом:
Airway нужно будет настроить так, чтобы он имел доступ к кластеру, а если точнее — чтобы мог искать ресурсы типа Secret.
Логика в нашем WASM-модуле будет такой:
Эта логика описывает зависимость: создать базу данных, дождаться секрета и только после этого развернуть приложение. Больше никаких подов в состоянии CrashLoopBackOff, только чистая оркестрация с отслеживанием состояния!
Заключительные мысли
Применяя подход «логика приложения как код», Yoke предлагает компромисс между статическими YAML-шаблонами и полнофункциональными операторами. Он позволяет кодифицировать сложные, активно реагирующие стратегии развёртывания с отслеживанием состояния, используя привычные языки программирования и инструменты, делая настоящую оркестрацию доступной для каждого.