У Flux долго был слепое пятно ровно там, где ArgoCD-сетапы с Argo Rollouts выглядели сильнее: проверка готовности после выката умела смотреть только на «известные» типы ресурсов. Кастомный CRD со своей логикой готовности Flux фактически не видел. В 2.8 это закрыли — healthCheckExprs позволяет описать health check CEL-выражением поверх произвольного status. Разбираемся, что это меняет на практике и где заканчивается его юрисдикция.
Содержание
Открыть содержание
Как Flux проверял готовность раньше
kustomize-controller и helm-controller всегда умели ждать не просто «манифест применён», а «ресурс действительно готов» — это встроено в kstatus, библиотеку, которая знает стандартные конвенции Kubernetes: у Deployment готовность — это status.availableReplicas == spec.replicas, у Job — status.succeeded > 0, у объектов с status.conditions — условие Ready: True.
Проблема в том, что это работает только для типов, которые kstatus знает или которые следуют общей конвенции conditions. Как только в кластере появляется CRD со своей, отличной от стандартной, схемой status — например, оператор базы данных, у которого готовность выражается полем status.phase: Running или парой status.readyReplicas / status.lastJobStatus — Flux видит только то, что ресурс применился, и не может сказать, готов ли он содержательно. Реконсиляция считается успешной раньше, чем сервис реально способен принимать трафик.
На практике это било по надёжности выкатки. Если dependsOn следующей Kustomization ссылался на такой CRD, зависимость считалась выполненной сразу после apply — Flux не мог отличить «оператор ещё разворачивает реплики» от «оператор готов принимать нагрузку». Следующий шаг цепочки стартовал раньше времени, и первые ошибки видели уже пользователи, а не reconcile-цикл. Раньше единственным обходным путём было писать собственный оператор-обёртку, который транслирует кастомный статус в стандартные conditions — лишний компонент ради того, что, по сути, однострочная проверка.
Что добавляет healthCheckExprs
healthCheckExprs — это список правил в спеке Kustomization или HelmRelease, каждое из которых привязано к apiVersion/kind и описывает три состояния через CEL-выражения над объектом status этого ресурса:
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps-db
namespace: flux-system
spec:
interval: 10m
path: ./apps/db
sourceRef:
kind: GitRepository
name: infra
healthCheckExprs:
- apiVersion: apps.acme.io/v1
kind: AcmeOperator
current: "status.readyReplicas >= 2 && status.lastJobStatus == 'Succeeded'"
inProgress: "status.lastJobStatus == 'Running'"
failed: "status.lastJobStatus == 'Failed'"
current — ресурс готов, реконсиляция может считаться успешной. inProgress — ещё выкатывается, ждём следующего опроса. failed — явная ошибка, Flux сразу помечает Kustomization как нездоровую, не дожидаясь таймаута. CEL-контекст — это объект самого ресурса (status, при необходимости metadata), так что выражение может учитывать что угодно из его status, а не только «есть ли conditions».
Ключевая практическая разница с обычным kstatus: до healthCheckExprs health check был бинарным по факту наличия стандартных полей — либо ресурс попадает под конвенцию, либо Flux его не проверяет вообще. Теперь можно закодировать произвольную бизнес-логику готовности: «минимум 2 реплики доступны и последний миграционный Job завершился успешно» — то есть AND двух независимых сигналов, которых по отдельности kstatus никогда не сопоставил бы.
CEL-выражение видит весь объект целиком, а не только status, так что при необходимости можно опираться и на metadata (например, на аннотацию с версией) — но в подавляющем большинстве случаев хватает полей status, которые уже пишет ваш оператор. Правило одно: если поле есть в выводе kubectl get <crd> -o yaml, оно доступно и в CEL-выражении под тем же именем.
Как это меняет откат и порядок выката
Здесь начинается настоящий эффект. Kustomization, у которой в dependsOn стоит ссылка на другую, не начнёт применяться, пока зависимость не станет Ready. Раньше «Ready» для CRD без стандартного статуса означало «применилось» — то есть dependsOn фактически ничего не гарантировал за пределами известных типов. С healthCheckExprs Ready начинает означать именно то, что вы описали в current: следующая Kustomization в цепочке не тронется, пока CEL-проверка не пройдёт.
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps-api
spec:
dependsOn:
- name: apps-db # ждёт именно current из healthCheckExprs apps-db,
# ... # а не просто факта применения манифеста
Если current не выполняется в течение timeout, Kustomization помечается Ready: False, и всё, что от неё зависит через dependsOn, остаётся заблокированным — это тот же принцип «не катить дальше по цепочке, пока предыдущий шаг не подтверждён», который в ArgoCD с Argo Rollouts делает AnalysisTemplate: следующий шаг rollout не наступает, пока метрики анализа не сойдутся. Разница — в том, что именно проверяется и на каком уровне.
Здесь заканчивается его юрисдикция: сравнение с Flagger
Важно не спутать healthCheckExprs с progressive delivery. Это go/no-go проверка одного ресурса на одном шаге реконсиляции: либо current, либо нет. Она не умеет постепенно сдвигать трафик, сравнивать метрики канареечной и стабильной версий или откатываться по SLO — за это в экосистеме Flux по-прежнему отвечает Flagger, который управляет отдельным циклом: 10% → 50% → 100% трафика с проверкой Prometheus-метрик на каждом шаге, и автоматическим роллбэком, если канарейка не проходит.
healthCheckExprs (Flux) | Flagger canary | Argo Rollouts AnalysisTemplate | |
|---|---|---|---|
| Что проверяет | произвольное поле status через CEL | метрики (latency, error rate) во времени | метрики / произвольные проверки во времени |
| Гранулярность | один ресурс, один снимок | постепенный трафик-шифт | постепенный трафик-шифт |
| Результат | Ready: True/False | promote / rollback канарейки | promote / abort rollout |
| Блокирует что | следующую Kustomization через dependsOn | продвижение трафика на новую версию | продвижение rollout-степов |
| Заменяет ли canary | нет | да, это и есть canary | да, это и есть canary |
Иначе говоря: healthCheckExprs закрывает разрыв в gating — Flux наконец умеет содержательно ждать нестандартные CRD, — но progressive delivery как отдельный процесс с постепенным трафиком и авто-роллбэком по метрикам остаётся задачей Flagger, а не Kustomization. Более того, эти два механизма не конкурируют, а стоят на разных уровнях: healthCheckExprs может быть предусловием для того, чтобы Flagger вообще начал раскатывать канарейку — сначала Flux убеждается, что база и миграции готовы, и только потом Flagger начинает сдвигать трафик на новую версию API поверх неё.
Что нужно, чтобы включить CEL health check
Нужен Flux 2.8+ в кластере (flux check покажет версию контроллеров) и CRD, чей status вы понимаете — посмотрите kubectl get <crd> <name> -o yaml и найдите поля, которые реально отражают готовность.
Возьмём HelmRelease из уже описанного в блоге one-cluster GitOps-сетапа и добавим ему CEL-проверку статуса кастомного оператора:
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: acme-operator
namespace: flux-system
spec:
interval: 10m
chart:
spec:
chart: acme-operator
sourceRef:
kind: HelmRepository
name: acme-charts
healthCheckExprs:
- apiVersion: apps.acme.io/v1
kind: AcmeOperator
current: "status.readyReplicas >= 2 && status.lastJobStatus == 'Succeeded'"
failed: "status.lastJobStatus == 'Failed'"
Миграция существующей Kustomization на CEL
Если у вас уже есть рабочая Kustomization без healthCheckExprs, добавление — не breaking change: пока секции нет, Flux продолжает использовать стандартный kstatus, поведение не меняется. Миграция — это:
- Найти CRD, у которых готовность сейчас не проверяется по существу (Flux видит только apply) — обычно это операторские ресурсы с нестандартным
status. - Написать
current/failed/inProgressпо реальным полямstatusэтого CRD и проверить CEL-выражение локально, до коммита — опечатка в имени поля не даст ошибки применения, просто health check никогда не станетTrue. - Добавить
healthCheckExprsв спеку и закоммитить — при следующем reconcile Flux начнёт оценивать состояние по новому правилу. - Если на CRD завязан
dependsOnот другихKustomization, проверить, что цепочка по-прежнему проходитcurrentв разумное время, а не упирается вtimeout.
Как проверить, что работает
flux get kustomizations apps-db
# NAME READY MESSAGE
# apps-db True Health check passed
kubectl get kustomization apps-db -n flux-system -o jsonpath='{.status.conditions}'
Если READY=False и в MESSAGE — Health check failed, значит CEL-выражение из failed сработало или current не выполнилось за timeout. Полезно временно сломать условие руками (например, убрать readyReplicas) и убедиться, что Kustomization действительно уходит в Ready: False, а зависимые от неё ресурсы не применяются — это и есть проверка, что gate реально работает, а не просто существует в yaml.
Итог
healthCheckExprs устраняет конкретный и давний пробел Flux: теперь готовность кастомного CRD можно описать произвольным CEL-выражением по его собственному status, а не ждать, пока он впишется в конвенцию kstatus. Это делает dependsOn-цепочки надёжными для нестандартных ресурсов и даёт паритет с ArgoCD в вопросе «умного» gating. Но это по-прежнему проверка одного снимка состояния, а не progressive delivery — постепенный трафик-шифт с метриками и авто-роллбэком остаётся за Flagger, и если вам нужен canary, а не просто «применилось и заработало», CEL health check не заменяет отдельный canary-контроллер, а хорошо с ним сочетается: gate решает, можно ли вообще двигаться дальше, а Flagger — как двигаться безопасно.