Skip to content

GitOps (ArgoCD)

Состояние кластера декларативно в Git и сводится ArgoCD. Деплой = коммит манифестов (или тегов образов), а не kubectl apply. Git — единственный источник истины для долгоживущих нагрузок; кластер постоянно self-heal'ится к нему.

Большая часть этой страницы — замысел; установка живёт в другом месте

Argo CD развёрнут и работает, и управляет сам собой (gitops-apps/control/argocd.yaml, чарт argo-cd 10.3.2). Просто не здесь: манифесты лежат во втором репозитории — Agentfy/gitops, — который до недавнего времени не был назван в этом ни разу. Начните с Двух репозиториев: там проведена граница и перечислено, что работает сегодня, файл за файлом. Каждый раздел ниже теперь говорит, какие его решения установка взяла, а какие нет.

Граница — чем ArgoCD управляет, а чем нет

Ключевое решение для платформы с эфемерным рантаймом:

ArgoCD управляет (GitOps)ArgoCD НЕ управляет
Постоянные Deployments: api, app, admin, LightRAGэфемерные worker-Job'ы (браузер крутится внутри них)
Services, Ingress, cert-manager, NetworkPolicies, ResourceQuotas, namespacesотдельные Job'ы AgentRuntimeSession
Redis / Postgres (через оператор или манифесты)всё, что создаётся под задачу в рантайме
ServiceAccount + RBAC воркера, ref образа раннера, пресеты RuntimeProfile (ConfigMap)

Job'ы воркера создаются императивно слайсом runtime/worker через k8s API (сотни раз, scale-to-zero). GitOps не должен ими владеть — Argo постоянно видел бы их как «дрейф» и пруннил. Argo владеет деплой-артефактами воркера (образ, RBAC, профили, шаблон NetworkPolicy); runtime-менеджер владеет экземплярами Job.

И ArgoCD ≠ Argo Workflows — Workflows для воркера не используем (Native Jobs).

Репозиторий и раскладка

Разделение состоялось. Манифесты лежат не в agentfy2/k8s/, а в Agentfy/gitops, и именно там проверяется каждое утверждение этой страницы. Что там на самом деле:

Agentfy/gitops/
├── bootstrap/            применяется руками ОДИН раз: clusterissuer, argo ingress, root-app
├── gitops-apps/
│   ├── control/          Application'ы на control-кластере (argocd, rancher, vault, cert-manager)
│   └── dev/ · prod/      Application'ы на контур (cert-manager, ESO, external-dns, reloader)
│       └── manifests/    СЫРЫЕ манифесты, доставляются В рабочий кластер
├── agentfy-apps/dev/     по Application на сервис продукта (api, app, admin, redis)
└── helm/                 сами чарты: agentfy-{api,app,admin,redis}

Одно правило оттуда стоит перенести сюда, потому что нарушается оно молча: в gitops-apps/<env>/ могут лежать только объекты Application — они обязаны существовать на control-кластере. Сырой ClusterIssuer или ExternalSecret кладут в gitops-apps/<env>/manifests/, откуда отдельный Application (manifests-app.yaml, directory.recurse: true) доставляет их в рабочий кластер.

Отменено — раскладка, которую планировал проект, и почему она сохранена

Дерево ниже было планом: чарты в k8s/charts/, values в k8s/values/<env>/, Application'ы в k8s/argocd/, всё в этом репозитории, с «опцией вынести в отдельный репо agentfy-deploy позже». Опцию взяли. План сохранён, а не стёрт, потому что форма, за которую он спорит — чарт на компонент, values по средам, app-of-apps, — это ровно та форма, которую использует Agentfy/gitops; изменились только адреса. Единственный компонент отсюда, которого нет нигде, — LightRAG.

Helm — один чарт на компонент + values по средам:

