Files
alva/docs/DRIFT.md
T
Claude 19796bec70 Terraform blir enda vägen, och backup ett obligatoriskt val
Kustomize- och Argo CD-vägen är borttagen. Den beskrev samma system en
gång till och kunde inte köras samtidigt som Terraform utan att de
motarbetade varandra — selfHeal återställde det Terraform ändrade och
prune tog bort det Terraform skapade. Kvar utanför Terraform är bara
postgres-init.sql, som läses av både definitionen och integrationstestet
så att schemat inte kan glida isär från det som testas.

Säkerhetskopieringen var en förhoppning: en StatefulSet med en volym och
ingen kopia. Går volymen förlorad är det inte "data" som försvinner utan
varje ärendes bevisvärde — vad som kontrollerades, av vem, när, med
vilken evidens — och det går inte att återskapa i efterhand.

databas_lage är därför ett obligatoriskt val utan standardvärde:

  extern    managerad Postgres, leverantörens backup och PITR
            (rekommenderat i produktion)
  cnpg      CloudNativePG i klustret: basbackup 02:30, kontinuerlig
            WAL-arkivering till objektlagring, PITR och failover
  inbyggd   en volym, ingen backup — spärras av en precondition när
            miljön är produktion

Preconditions fångar felkonfiguration vid plan i stället för vid drift:
extern utan anslutning, cnpg utan backupmål eller nycklar, inbyggd i
produktion.

Driftsättningen är nu två åtskilda flöden. Publicera bygger och taggar
bilderna vid varje main-push; Driftsätt startas för hand med en tagg mot
en GitHub-miljö som kan kräva godkännande, kör fmt/init/validate/plan/
apply, skriver ut kartan och rökkontrollerar hälsa och API-spec. En bild
i registret är inte samma sak som en bild som kör. Rollback är att köra
Driftsätt igen med en tidigare tagg. CI kör dessutom terraform validate
på varje PR — den kontroll jag inte kunde köra själv.

Två fel hittade vid egengranskning av definitionen: schemafilen delades
på semikolon, vilket hade klippt itu plpgsql-funktionen med
append-only-triggern (nu hela filen via postInitApplicationSQLRefs), och
null-satta fält i kubernetes_manifest utelämnas nu i stället.

Verifierat: 87 vitest-tester, typkontroll, eslint, OpenAPI-validering,
terraform fmt, statisk referenskontroll av modulen och integrationstest
mot riktig Postgres. terraform validate kunde inte köras här —
registry.terraform.io är blockerad av sessionens egress-policy, därav
CI-jobbet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
2026-08-03 22:16:52 +00:00

8.5 KiB
Raw Blame History

Guidad Felsökning Drift i Kubernetes (helt självhostat)

Målarkitekturen ur Master Prompt som kod: hela stacken körbar i eget kluster — webb, AI-orkester, plattformsbackend (auth + händelse-API + Live Share) och Postgres. Inga externa tjänstberoenden utöver Anthropic-API:et för AI-anropen.

Arkitektur

flowchart LR
    T[Tekniker] -->|HTTPS| I[Ingress + TLS]
    T2[Kund via delningslänk] -->|HTTPS| I
    I -->|/| W[web\n210 pods, HPA]
    I -->|/api/ai| A[ai-orkester\n210 pods, HPA]
    I -->|/api, /halsa| P[plattform\n210 pods, HPA]
    A -->|Claude API| C[(Anthropic)]
    P --> DB[(Postgres\nStatefulSet + PVC)]
    K[Secret: felsokning-hemligheter] --> A & P & DB
