- ONBOARDING.md: replace 'propagating module changes' section with two explicit options — workflow_dispatch with recreate_targets (recommended, CI audit trail), or local terraform apply -replace (emergency). Adds a 'do not' section banning manual .env edits on VMs. - deployment-log.md: iteration 4 summary (version badge + module v1.5.0 + workflow_dispatch).
19 KiB
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 Keboolukeboola/agnes-infra-keboola) - GCP projekt:
kids-ai-data-analysis(Keboola) — pozn.: ponechán, ownerpetr@keboola.com - Deploy SA:
agnes-deploy@<project>.iam.gserviceaccount.com - TF state bucket:
gs://agnes-<project>-tfstate/<customer>/ - VM SA:
agnes-<customer>-vm@<project>.iam.gserviceaccount.com(scope: secretmanager.secretAccessor) - Secrets v SM:
keboola-storage-token— sdílený, manuálně vytvořenýagnes-<customer>-jwt-secret— per-customer, auto-generovaný TF
- Image tag:
:stable(floating) — prod default:dev(floating) — dev default:dev-<branch-slug>— per-branch (vyžaduje workflow commit — viz Známá omezení)
Chronologie
2026-04-21 odpoledne — Fáze 0 + 1 (MVP)
- 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. - GHCR image public:
docker manifest inspect ghcr.io/keboola/agnes-the-ai-analyst:stablefunguje bez auth. - Snapshot boot disku:
data-analyst-pre-migration-20260421(safety net před Fází 2). - Per-branch tagging v release.yml: commit
0ade45c— přidává:dev-<slug>tag. Nepushnuto do origin kvůli chybějícímuworkflowscope; uložen jako patch~/.agnes-keys/0ade45c-workflow-per-branch-tag.patch. - bootstrap-gcp.sh: Vytváří SA + role + tfstate bucket + SA key. Spuštěno na
kids-ai-data-analysis. Vytvořenagnes-deploySA, bucketgs://agnes-kids-ai-data-analysis-tfstate, klíč uložen do~/.agnes-keys/agnes-deploy-kids-ai-data-analysis-key.json. - Secret Manager:
keboola-storage-token,jwt-secret-keynahrány (obě s PŘEDCHOZÍMI hodnotami —jwt-secret-keyaby existing JWT tokeny zůstaly validní;keboola-storage-tokenpro kontinuitu syncu). Rotace tokenu odložena do Fáze 2 completion. - fetch-env-from-secrets.sh: VM-side skript, který stahuje secrets a skládá
.env. - 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/healthstatus: degraded(stale tables, OK), imageghcr.io/keboola/agnes-the-ai-analyst:stable, loginzdenek.srotyr@keboola.com / 1234funguje.
- Deploy MVP na staré VM
data-analyst-dev: App dir je/opt/data-analyst/pod useremzdeneksrotyr(jiná struktura než prod). Scope VM je omezený —fetch-env.shselhal, ale .env zůstal beze změny (stejné hodnoty), app běží na:stable. - tmp_oss smazán: Starý osobní fork už neexistoval.
2026-04-21 odpoledne — Fáze 2 (TF modul + nové VMs)
- TF modul
infra/modules/customer-instance/: Refactor z monolitníhoinfra/main.tfna reusable modul s:prod_instanceobject +dev_instanceslist (podporuje per-branch image_tag)- Persistent
/datadisk (pd-ssd, default 50 GB prod / 20 GB dev) - Dedikovaný VM SA
agnes-<customer>-vmjen ssecretmanager.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
- Tag
infra-v1.0.0push do origin. - Privátní repo
keboola/agnes-infra-keboola: Vytvořen v Keboola org. Struktura:terraform/main.tf— module referencegithub.com/keboola/agnes-the-ai-analyst//infra/modules/customer-instance?ref=infra-v1.0.0, backendgcsterraform/variables.tf— default hodnoty pro Keboolu (project, region, prod_instance, dev_instances).github/workflows/plan.yml— PR:terraform plan→ komentář v PR přesgh pr comment(neactions/github-scriptkvůli validátoru).github/workflows/apply.yml— push main: apply-dev (envdev, no protection) → apply-prod (envprod, protected_branches, 5min wait, smoke test)- GitHub secret
GCP_SA_KEYnahrán z~/.agnes-keys/agnes-deploy-*.json - Environmenty
devaprodvytvořeny přesgh api
- Terraform apply Keboola instance: 12 resources vytvořeno:
agnes-prodVM +agnes-prod-datadisk (50 GB) +agnes-prod-ip(34.77.102.61)agnes-devVM +agnes-dev-datadisk (20 GB) +agnes-dev-ip(34.77.94.14)- Firewall
agnes-keboola-allow-web agnes-keboola-vmSA + IAM bindingagnes-keboola-jwt-secret+ version- TF state v
gs://agnes-kids-ai-data-analysis-tfstate/keboola/
- Data migration starý prod → nový prod (~2 min):
docker compose downna starém prod VMtar czf /tmp/agnes-data.tar.gz -C /var/lib/docker/volumes/app_data/_data .(1.8 GB)gsutil cpdogs://agnes-kids-ai-data-analysis-tfstate/migration/agnes-data-20260421-1624.tar.gz- Problém:
agnes-keboola-vmSA nemělstorage.objectViewerna bucketu →gsutil iam ch serviceAccount:...:objectViewer gs://...(dočasné, pro download) docker compose downna novém prod VMgsutil cpz bucketu na nový VM +tar xzf ... -C /datadocker compose up -dna 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-<slug> 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í:
gh auth refresh -h github.com -s workflow
cd <public-repo>
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-<branch-slug> 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á (uživatelské akce)
- Approve prod environment v
apply.ymlrunu (https://github.com/keboola/agnes-infra-keboola/actions/runs/24731681502) — jinak se state neaplikuje na prod - Změnit heslo admin usera z
1234(http://34.77.102.61:8000/login → profil) - Rotovat Keboola Storage token v Keboola UI →
gcloud secrets versions add keboola-storage-token --data-file=- --project=kids-ai-data-analysis→ restart app containerů na obou VMs (cron to zachytí při dalším tiku nebosudo /usr/local/bin/agnes-auto-upgrade.sh)
Aktualizace průběhu (2026-04-21 pozdně)
Fixy po první migraci
-
Docker named volume → bind mount /data: Po první migraci nové VMs používaly
agnes_dataDocker named volume (uložený na boot disku 30GB), nikoli persistent disk mountovaný na/data(50GB). Fix: vdocker-compose.prod.ymloverride volumedatajako bind mount/data. Commit52d6345. Bumplý taginfra-v1.1.0. -
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.shspouštěným cronem každých 5 min. Detekuje změnu image digest, pokud ano, pullne +docker compose up -d. Commitcbd85c5v modulu, taginfra-v1.1.0. -
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é.
Iterace 2 — finalizace (2026-04-21 večer)
- Workflow commit pushnut — po
gh auth refresh -s workflowprotlačen0ade45c+ merge do main. Per-branch tagging:dev-<slug>v GHCR aktivní. - Dev data zmigrovaná —
data-analyst-dev→ lokál →agnes-dev. DuckDB registry obsahuje 99 tabulek + 1 admin usera. - Module bumpnut na v1.2.0 v Keboola infra repu — README plně v EN, CI spustí čistý plan.
- Backup + monitoring → infra-v1.3.0: daily snapshot schedule na
/datadisku (30d retention), per-VM uptime check + alert policy. Template repo bumpnut na v1.3.0. - Renovate config v template + keboola-infra repu — tracks
infra-v*tagy, otevírá PR při nové verzi. - Staré VMs smazané —
data-analyst,data-analyst-dev, jejich static IP, pre-migration snapshot, migration tar z bucketu. - Temporary IAM grants revokovány —
secretmanager.secretAccessorodebrán z default compute SA (na secrets),storage.objectViewerodebrán zagnes-keboola-vmna tfstate bucket. - Onboarding ONBOARDING.md rozšířen o propagation přes
-replace, backup restore, monitoring setup, race condition fix. - Auth v2 → v3 action bump v obou workflow repech (silences Node 20 deprecation warning).
- Prod apply-dev úspěšně proběhl po manuálním triggeru (initial apply měl race s timing secret creation). apply-prod čeká na reviewera.
Iterace 4 — version badge + workflow-driven recreate
- Version badge v UI:
/api/versionendpoint + footer badge vbase.htmlloadí asynchronně, zobrazuje<channel>-<version> · <tag> · deployed <relative> (<UTC>)s commit SHA v tooltipu. - Module
infra-v1.5.0: startup-script odvozujeAGNES_VERSIONaRELEASE_CHANNELz image tagu (stable-YYYY.MM.N / dev-…) a zjistíAGNES_COMMIT_SHAzdocker pulldigest. Tyto vars jdou do.env→ app čte →/api/versionvrací → badge renderuje. workflow_dispatchsrecreate_targetsvapply.yml(oba repa): manuálně spustitelný workflow input s comma-separated TF resource addresses →-replace=<addr>předánoterraform apply. Řešíignore_changes = [metadata_startup_script]gotchu. Dev targets routed doapply-dev, prod doapply-prod.- Dokumentace propagation přepsána v
docs/ONBOARDING.md— Option A (workflow_dispatch, recommended) vs Option B (local TF), plus explicit DO NOT sekce proti ručnímu SSH zásahu.
Iterace 3 — code review + bootstrap fix + doc sweep
- Code review dispatched přes
superpowers:requesting-code-reviewsubagent. Nálezy: 7 critical, 9 important, 10 minor. - Critical + important fixy →
infra-v1.4.0:- C1 VM SA scoped per-secret (ne project-wide)
- C3
chmod 640na startup log - C4 Fail-fast když
keboola-storage-tokenchybí (odstraněn|| echo "") - C5 Cron auto-upgrade sources
.envproAGNES_TAG - C7
depends_onna IAM bindings + secret version (eliminuje první-boot race) - I1 Firewall split:
:8000conditional na tls_mode; SSH v samostatné rule - I2
firewall_ssh_source_rangesvar (default: IAP tunnel range) - I4
compose_refvar pinuje docker-compose files - I5
acme_emailvar (falls back to seed_admin_email) - I6 Merge order v
dev_instances(user values win over defaults) - I7
|| truena Caddyfile fetch odstraněno
- A test —
/auth/bootstrapbug fix: SEED_ADMIN_EMAIL seeded usera bez hesla, který blokoval bootstrap endpoint. Fix: endpoint je teď disabled jen když existuje user spassword_hash. Seedované passwordless users může endpoint activate (set password + promote to admin). Tests: 8/8 passed. - B test — dry-run onboarding: našel 2 gapy v templatu (module pinoval v1.3.0, tfvars.example měl CZ komentáře). Bumpnuto na v1.4.0, komentáře přeloženy, přidány docs pro nové vars.
- Wait timer na prod GitHub environment v keboola-infra repu odebrán (0 s) — reviewer-only gate.
- Node 20 deprecation warning —
google-github-actions/auth@v2 → v3; prohashicorp/setup-terraform@v3(stále Node 20) přidánFORCE_JAVASCRIPT_ACTIONS_TO_NODE24=trueenv-var, který force Node 24 runtime. - Docs sweep:
docs/DEPLOYMENT.mdpřepsán — rozcestník Terraform (recommended) vs Docker Compose (OSS self-host)docs/ONBOARDING.mdsekce 4 + 6 aktualizované pro v1.4.0 (nové vars, bootstrap semantics)README.mddocs list expandedkeboola/agnes-infra-keboolaREADME bumped + wait-timer note
Finální stav (po iteraci 3)
| Resource | Value |
|---|---|
| Prod VM | agnes-prod @ 34.77.102.61 (e2-small, 50GB /data PD, daily snapshot, uptime check) |
| Dev VM | agnes-dev @ 34.77.94.14 (e2-small, 20GB /data PD, daily snapshot, uptime check) |
| Staré VMs | 🗑️ smazané |
| Image tagy | prod :stable, dev :dev, feature branches :dev-<slug> (aktivní po v1.4) |
| Auto-upgrade | Cron */5 * * * * — reads AGNES_TAG z .env, digest change → restart |
| Prod health | degraded (stale tables), 103 tables, 9.3M rows, 2 users |
| Dev DB | 99 tables v registry, admin user admin@keboola.com |
| Backups | Daily snapshot @ 02:00, 30-day retention (oba data disky) |
| Monitoring | uptime check 60s/10s per VM, alert > 5 min failure (notification channels nenapojené) |
| Firewall | Web 80/443 + 8000 (jen když TLS off); SSH na IAP range only |
| Login prod | zdenek.srotyr@keboola.com / 1234 (pending: user rotate) |
| Login dev | admin@keboola.com / 1234 (pending: user rotate) |
| 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 per-secret) | agnes-keboola-vm@kids-ai-data-analysis.iam.gserviceaccount.com |
| Secrets | keboola-storage-token (manual), agnes-keboola-jwt-secret (TF), jwt-secret-key (legacy) |
| Public upstream repo | https://github.com/keboola/agnes-the-ai-analyst |
| Template repo | https://github.com/keboola/agnes-infra-template (is_template=true, ref infra-v1.4.0) |
| Keboola infra repo | https://github.com/keboola/agnes-infra-keboola (EN README, Renovate, ref infra-v1.4.0) |
| Module tagy | v1.0.0 → v1.1.0 (volume+cron) → v1.2.0 (CI fix) → v1.3.0 (backups+monitoring) → v1.4.0 (review fixes) |
Onboarding druhého zákazníka — kompletní flow
Podle docs/ONBOARDING.md — cíl: < 1 hodina. Klíčové kroky:
bootstrap-gcp.sh <PROJECT_ID>— SA + bucket + klíčgcloud secrets create keboola-storage-token ...(pokud source = keboola)gh repo create <org>/agnes-infra-<cust> --template keboola/agnes-infra-template --private- Upload GCP_SA_KEY do GH secret
- Editovat
terraform/main.tf(backend bucket/prefix) +terraform.tfvars - Vytvořit
dev+prodenvironments přesgh api git push→ CI applyPOST /auth/bootstrapadmin user- 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:
# 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 "<KEBOOLA_TOKEN>" | 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