Перейти к содержимому
Hogin Hogin
Назад

Flux 2.8: health-check-и на CEL и что это меняет в проверке релизов

8 мин чтения

У 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, у Jobstatus.succeeded > 0, у объектов с status.conditions — условие Ready: True.

Проблема в том, что это работает только для типов, которые kstatus знает или которые следуют общей конвенции conditions. Как только в кластере появляется CRD со своей, отличной от стандартной, схемой status — например, оператор базы данных, у которого готовность выражается полем status.phase: Running или парой status.readyReplicas / status.lastJobStatus — Flux видит только то, что ресурс применился, и не может сказать, готов ли он содержательно. Реконсиляция считается успешной раньше, чем сервис реально способен принимать трафик.

Health check без CEL против health check с CEL: kstatus видит только Ready на известных типах, CEL-выражение читает произвольные поля status

На практике это било по надёжности выкатки. Если 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 не наступает, пока метрики анализа не сойдутся. Разница — в том, что именно проверяется и на каком уровне.

Gate между Kustomization: apps-api ждёт current из healthCheckExprs apps-db, а не просто факт apply

Здесь заканчивается его юрисдикция: сравнение с Flagger

Важно не спутать healthCheckExprs с progressive delivery. Это go/no-go проверка одного ресурса на одном шаге реконсиляции: либо current, либо нет. Она не умеет постепенно сдвигать трафик, сравнивать метрики канареечной и стабильной версий или откатываться по SLO — за это в экосистеме Flux по-прежнему отвечает Flagger, который управляет отдельным циклом: 10% → 50% → 100% трафика с проверкой Prometheus-метрик на каждом шаге, и автоматическим роллбэком, если канарейка не проходит.

healthCheckExprs (Flux)Flagger canaryArgo Rollouts AnalysisTemplate
Что проверяетпроизвольное поле status через CELметрики (latency, error rate) во времениметрики / произвольные проверки во времени
Гранулярностьодин ресурс, один снимокпостепенный трафик-шифтпостепенный трафик-шифт
РезультатReady: True/Falsepromote / 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, поведение не меняется. Миграция — это:

  1. Найти CRD, у которых готовность сейчас не проверяется по существу (Flux видит только apply) — обычно это операторские ресурсы с нестандартным status.
  2. Написать current/failed/inProgress по реальным полям status этого CRD и проверить CEL-выражение локально, до коммита — опечатка в имени поля не даст ошибки применения, просто health check никогда не станет True.
  3. Добавить healthCheckExprs в спеку и закоммитить — при следующем reconcile Flux начнёт оценивать состояние по новому правилу.
  4. Если на 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 и в MESSAGEHealth 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 — как двигаться безопасно.


Поделиться:

Следующая статья
OBI: zero-code трейсинг приложений через eBPF, наследник Grafana Beyla