Добавление нового окружения в FluxCD hub-and-spoke кластер

Published: 2026-04-30

Полный чеклист для подключения нового окружения — от создания кластера до первого успешного reconcile Flux. На примере гипотетического окружения staging в Yandex Cloud (лёгкий стек, без Kafka/Mongo/Cassandra/ClickHouse).


Общая схема

В hub-and-spoke модели hub-кластер (infra) запускает Flux и управляет spoke-кластерами через kubeConfig в каждом объекте Kustomization. Добавление нового окружения включает:

  1. Создание Kubernetes-кластера
  2. Создание структуры директорий FluxCD-проекта
  3. Создание верхнеуровневого kustomization-файла
  4. Написание патчей для каждого чарта
  5. Запечатывание kubeconfig как SealedSecret
  6. Подключение namespaces и secrets
  7. Регистрация в корневом kustomization

Шаг 1: создать кластер

Для Yandex Cloud:

bash# Создать через Terraform (или вручную в консоли YC)
yc managed-kubernetes cluster get-credentials staging --external
kubectl config rename-context yc-staging staging-k8s
cp ~/.kube/config .kubeconfigs/staging.yaml

Для on-prem k3s:

bash# Добавить хосты в ansible/inventory/staging/hosts.yaml
# Добавить переменные в ansible/group_vars/staging.yaml
ansible-playbook ansible/install-k3s.yaml -i ansible/inventory/staging/
ansible-playbook ansible/get-kubeconfig.yaml -i ansible/inventory/staging/
ansible-playbook ansible/upgrade-cilium.yaml -i ansible/inventory/staging/
cp ~/.kube/config .kubeconfigs/staging.yaml

Шаг 2: создать структуру проекта

bashmkdir -p fluxcd/projects/staging/kustomization/{hub/patches,spoke/secrets,spoke/monitoring,routes}

За основу взять ближайшее по смыслу окружение. Для лёгкого стека YC лучший шаблон — sre или loadgds:

bashcp fluxcd/projects/sre/kustomization/hub/kustomization.yaml \
   fluxcd/projects/staging/kustomization/hub/kustomization.yaml

cp -r fluxcd/projects/sre/kustomization/spoke/ \
      fluxcd/projects/staging/kustomization/spoke/

cp fluxcd/projects/sre/kustomization/routes/kustomization.yaml \
   fluxcd/projects/staging/kustomization/routes/kustomization.yaml

Шаг 3: создать staging-kustomization.yaml

Скопировать fluxcd/sre-kustomization.yamlfluxcd/staging-kustomization.yaml и отредактировать:

yaml# Namespace для hub-объектов
apiVersion: v1
kind: Namespace
metadata:
  name: staging
---
# SealedSecret placeholder — заполнить на шаге 5
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: staging-kubeconfig
  namespace: staging
spec:
  encryptedData:
    value: PLACEHOLDER
---
# Hub: деплой приложений в staging-кластер
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: staging-apps
  namespace: staging
spec:
  interval: 10m
  path: ./fluxcd/projects/staging/kustomization/hub/
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-system
    namespace: flux-system
  kubeConfig:
    secretRef:
      name: staging-kubeconfig
---
# Spoke: ресурсы уровня кластера
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: staging-spoke
  namespace: staging
spec:
  interval: 10m
  path: ./fluxcd/projects/staging/kustomization/spoke/
  prune: true
  dependsOn:
    - name: staging-apps
  sourceRef:
    kind: GitRepository
    name: flux-system
    namespace: flux-system
  kubeConfig:
    secretRef:
      name: staging-kubeconfig
---
# Routes: ApisixRoutes и TLS-конфигурация
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: staging-routes
  namespace: staging
spec:
  interval: 10m
  path: ./fluxcd/projects/staging/kustomization/routes/
  prune: true
  dependsOn:
    - name: staging-spoke
  sourceRef:
    kind: GitRepository
    name: flux-system
    namespace: flux-system
  kubeConfig:
    secretRef:
      name: staging-kubeconfig

Шаг 4: создать патчи

Один файл патча на каждый Helm chart, только дельта относительно base.

kube-prom-stack.yaml — storageClass, retention, external labels:

