В моём DocOps-проекте используются два репозитория. В первом хранятся статьи, шаблоны сайта, валидаторы и пайплайн сборки. Во втором — манифесты Kubernetes и версия образа контейнера, которая должна работать в кластере.
Автор меняет Markdown только в репозитории документации. После ревью изменение мержится в основную ветку, а дальнейшей публикацией управляет автоматизация: пайплайн проверяет сайт, выпускает новую версию, публикует образ и обновляет GitOps-репозиторий.
Flux следит только за GitOps-репозиторием. Репозиторий со статьями он не читает и напрямую не знает, что в нём произошёл мерж.
Полная цепочка выглядит так:
Merge Request со статьёй
→ мерж в репозиторий документации
→ проверки и сборка
→ публикация образа контейнера
→ автоматический коммит в GitOps-репозиторий
→ согласование состояния Flux
→ развёртывание новой версии в Kubernetes
→ обновлённый сайт
На этой схеме видно, что CI и Flux не дублируют друг друга. Каждый компонент получает результат предыдущего этапа и отвечает за свою часть доставки.
Два репозитория хранят разное состояние
Репозиторий документации отвечает на вопрос: что мы собираем? В нём находятся Markdown-файлы, шаблоны Jekyll, валидаторы, конфигурация сборки и правила выпуска версии.
GitOps-репозиторий отвечает на другой вопрос: что должно работать в среде? В нём находятся ресурсы Kubernetes, конфигурация среды и версия образа, которую должен использовать Deployment.
Это не два конкурирующих источника истины. Содержимое сайта хранится в одном репозитории, а желаемое состояние среды — в другом.
Такое разделение не смешивает историю статей с историей развёртываний. По репозиторию документации можно понять, как менялся сайт, а по GitOps-репозиторию — какие версии выбирались для Kubernetes.
Мерж только запускает публикацию
Под мержем здесь имеется в виду мерж статьи в основную ветку репозитория документации. Второй пользовательский Merge Request при публикации не создаётся: GitOps-репозиторий позже обновляет сам пайплайн.
После мержа статья ещё не доступна пользователю. Сначала пайплайн должен повторно проверить исходники, собрать статический сайт, определить версию и опубликовать образ контейнера.
Затем эту версию нужно выбрать для среды, записать в GitOps-репозиторий и применить в Kubernetes. Мерж запускает эту цепочку, но не подтверждает её завершение.
CI готовит артефакт к развёртыванию
Пайплайн Merge Request проверяет Markdown, метаданные статей, сообщения коммитов, внутренние валидаторы и сборку Jekyll. После мержа те же проверки повторяются для основной ветки.
Затем автоматизация выпуска определяет новую версию. Сайт собирается и упаковывается в образ контейнера:
registry.example.org/docs:v1.2.3
Образ отправляется в реестр образов. На этом CI выполняет свою основную задачу: проверяет изменение и подготавливает артефакт к развёртыванию.
Состояние среды при этом ещё не изменилось. Kubernetes продолжает использовать версию, записанную в GitOps-репозитории.
Именно поэтому публикация образа сама по себе ещё не является развёртыванием.
CD начинается с выбора версии для среды
После публикации образа пайплайн репозитория документации клонирует GitOps-репозиторий и меняет версию в конфигурации Kustomize:
images:
- name: docs-image
newName: registry.example.org/docs
newTag: v1.2.3
Затем пайплайн автоматически создаёт коммит в основной ветке GitOps-репозитория. Пользователь на этом этапе ничего не мержит вручную.
Новая версия ещё не работает в Kubernetes, но решение о развёртывании уже принято. В Git записано, какой артефакт должен использоваться в конкретной среде.
Именно здесь начинается CD. До изменения GitOps-репозитория система создаёт артефакт, после изменения — доставляет выбранный артефакт в среду.
Flux применяет записанное решение
Flux отслеживает основную ветку GitOps-репозитория. Когда там появляется новый коммит, он получает изменённое желаемое состояние, строит итоговые манифесты Kubernetes и применяет их в кластере.
Изменение версии в поле image обновляет шаблон пода в ресурсе Deployment. Kubernetes создаёт новую ReplicaSet и запускает поды с новой версией сайта.
Flux не проверяет Markdown, не запускает Jekyll, не определяет номер версии и не собирает образ контейнера. Он также не выбирает образ в реестре: это решение уже принял пайплайн и записал в Git.
Поэтому Flux начинает работу не с исходников и не с результата сборки. Он получает готовое описание того, что должно работать в Kubernetes.
Flux поддерживает состояние после развёртывания
Работа Flux не заканчивается после первого применения новой версии. Он продолжает сравнивать фактическое состояние Kubernetes с состоянием, описанным в Git.
Если управляемый ресурс изменили вручную, следующий цикл согласования возвращает его к желаемой конфигурации. Если ресурс удалили из GitOps-репозитория, Flux может удалить его из кластера.
При временной остановке Flux уже запущенный сайт продолжит обслуживать запросы. Однако новые версии не будут применяться, а ручные расхождения не будут исправляться.
Flux не находится на пути пользовательского запроса. Он отвечает за доставку и поддержание состояния среды.
Flux — часть CD, но не весь процесс
В моём проекте ответственность распределена так:
CI:
исходники → проверенный образ контейнера
Выбор версии:
образ контейнера → изменение GitOps-репозитория
Flux:
GitOps-репозиторий → состояние Kubernetes
Kubernetes:
новый шаблон пода → работающая версия сайта
CD начинается раньше, чем Flux получает изменение. Сначала пайплайн выбирает версию для среды и фиксирует это решение в Git. Затем Flux применяет его и продолжает поддерживать соответствие кластера GitOps-репозиторию.
Поэтому фраза «Flux развёртывает документацию» упрощает процесс. Flux выполняет важную часть развёртывания, но не собирает продукт и не принимает решение о том, какую версию нужно запустить.
Зелёный пайплайн не означает доступный сайт
В текущей реализации задание развёртывания завершается после отправки коммита в GitOps-репозиторий. Оно подтверждает, что новое желаемое состояние записано в Git, но не ждёт, пока Flux получит коммит, успешно применит конфигурацию и Kubernetes завершит обновление Deployment.
Пайплайн также не проверяет, что сайт начал возвращать успешные HTTP-ответы. Поэтому его зелёный статус подтверждает передачу ответственности следующей системе, а не полную доступность новой версии пользователю.
Есть и другое ограничение. Коммит в GitOps-репозитории одновременно запускает его CI и становится доступен Flux, но Flux не ждёт результатов проверок. Значит, CI GitOps-репозитория в такой схеме не блокирует развёртывание некорректной конфигурации.
Если неуспешная проверка должна останавливать применение изменений, процесс нужно строить иначе. Но эта проблема не меняет основную границу ответственности между CI и Flux.
Границы помогают искать причину
Без понимания всей цепочки почти любой сбой можно назвать проблемой CI или Flux. На практике он может возникнуть при проверке Markdown, сборке Jekyll, выпуске версии, публикации образа, обновлении GitOps-репозитория, согласовании состояния или обновлении Deployment.
У каждого этапа свои состояния, логи и способы проверки. Например, Flux не сможет применить тег версии, который пайплайн не записал в GitOps-репозиторий. А успешный коммит в GitOps-репозитории не поможет, если Kubernetes не может скачать образ.
Понимание границ показывает, какой компонент уже выполнил свою работу и кому передал ответственность дальше.
Вывод
В DocOps-проекте публикация начинается с мержа статьи в репозиторий документации. CI проверяет изменение, собирает сайт и публикует образ контейнера.
CD начинается, когда пайплайн выбирает этот образ для среды и записывает новую версию в отдельный GitOps-репозиторий. Flux получает записанное состояние, применяет его в Kubernetes и исправляет последующие расхождения.
Мерж, зелёный пайплайн и успешный коммит в GitOps-репозитории подтверждают разные этапы процесса. Ни один из них сам по себе не означает, что новая версия уже доступна пользователю.
Публикация документации — это не одно задание после мержа, а последовательная передача ответственности между двумя репозиториями, пайплайном, Flux и Kubernetes.