Komponent Vad Var
web SPA:n bakom oprivilegierad nginx (Dockerfile, docker/nginx.conf) Deployment + Service + HPA + PDB
plattform Självhostad backend (services/plattform): multi-tenant — registrering skapar organisation + systemadministratör, admin hanterar användare (tekniker/arbetsledare/admin), all ärendedata organisationsisolerad. Inloggning (bcrypt via pgcrypto, HS256-JWT med roll + org i anspråken), append-only händelse-API, publik delningsendpoint Deployment + Service + HPA + PDB
ai-orkester AI-orkestern (services/ai-orkester): fyra uppgifter routade till Sonnet 5 / Opus 5 / Haiku 4.5 — verifierar plattformens JWT (delad hemlighet) Deployment + Service + HPA + PDB
postgres Händelselogg + användare; append-only garanterat med databastriggers — historik kan inte ändras eller raderas oavsett roll Tre lägen: extern managerad Postgres (rekommenderat), CloudNativePG i klustret, eller en enkel StatefulSet utan backup för prov
Hemligheter anthropic-api-key, jwt-secret (delas av plattform + orkester), postgres-losenord, integration-nyckel (krypterar kundernas märkesspecifika credentials)
Miljöflaggor TILLATNA_URSPRUNG (CORS-lista; utelämnad = *), TILLAT_INTERNA_UPPSLAG (true tillåter leverantörsuppslag mot privata nät), REGISTRERING_OPPEN, ECM_REGLER_FIL, INTEGRATIONER_FIL Secret felsokning-hemligheter — aldrig i bilder eller manifest

Klienten har två driftlägen, valda vid bygget: med VITE_PLATTFORM_URL går inloggning, synk, Live Share och AI mot klustret (helt självhostat); utan den används Supabase-läget (edge-funktion + managerad Postgres/Auth) som tidigare. Samma händelsemodell, samma orkester — låst av paritetstester.

Infrastrukturen som kod

infra/terraform är systemets definition — läs README:n där. Börja i karta.tf: hela systemet beskrivet en gång som data (tjänster, portar, routing, hemligheter, dataflöden, gränser). terraform output karta skriver ut samma sak i klartext.

Definitionen omfattar hemligheter, databasschema, nätverksgränser och alla tre databaslägena. Kustomize- och Argo CD-vägen är borttagen — infra/postgres-init.sql är det enda som blivit kvar utanför Terraform, och den läses av både Terraform och integrationstestet så att schemat inte kan glida isär från det som testas.

Nätverksgränser

Terraform-vägen stänger namnrymden och öppnar bara de faktiska flödena:

Från Till Varför
ingress-kontrollern web, plattform, ai-orkester :8080 den enda vägen in
plattform postgres :5432 händelseloggen
plattform internet :443 utom privata nät kundernas leverantörer
ai-orkester internet :443 utom privata nät Claude
web, postgres ringer ingenting

Undantagen för privata nät (10/8, 172.16/12, 192.168/16, 169.254/16, 127/8, 100.64/10) är samma gräns som koden själv upprätthåller i pekarInat — två oberoende spärrar mot att ett kundkonfigurerat uppslag används för att nå klustrets insida eller molnets metadatatjänst. Kräver en CNI som tillämpar NetworkPolicy; annars är reglerna dokumentation, inte skydd.

Driftsätta

Allt går genom Terraform — hemligheter, schema och nätverksgränser ingår. Det finns ingen kubectl apply att komma ihåg.

cd infra/terraform
cp terraform.tfvars.exempel terraform.tfvars   # domän, register, databasläge, nycklar
terraform init
terraform plan
terraform apply -var bildtagg=<git-sha>

terraform output karta       # hela systemet i klartext
terraform output endpoints   # adresser att kontrollera

Sedan:

curl https://app.exempel.se/halsa            # → {"status":"ok"}
curl https://app.exempel.se/api/openapi.yaml # hela API-specen

Klustret behöver: en CNI som tillämpar NetworkPolicy, ingress-nginx, cert-manager, en metrics-server och en StorageClass med ReadWriteOnce.

Att skapa nya organisationer är stängt som standard (registrering_oppen = false); användare inom en organisation skapas alltid av dess systemadministratör.