k8s/
├── charts/
│   ├── api/ app/ admin/ lightrag/   # чарт на приложение (templates/ + values.yaml)
│   ├── worker/        # SA + RBAC + шаблон NetworkPolicy + runtimeProfiles (чарт)
│   ├── platform/      # Redis, Postgres (CNPG), namespaces, ResourceQuota/LimitRange
│   └── networking/    # Ingress, cert-manager Issuer, default-deny NetworkPolicies
├── values/
│   ├── dev/  <app>.yaml     # реплики, ресурсы, хосты, теги образов
│   └── prod/ <app>.yaml
└── argocd/
    ├── root.yaml      # корневой Application app-of-apps
    └── apps/          # по одному ArgoCD Application на чарт (source.helm.valueFiles → values/<env>)

(Общие сниппеты — через небольшой base/library-чарт или зависимости чартов; секреты в valuesне кладём — см. ниже.)

App-of-apps

Эту часть установка взяла целиком. Корневой Application — это bootstrap/root-app.yaml; он смотрит в gitops-apps/control на main, и все остальные Application'ы достижимы оттуда, включая два, которые сами являются app-of-apps (agentfy-dev-apps, agentfy-prod-apps, каждый смотрит в папку своего контура), и один, который несёт продукт (agentfy-dev-servicesagentfy-apps/dev/). Добавить сервис по-прежнему — один дочерний манифест. ApplicationSet не используется; папки продублированы по контурам руками.

Поток доставки

пуш кода → сборка и пуш образа (ghcr.io/agentfy/<app>) → бамп image.tag в
           Agentfy/gitops helm/<chart>/values.dev.yaml → коммит → ArgoCD синкает

Правая половина настоящая: тег действительно одна строка в helm/agentfy-{api,app,admin}/values.dev.yaml, Git — источник истины, деплой откатывается через git revert.

Левая половина не подтверждена. Ни в одном из репозиториев нет workflow, который собирал бы или пушил образ приложения: три workflow GitHub Actions в Agentfy/gitops — это ansible-lint и два ручных запуска Ansible, а в этом репозитории нет .github/ вообще, — при этом values-файлы фиксируют реальные теги, значит образы существуют. Кто их собирает, нигде проверяемо не записано. Argo CD Image Updater не установлен; бампы тегов — это коммиты.

Секреты в Git (никогда не plaintext)

Манифесты ссылаются на инфра-креды (DB URL, Redis URL, KEK секретов, Claude API key, ключ Resend, креды registry) — никогда не в открытом виде. Один из вариантов:

  • Sealed Secrets (Bitnami)не взяли. Это была рекомендация для MVP; проиграла она потому, что оставляет шифротекст в Git — ротация значения становится коммитом, а ключ контроллера — единой точкой потери.
  • SOPS + ageне взяли, то же возражение плюс плагин Argo, который надо сопровождать.
  • External Secrets Operator — это и есть выбор, и он работает. Чарт external-secrets 2.9.0 в обоих контурах (gitops-apps/{dev,prod}/external-secrets.yaml), читает HashiCorp Vault, который стоит на control-кластере (gitops-apps/control/vault.yaml, чарт vault 0.34.0, standalone, 5Gi, vault.agentfy.ai). Стор — это ClusterSecretStore с именем vault: KV v2 по пути secret, аутентификация kubernetes на mount kubernetes-dev / kubernetes-prod, роль eso (gitops-apps/{dev,prod}/manifests/external-secret*/clustersecretstore.yaml).

В Git не шифруется вообще ничего: значение живёт в Vault, ExternalSecret называет его путь, и появляется обычный Kubernetes Secret. Рабочие примеры — externalsecret-external-dns.yaml (ключи AWS для ExternalDNS, обновление раз в час), externalsecret-ghcr.yaml (pull-секрет реестра) и собственный секрет api, который helm/agentfy-api/ шаблонизирует из secrets.vaultKey.

Замечание: секреты агента тут не лежат — они зашифрованы в Postgres (см. Секреты агента). GitOps держит только платформенные/инфра-креды.

