From 03dd81c825f2fea56e5be67bfdc104621ceab7c8 Mon Sep 17 00:00:00 2001 From: ZdenekSrotyr Date: Tue, 21 Apr 2026 16:51:20 +0200 Subject: [PATCH] docs: update deployment log with final state and onboarding workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Volume fix documented (Docker named volume → bind mount /data) - Watchtower → cron-based auto-upgrade - Final state snapshot of VMs, repos, tags, secrets - Onboarding flow summary for 2nd customer --- .../plans/2026-04-21-deployment-log.md | 219 ++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-21-deployment-log.md diff --git a/docs/superpowers/plans/2026-04-21-deployment-log.md b/docs/superpowers/plans/2026-04-21-deployment-log.md new file mode 100644 index 0000000..3dad12a --- /dev/null +++ b/docs/superpowers/plans/2026-04-21-deployment-log.md @@ -0,0 +1,219 @@ +# Agnes Multi-Customer Deployment Log + +**Datum:** 2026-04-21 +**Spec:** `docs/superpowers/specs/2026-04-21-multi-customer-deployment-spec.md` +**Plan:** `docs/superpowers/plans/2026-04-21-multi-customer-deployment.md` + +Průběžný log všeho, co bylo uděláno, včetně zvolených hodnot, úprav plánu, objevených překážek a jejich řešení. Cílem je, aby **další zákazník šel nasadit jedním skriptem**. + +--- + +## Přehled + +Startup stav: Keboola prod/dev Agnes běžel z osobního forku `padak/tmp_oss` (branch `feature/v2-fastapi-duckdb-docker-cli`), git pull při boot, tokeny v plaintextu v VM metadata. Cíl: přejít na self-deploy model — public upstream `keboola/agnes-the-ai-analyst` + privátní `keboola/agnes-infra-keboola` s Terraformem, GHCR `:stable` image, Secret Manager. + +## Konvence + +- **Public repo:** `keboola/agnes-the-ai-analyst` (app + TF modul) +- **Privátní repo:** `keboola/agnes-infra-{customer}` (pro Keboolu `keboola/agnes-infra-keboola`) +- **GCP projekt:** `kids-ai-data-analysis` (Keboola) — pozn.: ponechán, owner `petr@keboola.com` +- **Deploy SA:** `agnes-deploy@.iam.gserviceaccount.com` +- **TF state bucket:** `gs://agnes--tfstate//` +- **VM SA:** `agnes--vm@.iam.gserviceaccount.com` (scope: secretmanager.secretAccessor) +- **Secrets v SM:** + - `keboola-storage-token` — sdílený, manuálně vytvořený + - `agnes--jwt-secret` — per-customer, auto-generovaný TF +- **Image tag:** + - `:stable` (floating) — prod default + - `:dev` (floating) — dev default + - `:dev-` — per-branch (vyžaduje workflow commit — viz Známá omezení) + +## Chronologie + +### 2026-04-21 odpoledne — Fáze 0 + 1 (MVP) + +1. **Ověření IAM přes operativu:** `gcloud iam service-accounts create test...` — funguje i bez přímé role na projektu. Keboola má org-level inherited perms. Owner zůstává `petr@keboola.com`. +2. **GHCR image public:** `docker manifest inspect ghcr.io/keboola/agnes-the-ai-analyst:stable` funguje bez auth. +3. **Snapshot boot disku:** `data-analyst-pre-migration-20260421` (safety net před Fází 2). +4. **Per-branch tagging v release.yml:** commit `0ade45c` — přidává `:dev-` tag. **Nepushnuto** do origin kvůli chybějícímu `workflow` scope; uložen jako patch `~/.agnes-keys/0ade45c-workflow-per-branch-tag.patch`. +5. **bootstrap-gcp.sh:** Vytváří SA + role + tfstate bucket + SA key. Spuštěno na `kids-ai-data-analysis`. Vytvořen `agnes-deploy` SA, bucket `gs://agnes-kids-ai-data-analysis-tfstate`, klíč uložen do `~/.agnes-keys/agnes-deploy-kids-ai-data-analysis-key.json`. +6. **Secret Manager:** `keboola-storage-token`, `jwt-secret-key` nahrány (obě s PŘEDCHOZÍMI hodnotami — `jwt-secret-key` aby existing JWT tokeny zůstaly validní; `keboola-storage-token` pro kontinuitu syncu). Rotace tokenu odložena do Fáze 2 completion. +7. **fetch-env-from-secrets.sh:** VM-side skript, který stahuje secrets a skládá `.env`. +8. **Deploy MVP na staré VM `data-analyst`:** + - `docker compose down` → `git remote set-url origin https://github.com/keboola/agnes-the-ai-analyst.git` → `git fetch + reset --hard origin/main` → scp fetch-env.sh → `fetch-env.sh` → `docker compose pull + up -d` + - Ověřeno: `/api/health` `status: degraded` (stale tables, OK), image `ghcr.io/keboola/agnes-the-ai-analyst:stable`, login `zdenek.srotyr@keboola.com / 1234` funguje. +9. **Deploy MVP na staré VM `data-analyst-dev`:** App dir je `/opt/data-analyst/` pod userem `zdeneksrotyr` (jiná struktura než prod). Scope VM je omezený — `fetch-env.sh` selhal, ale .env zůstal beze změny (stejné hodnoty), app běží na `:stable`. +10. **tmp_oss smazán:** Starý osobní fork už neexistoval. + +### 2026-04-21 odpoledne — Fáze 2 (TF modul + nové VMs) + +11. **TF modul `infra/modules/customer-instance/`:** Refactor z monolitního `infra/main.tf` na reusable modul s: + - `prod_instance` object + `dev_instances` list (podporuje per-branch image_tag) + - Persistent `/data` disk (pd-ssd, default 50 GB prod / 20 GB dev) + - Dedikovaný VM SA `agnes--vm` jen s `secretmanager.secretAccessor` + - Auto-generovaný JWT secret v SM + - OS Login (`enable-oslogin=TRUE`) + - Startup script: mount disku, download docker-compose z main branch, fetch secrets, `docker compose up`, volitelně watchtower + Caddy profile + - **Commit:** `a2c05a5 infra: refactor Terraform into reusable customer-instance module` +12. **Tag `infra-v1.0.0`** push do origin. +13. **Privátní repo `keboola/agnes-infra-keboola`:** Vytvořen v Keboola org. Struktura: + - `terraform/main.tf` — module reference `github.com/keboola/agnes-the-ai-analyst//infra/modules/customer-instance?ref=infra-v1.0.0`, backend `gcs` + - `terraform/variables.tf` — default hodnoty pro Keboolu (project, region, prod_instance, dev_instances) + - `.github/workflows/plan.yml` — PR: `terraform plan` → komentář v PR přes `gh pr comment` (ne `actions/github-script` kvůli validátoru) + - `.github/workflows/apply.yml` — push main: apply-dev (env `dev`, no protection) → apply-prod (env `prod`, protected_branches, 5min wait, smoke test) + - GitHub secret `GCP_SA_KEY` nahrán z `~/.agnes-keys/agnes-deploy-*.json` + - Environmenty `dev` a `prod` vytvořeny přes `gh api` +14. **Terraform apply Keboola instance:** 12 resources vytvořeno: + - `agnes-prod` VM + `agnes-prod-data` disk (50 GB) + `agnes-prod-ip` (34.77.102.61) + - `agnes-dev` VM + `agnes-dev-data` disk (20 GB) + `agnes-dev-ip` (34.77.94.14) + - Firewall `agnes-keboola-allow-web` + - `agnes-keboola-vm` SA + IAM binding + - `agnes-keboola-jwt-secret` + version + - TF state v `gs://agnes-kids-ai-data-analysis-tfstate/keboola/` +15. **Data migration starý prod → nový prod (~2 min):** + - `docker compose down` na starém prod VM + - `tar czf /tmp/agnes-data.tar.gz -C /var/lib/docker/volumes/app_data/_data .` (1.8 GB) + - `gsutil cp` do `gs://agnes-kids-ai-data-analysis-tfstate/migration/agnes-data-20260421-1624.tar.gz` + - **Problém:** `agnes-keboola-vm` SA neměl `storage.objectViewer` na bucketu → `gsutil iam ch serviceAccount:...:objectViewer gs://...` (dočasné, pro download) + - `docker compose down` na novém prod VM + - `gsutil cp` z bucketu na nový VM + `tar xzf ... -C /data` + - `docker compose up -d` na novém prod VM + - **POZOR:** Analytics DB se nezbudovala automaticky po extrakci — viz Známá omezení. + +## Klíčové hodnoty (kopíruj pro další zákazníky) + +``` +GCP_PROJECT_ID = kids-ai-data-analysis +CUSTOMER_NAME = keboola +DEPLOY_SA = agnes-deploy@kids-ai-data-analysis.iam.gserviceaccount.com +TFSTATE_BUCKET = gs://agnes-kids-ai-data-analysis-tfstate +TFSTATE_PREFIX = keboola +VM_SA = agnes-keboola-vm@kids-ai-data-analysis.iam.gserviceaccount.com +JWT_SECRET = agnes-keboola-jwt-secret (TF-managed) +KEBOOLA_TOKEN_SECRET = keboola-storage-token (manuálně vytvořený) +INFRA_MODULE_REF = infra-v1.0.0 (github.com/keboola/agnes-the-ai-analyst) +PROD_IP = 34.77.102.61 (agnes-prod) +DEV_IP = 34.77.94.14 (agnes-dev) +STARÝ PROD IP (legacy) = 35.195.96.98 (data-analyst — po stabilitě smazat) +STARÝ DEV IP (legacy) = 34.62.223.189 (data-analyst-dev — po stabilitě smazat) +``` + +## Známá omezení / TODO + +### Workflow commit nepushnutý +Commit `0ade45c` (per-branch `:dev-` tag v release.yml) vyžaduje `workflow` scope na GH tokenu, který aktuální token nemá. Uloženo v `~/.agnes-keys/0ade45c-workflow-per-branch-tag.patch`. + +**Akce pro dokončení:** +```bash +gh auth refresh -h github.com -s workflow +cd +git am ~/.agnes-keys/0ade45c-workflow-per-branch-tag.patch +git push origin feature/multi-customer-deployment +``` + +Bez toho fungují jen floating tagy `:dev` a `:stable`, ale ne pinned `:dev-` v `dev_instances`. + +### Analytics DB se po migraci dat nepřebudovala +Po kopii `/data` přes tar na nový prod VM má `system.duckdb` všechno (table_registry, users), ale analytics DB je prázdná — SyncOrchestrator nespustil `rebuild()` automaticky. Endpoint `/api/sync/trigger` nebo `/api/sync/rebuild` bude třeba dohledat v app API a zavolat autentizovaně. + +### Dev VM `data-analyst-dev` staré scope +Staré `data-analyst-dev` má omezené compute SA scope bez Secret Manageru. V Fázi 2 se nahrazuje novým `agnes-dev` (s dedikovaným VM SA), staré zruš po ověření stability. + +### Starý Keboola token nerotován +Nový token v SM je stále ten stejný, co byl v `.env` na starém VM. Po ověření stability nového proudu v Keboola UI vygenerovat nový + `gcloud secrets versions add keboola-storage-token` + restart containerů. Starý pak invalidovat. + +### Admin heslo `1234` na starém prod +Migrace dat zkopírovala users table, takže heslo je platné i na novém prod. Rotace je uživatelův úkon přes UI. Nové dev VM má jiný state → jiné hesla. + +## Co zbývá + +- [ ] Po 24h stability smazat staré `data-analyst` a `data-analyst-dev` VMs +- [ ] Smazat snapshot `data-analyst-pre-migration-20260421` (až bude jistý úspěch) +- [ ] Rotovat Keboola Storage token v Keboola UI → `gcloud secrets versions add keboola-storage-token` → smazat starou verzi +- [ ] Pushnout workflow commit `0ade45c` (per-branch :dev- tag) po `gh auth refresh -h github.com -s workflow` +- [ ] Renovate config (opt-in, pro zákazníky co chtějí pinned `:stable-YYYY.MM.N`) + +## Aktualizace průběhu (2026-04-21 pozdně) + +### Fixy po první migraci + +1. **Docker named volume → bind mount /data:** + Po první migraci nové VMs používaly `agnes_data` Docker named volume (uložený na boot disku 30GB), nikoli persistent disk mountovaný na `/data` (50GB). Fix: v `docker-compose.prod.yml` override volume `data` jako bind mount `/data`. Commit `52d6345`. Bumplý tag `infra-v1.1.0`. + +2. **Watchtower → cron:** + `containrrr/watchtower` (v1.7.1 i latest) má nekompatibilní Docker API (posílá 1.25, daemon vyžaduje 1.40+). Nahrazen bash skriptem `/usr/local/bin/agnes-auto-upgrade.sh` spouštěným cronem každých 5 min. Detekuje změnu image digest, pokud ano, pullne + `docker compose up -d`. Commit `cbd85c5` v modulu, tag `infra-v1.1.0`. + +3. **Ověření auto-upgrade:** + Během finálního verify cyklu cron pullnul novější `:stable-2026.04.33` (nejnovější release) a recreate containers na prod. Fungování potvrzené. + +### Finální stav + +| Resource | Value | +|---|---| +| **Prod VM** | `agnes-prod` @ 34.77.102.61 (e2-small, 50GB /data PD) | +| **Dev VM** | `agnes-dev` @ 34.77.94.14 (e2-small, 20GB /data PD) | +| **Starý prod VM** (decommission pending 24h) | `data-analyst` @ 35.195.96.98, app stopped | +| **Starý dev VM** (decommission pending 24h) | `data-analyst-dev` @ 34.62.223.189, app stopped | +| **Image verze na prod** | `ghcr.io/keboola/agnes-the-ai-analyst:stable` (aktuálně `stable-2026.04.33`) | +| **Image verze na dev** | `ghcr.io/keboola/agnes-the-ai-analyst:dev` | +| **Auto-upgrade** | Cron `*/5 * * * *` — bash skript, detekce digest change → restart | +| **Prod health** | `degraded` (stale tables — první sync po migraci pending), 103 tables, 9.3M rows, 2 users | +| **Login** | Zachovaný admin `zdenek.srotyr@keboola.com / 1234` | +| **TF state** | `gs://agnes-kids-ai-data-analysis-tfstate/keboola/` (versioned, GCS backend) | +| **Deploy SA** | `agnes-deploy@kids-ai-data-analysis.iam.gserviceaccount.com` | +| **VM SA** (scope: secretmanager.secretAccessor) | `agnes-keboola-vm@kids-ai-data-analysis.iam.gserviceaccount.com` | +| **Secrets** | `keboola-storage-token`, `agnes-keboola-jwt-secret` (TF-managed) | +| **Public upstream repo** | https://github.com/keboola/agnes-the-ai-analyst | +| **Template repo** | https://github.com/keboola/agnes-infra-template (is_template=true) | +| **Keboola private infra repo** | https://github.com/keboola/agnes-infra-keboola (dev + prod GitHub environments configured) | +| **Module tag** | `infra-v1.1.0` (obsahuje bind-volume fix + cron auto-upgrade) | + +### Onboarding druhého zákazníka — kompletní flow + +Podle [`docs/ONBOARDING.md`](../../ONBOARDING.md) — cíl: < 1 hodina. Klíčové kroky: + +1. `bootstrap-gcp.sh ` — SA + bucket + klíč +2. `gcloud secrets create keboola-storage-token ...` (pokud source = keboola) +3. `gh repo create /agnes-infra- --template keboola/agnes-infra-template --private` +4. Upload GCP_SA_KEY do GH secret +5. Editovat `terraform/main.tf` (backend bucket/prefix) + `terraform.tfvars` +6. Vytvořit `dev` + `prod` environments přes `gh api` +7. `git push` → CI apply +8. `POST /auth/bootstrap` admin user +9. Otestovat `/api/health` + login + +Předpokládám, že nový zákazník (např. GRPN) projde všech 9 kroků za **~30–45 min** včetně čekání na TF apply. + + +## Budoucí one-click deploy + +Cíl: pro nového zákazníka `{customer}` (např. `grpn`) by mělo stačit: + +```bash +# 1. Vytvořit GCP projekt (má billing) +gcloud projects create agnes-{customer} + +# 2. Bootstrap GCP (SA + bucket + role + klíč) +./scripts/bootstrap-gcp.sh agnes-{customer} + +# 3. Vytvořit Keboola Storage secret v zákaznickém SM (manuálně, token dodá zákazník) +echo -n "" | gcloud secrets create keboola-storage-token \ + --data-file=- --replication-policy=automatic --project=agnes-{customer} + +# 4. Klonovat template repo (template repo musí existovat — Fáze 6) +gh repo create {org}/agnes-infra-{customer} --template keboola/agnes-infra-template --private + +# 5. Upload SA key do GH secretu +cd agnes-infra-{customer} +gh secret set GCP_SA_KEY < ~/.agnes-keys/agnes-deploy-agnes-{customer}-key.json + +# 6. Vyplnit terraform/terraform.tfvars (customer_name, project, IP preferences) + +# 7. První apply — spustí CI/CD a nahodí VMs +git add . && git commit -m "initial" && git push +``` + +Co tomu ještě chybí: +- Template repo (Fáze 6) +- Onboarding skript, který provede kroky 1–7 interaktivně +- Dokumentace: jak nastavit DNS, TLS, admin account bootstrap