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-services → agentfy-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-secrets2.9.0 в обоих контурах (gitops-apps/{dev,prod}/external-secrets.yaml), читает HashiCorp Vault, который стоит на control-кластере (gitops-apps/control/vault.yaml, чартvault0.34.0, standalone, 5Gi,vault.agentfy.ai). Стор — этоClusterSecretStoreс именемvault: KV v2 по путиsecret, аутентификацияkubernetesна mountkubernetes-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 (единственный императивный шаг)
- Поставить ArgoCD в namespace
argocd(Helm/манифест) — один раз. - Применить корневой app-of-apps Application → Argo тянет всё остальное из Git.
- Дальше кластер (опц. включая сам 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 даёт воспроизводимое, аудируемое, откатываемое платформенное состояние; эфемерный рантайм агента остаётся динамичным поверх него.