19796bec70
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
149 lines
8.5 KiB
Markdown
149 lines
8.5 KiB
Markdown
# Guidad Felsökning – Drift i Kubernetes (helt självhostat)
|
||
|
||
Målarkitekturen ur [Master Prompt](MASTER-PROMPT.md) 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
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
T[Tekniker] -->|HTTPS| I[Ingress + TLS]
|
||
T2[Kund via delningslänk] -->|HTTPS| I
|
||
I -->|/| W[web\n2–10 pods, HPA]
|
||
I -->|/api/ai| A[ai-orkester\n2–10 pods, HPA]
|
||
I -->|/api, /halsa| P[plattform\n2–10 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](../infra/terraform/README.md).
|
||
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.
|
||
|
||
```sh
|
||
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:
|
||
|
||
```sh
|
||
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** 2–10 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.
|
||
|
||
```mermaid
|
||
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.
|
||
|