yamlspec:
  values:
    prometheus:
      prometheusSpec:
        retention: 7d
        externalLabels:
          cluster: staging    # ОБЯЗАТЕЛЬНО уникально для каждого кластера
        storageSpec:
          volumeClaimTemplate:
            spec:
              storageClassName: yc-network-ssd
              resources:
                requests:
                  storage: 20Gi
    grafana:
      ingress:
        hosts:
          - grafana.staging.test.example.com

elasticsearch.yaml — hostname и storage:

yamlspec:
  values:
    master:
      persistence:
        storageClass: yc-network-ssd
    ingress:
      hosts:
        - host: elasticsearch.staging.test.example.com

apisix-int.yaml — external IP (YC назначит автоматически):

yamlspec:
  values:
    service:
      type: LoadBalancer
      # loadBalancerIP не указывать — YC назначит сам

Шаг 5: запечатать kubeconfig

bashkubectl create secret generic staging-kubeconfig \
  --from-file=value=.kubeconfigs/staging.yaml \
  --namespace staging \
  --dry-run=client -o yaml | \
  kubeseal \
    --format yaml \
    --controller-namespace flux-system \
    --kubeconfig .kubeconfigs/infra.yaml \
  > /tmp/staging-sealed.yaml

# Скопировать encryptedData.value из /tmp/staging-sealed.yaml
# в fluxcd/staging-kustomization.yaml
# Удалить временный файл
rm /tmp/staging-sealed.yaml

Kubeconfig запечатан ключом Sealed Secrets infra-кластера. При пересборке infra-кластера нужно перезапечатать все kubeconfig'и новым ключом.


Шаг 6: добавить в spoke overlays

Создать fluxcd/projects/staging/kustomization/spoke/namespaces.yaml со списком namespaces. Для лёгкого стека исключить kafka, mongodb, rabbitmq, cassandra, clickhouse.

Добавить docker-registry pull secrets в spoke/secrets/ — скопировать из существующего окружения и перезапечатать с namespace staging.

Для YC-кластеров: убрать cilium-lb-pool.yaml и cilium-l2-announcement.yaml — не нужны.


Шаг 7: создать ApisixTls маршрут

yaml# routes/apisix-tls-staging.yaml
apiVersion: apisix.apache.org/v2
kind: ApisixTls
metadata:
  name: staging-example-com
  namespace: ingress-apisix
spec:
  hosts:
    - "*.staging.test.example.com"
  secret:
    name: staging-example-com-tls
    namespace: ingress-apisix

TLS-сертификат и ключ добавить как SealedSecret в spoke/secrets/.


Шаг 8: зарегистрировать в корневом kustomization

yaml# fluxcd/kustomization.yaml
resources:
  - flux-system/
  - infra-kustomization.yaml
  - dev-kustomization.yaml
  - test-kustomization.yaml
  - sre-kustomization.yaml
  - loadgds-kustomization.yaml
  - staging-kustomization.yaml   # ← добавить

Шаг 9: commit, push, проверка

bashgit add fluxcd/
git commit -m "feat: add staging environment"
git push

# Ждём, пока Flux подберёт изменения (обычно 2-3 минуты), затем проверяем
flux get kustomizations -A --context=infra-k8s | grep staging

# Принудительный reconcile если нужно
flux reconcile ks staging-apps -n staging --context=infra-k8s --timeout=10m

# Проверить поды на новом кластере
kubectl get pods -A --context=staging-k8s

Типичные ошибки

  • Забыть externalLabels.cluster в kube-prom-stack — Grafana не сможет отличить метрики staging от dev. Значение должно быть уникальным для каждого кластера.
  • Нет поля namespace в патче — Kustomize не найдёт целевой HelmRelease. Целевой объект в патче должен включать namespace.
  • dependsOn указывает на несуществующий объект — проверить, что имена объектов Kustomization в spoke.yaml и monitoring.yaml совпадают точно.
  • Отсутствует TLS-сертификат — если APISIX стартует до расшифровки TLS SealedSecret, маршруты упадут. Цепочка dependsOn обеспечивает порядок.
  • Неправильный контекст в kubeconfig — запечатанный kubeconfig должен содержать полный путь к серверу, а не alias, существующий только локально.
  • Скопировать патчи с Cilium L2 на YC-кластер — убрать поле loadBalancerIP; YC назначает автоматически.