Политика синка

  • dev: automated синк + selfHeal + prune — полностью hands-off. Как и задумано.
  • prod: планировался через PR, с ручным окном для рисковых изменений. Сегодня это не так: у каждого Application в Agentfy/gitops, включая prod, стоит automated: {prune: true, selfHeal: true}. Гейт — это PR в main того репозитория, и больше ничего.
  • Sync waves используются, и это весь существующий порядок: 0 — redis и платформенные чарты, 1 — api и сырые манифесты, 2 — app и admin.
  • Миграции: планировались как PreSync hook Job. Сегодня prisma migrate deploy выполняется в initContainer у Deployment api (helm/agentfy-api/templates/deployment.yaml, по флагу runMigrations). Разница не косметическая: initContainer запускается на каждый под и гонится сам с собой при replicas > 1 — отчасти поэтому чарт api фиксирует strategy: Recreate и одну реплику.

Bootstrap (единственный императивный шаг)

  1. Поставить ArgoCD в namespace argocd (Helm/манифест) — один раз.
  2. Применить корневой app-of-apps Application → Argo тянет всё остальное из Git.
  3. Дальше кластер (опц. включая сам ArgoCD) управляется Git.

Так и произошло. В Agentfy/gitops это один kubectl apply -f bootstrap/ против control-кластера, и в папке ровно три файла: ClusterIssuer Let's Encrypt, Ingress для UI Argo CD (argo.agentfy.ai) и root-app.yaml. Argo CD действительно управляет собой оттуда — gitops-apps/control/argocd.yaml — с ignoreDifferences на argocd-secret, чтобы не воевать за сгенерированный пароль администратора.

Переиспользование из Ranch

cleanslice/ranch/k8s — рабочий референс, ~80% шаблон для нас:

  • argocd/app-of-apps.yaml — ровно этот app-of-apps (по Application на компонент, automated: prune+selfHeal). Копируем структуру; меняем repoURL/пути/namespaces.
  • platform/lightrag/*рабочий деплой LightRAG на Postgres с pgvector + AGE (LIGHTRAG_{KV,VECTOR,GRAPH,DOC_STATUS}_STORAGE=PG*) — наш выбранный бэкенд.
  • browser-pool-image/ — образ Ranch с Browserless + JWT-noVNC live-takeover; мы вместо этого вшиваем Playwright + Chromium прямо в образ worker (браузер крутится in-pod, без отдельного пула), но трюк с noVNC live-takeover стоит позаимствовать в воркер.
  • infrastructure/cnpg.yaml + database/pg-cluster.yaml — CloudNativePG для основной app-БД.
  • platform/{api,app,admin}/*, deploy/ (cert-issuer, ghcr-pullsecret), local/ bootstrap.

Адаптировать — единственное реальное различие: Ranch гоняет агентов через Argo Workflows (templates/rbac.yaml SA + RBAC на workflows + agent-workflow.manifest.ts). У нас Native k8s Jobs → выкидываем install/RBAC Workflows; форму RBAC оставляем (platform/api/rbac.yaml: API-ServiceAccount в platform + Role в namespace workers), но с verbs batch/jobs create/delete для нашего runtime/worker-менеджера.

Ни один пункт этого раздела не выполнен. Это обзор того, что стоит позаимствовать, написанный до того, как появился Agentfy/gitops. Установка взяла отсюда форму app-of-apps и pull-секрет ghcr; LightRAG, CNPG и нодпул workers ниже не развёрнуты нигде — живой Postgres стоит на своих VM в Hetzner (ansible/roles/postgres), а не на CNPG.

Зафиксированные gotchas: у CNPG в стоковом образе нет Apache AGE (не даёт переопределить shared_preload_libraries) → LightRAG-Postgres у Ranch на gzdaniel/postgres-for-rag (pgvector + AGE) — то есть на практике два Postgres (app-БД на CNPG; LightRAG/AGE-БД отдельно) пока нет кастомного CNPG-with-AGE. Hetzner: storageClassName: hcloud-volumes, PVC пинятся к worker-нодам; ноды node-role: workers + toleration workload=worker:NoSchedule.

Что остаётся императивным / рантайм

  • Job'ы worker и AgentRuntimeSession — создаёт runtime-менеджер под задачу.
  • Данные агента (агенты, память, чат, секреты) — в Postgres, не в Git.
  • Автоскейл нодпула workers — cluster-autoscaler реагирует на pending-Job'ы.

GitOps даёт воспроизводимое, аудируемое, откатываемое платформенное состояние; эфемерный рантайм агента остаётся динамичным поверх него.