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