Databasen: valet som avgör om det finns backup

databas_lage saknar standardvärde med flit.

Läge Backup Failover Använd när
extern Leverantörens, med PITR Leverantörens Produktion. Cloud SQL, RDS, Neon, Azure
cnpg Basbackup 02:30 + WAL-arkiv → objektlagring, PITR Ja Produktion när databasen måste ligga i klustret
inbyggd Ingen Nej Prov och demo — spärras när miljo = "produktion"

Går händelseloggen förlorad är det inte "data" som försvinner utan varje ärendes bevisvärde: vad som kontrollerades, av vem, när, med vilken evidens. Det går inte att återskapa i efterhand.

cnpg kräver CloudNativePG-operatorn installerad först — Terraform slår upp dess CRD redan vid plan. I extern läge kör ni infra/postgres-init.sql mot databasen själva; det är samma fil som integrationstestet kör.

Multi-tenant och roller

Enligt Master Prompt: varje kund är en egen tenant, ingen data blandas mellan kunder.

  • Registrering skapar organisationen och gör användaren till systemadministratör.
  • Admin skapar användare (tekniker/arbetsledare/admin) i sin organisation — via UI:t eller POST /api/anvandare.
  • All ärendedata är organisationsknuten: ärenden skapas i användarens organisation och händelse-API:t verifierar organisationstillhörighet på varje anrop — en annan organisations ärenden ger 404.
  • Rollen ligger i JWT:n och verifieras på servern; klienten anpassar bara UI:t.

Integrationstestet (services/plattform/integrationstest.sh, körs även i CI mot riktig Postgres) verifierar hela kedjan: registrering, synk, idempotens, append-only-triggern, organisationsisolering, delningsfiltrering och rollstyrning.

Säkerhet och robusthet

  • Append-only i tre lager: klienten lägger bara till, API:t exponerar inga update/delete, och databastriggers avvisar ändringar även för en felkonfigurerad roll.
  • JWT-flödet är verifierat tvärs tjänsterna: plattformen signerar, orkestern verifierar samma hemlighet; fel hemlighet och utgångna tokens avvisas (testat).
  • Alla containrar kör non-root utan capabilities; backend-tjänsterna med read-only rotfilsystem. Båda failar closed utan sina hemligheter.
  • HPA 210 pods per tjänst på 70 % CPU; PDB minst en pod uppe vid noddränering; readiness/liveness-prober överallt (pg_isready för Postgres).

CI/CD med GitOps

CI (.github/workflows/ci.yml): tester, produktionsbygge, integrationstest mot riktig Postgres och verifierande containerbyggen på varje push/PR.

CD — två flöden, medvetet åtskilda: en bild i registret är inte samma sak som en bild som kör.

flowchart LR
    P[Push till main] --> B[Publicera:\nbygger 3 bilder\ntaggade med git-SHA] --> G[(GHCR)]
    G -.-> D[Driftsätt:\nstartas för hand\nmed en tagg]
    D --> M[miljö: produktion\ngodkännande] --> T[terraform apply] --> K[Klustret]
    T --> R[Rökkontroll\nhälsa + API-spec]
  1. Publicera vid varje main-push: bygger de tre bilderna och taggar med git-SHA:t (GITHUB_TOKEN, inga externa hemligheter).
  2. Driftsätt startas för hand med en tagg, mot GitHub-miljön produktion som kan kräva godkännande. Kör fmt, init, validate, plan, apply, skriver ut kartan och rökkontrollerar hälsa och API-spec. bara_plan visar planen utan att applicera.
  3. Rollback = kör Driftsätt igen med en tidigare tagg.

Kustomize- och Argo CD-vägen är borttagen. Den beskrev samma system en gång till och kunde inte köras samtidigt som Terraform utan att de motarbetade varandra — selfHeal återställde det Terraform ändrade och prune tog bort det Terraform skapade.