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, без окремого пулу), але трюк із live-takeover через noVNC варто запозичити у воркер.
  • 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 дає відтворюваний, аудитований, відкочуваний платформний стан; ефемерний рантайм агента лишається динамічним поверх нього.