Sammendrag
Hvordan oppnår man 100 % oppetid når koden endres daglig? Denne artikkelen tar for seg arkitekturen som trengs for å unngå nedetid i kritiske systemer. Kunden i denne case-studien ønsket å forbli anonym, og omtales derfor som «NordFinans».
Vi går i dybden på det tekniske: Kubernetes-konfigurasjon, databasemigrering og applikasjonslogikk. Prinsippene gjelder uavhengig av stack — PHP, Python, Go eller Node.js.
Deployment-frekvens
Lead time (commit → prod)
Bakgrunn: Frykten for deployering
Den gamle verden
Mange bedrifter kjenner seg igjen i situasjonen NordFinans stod i: En massiv monolittisk applikasjon som hadde vokst seg uhåndterlig. Utrullingene var manuelle, trege og risikable:
Vedlikeholdsvinduer: Oppdateringer krevde planlagt nedetid, gjerne sent på kvelden. Brukerne forventet døgnåpne tjenester, og utviklerne ble slitne av nattarbeid.
«Deployment-frykt»: Fordi hver utrulling var stor og skummel, ble de utsatt så lenge som mulig. Det førte til en ond sirkel: enorme kodekonflikter og større sjanse for feil.
Tungvint skalering: Under trafikktopper måtte hele monolitten skaleres opp, selv om bare en liten del av systemet var under press.
Målsettingen
Selskapet satte tre ufravikelige krav:
| Krav | Mål |
|---|---|
| Deployment Frequency | Fra månedlige til daglige utrullinger |
| Change Failure Rate | Under 1 % feilrate ved utrulling |
| True Zero Downtime | Ingen avbrutte sesjoner eller 5xx-feil |
Strategi: Hybrid CI/CD med GitLab og GitHub
For å holde intern kode sikker, men offentlige integrasjoner tilgjengelige, valgte vi en hybrid strategi. Modellen passer godt for selskaper som har både lukket kildekode og åpne SDK-er.
GitLab: Kjernen for DevSecOps
GitLab (Self-Managed) ble valgt som primær plattform for intern kildekode og infrastruktur.
Hvorfor: GitLab gir alt i én pakke — kildekode, CI-pipelines, container registry og sikkerhetsscanning (SAST/DAST) — i et lukket økosystem.
Kubernetes-integrasjon: Via GitLab Agent kan plattform-teamet styre tilganger detaljert uten å eksponere sensitive nøkler til utviklere.
GitHub: Ansiktet utad
Offentlige SDK-er og partner-integrasjoner lever på GitHub.
Hvorfor: GitHub er standarden for open-source.
GitHub Actions: Actions kjører offentlige tester og publiserer pakker til registre som NPM, PyPI og Packagist.
Synkronisering
For å unngå oppsplitting fungerer GitLab som «Source of Truth». Kode speiles automatisk til GitHub, slik at utviklerne bare forholder seg til ett dashboard — mens koden lever to steder.
CI/CD-arkitektur
GitOps: Motoren under panseret
For å oppnå null nedetid må man fjerne manuelle feilkilder. Manuell kjøring av kubectl apply ble derfor strengt forbudt til fordel for en ren GitOps-modell.
ArgoCD som trafikkpoliti
ArgoCD synkroniserer tilstanden i Git med tilstanden i Kubernetes-clusteret.
Pull-basert modell: I stedet for at CI-serveren «pusher» endringer til clusteret (noe som krever admin-tilgang til prod), «puller» ArgoCD endringer fra et eget manifest-repo.
Sikkerhet: CI-systemet har aldri direkte tilgang til produksjonsmiljøet. Det fjerner en stor angrepsflate.
Flyten fra kode til prod
- CI (Build): Utvikler pusher kode. Pipeline kjører tester, bygger Docker-image og scanner for sårbarheter.
- CD (Update): Hvis bygget er vellykket, oppdaterer CI-jobben versjonstaggen i et separat manifest-repo.
- Sync: ArgoCD oppdager endringen, beregner differansen, og ruller ut endringen kontrollert i Kubernetes.
Teknisk dypdykk: Hvordan oppnå 100% oppetid?
Å bytte ut motoren på et fly mens det er i luften krever presisjon. Her er konfigurasjonene som gjør det mulig å rulle ut nye versjoner midt i arbeidstiden uten tapte forespørsler.
Rolling Update-strategien
Standardoppførselen til Kubernetes er «Rolling Update», men standardinnstillingene er ofte for aggressive for kritiske applikasjoner. Strategien må justeres for å sikre kapasitet:
apiVersion: apps/v1kind: Deploymentmetadata: name: api-serverspec: replicas: 4 strategy: type: RollingUpdate rollingUpdate: maxSurge: 25% maxUnavailable: 0 template: spec: containers: - name: api image: registry/api:v2.1.0 ports: - containerPort: 8080Graceful Shutdown: Løsningen på 502 Bad Gateway
Den vanligste feilen ved overgang til Kubernetes er å ignorere applikasjonens livssyklus. Når en pod skal dø, skjer to ting samtidig:
- Kubernetes fjerner podens IP fra lastbalansererne.
- Kubernetes sender SIGTERM til containeren for å stoppe prosessen.
Problemet: Prosesser som Nginx, Go-binærer eller Node.js stopper ofte raskere enn det tar å oppdatere nettverksreglene i clusteret. Resultatet? Trafikk sendes til en pod som nettopp har dødd. Brukeren ser «502 Bad Gateway».
spec: containers: - name: api lifecycle: preStop: exec: command: ["/bin/sh", "-c", "sleep 15"] # Graceful shutdown i applikasjonen terminationGracePeriodSeconds: 30Probes: Helsekontrollens kunst
Liveness Probe: «Er jeg i live?». Sjekker om prosessen kjører. Skal være enkel — ikke sjekk database-tilkobling her! Hvis databasen går ned, vil alle podene restarte samtidig i en evig loop.
Readiness Probe: «Er jeg klar for trafikk?». Sjekk om applikasjonen faktisk kan gjøre jobb (f.eks. db-kobling ok, cache varm). Hvis denne feiler, tas poden ut av trafikkflyten uten å restartes.
spec: containers: - name: api livenessProbe: httpGet: path: /health/live port: 8080 initialDelaySeconds: 10 periodSeconds: 10 readinessProbe: httpGet: path: /health/ready port: 8080 initialDelaySeconds: 5 periodSeconds: 5 failureThreshold: 3Databasen: Den største utfordringen
Kode er flyktig, men data er varige. Hvordan oppdaterer man et databaseskjema uten å låse tabeller eller krasje den gamle versjonen av koden som fortsatt kjører under en utrulling?
Løsningen er mønsteret Expand-Contract (Parallel Change).
Fase 1: Expand (Utvid)
Skal vi endre navn på en kolonne fra address til billing_address? Vi legger til den nye kolonnen, men beholder den gamle. Vi ruller ut koden. Nå eksisterer begge kolonnene.
Fase 2: Migrate (Dual Write)
Applikasjonen oppdateres til å skrive til begge kolonner, men lese fra den nye. Et bakgrunnsskript flytter gamle data.
Fase 3: Contract (Trekk sammen)
Når vi er sikre på at alle pods kjører ny kode som bruker billing_address, fjerner vi den gamle kolonnen i en siste migrering.
// Migration 1: Expand - Legg til ny kolonneSchema::table('customers', function (Blueprint $table) { $table->string('billing_address')->nullable();}); // Model: Dual write under overgangsperiodenclass Customer extends Model{ public function setAddressAttribute($value) { $this->attributes['address'] = $value; $this->attributes['billing_address'] = $value; } public function getAddressAttribute() { return $this->billing_address ?? $this->attributes['address']; }} // Migration 2: Contract - Fjern gammel kolonneSchema::table('customers', function (Blueprint $table) { $table->dropColumn('address');});Dette krever disiplin, men sikrer at databasen aldri er ute av synk med noen versjon av applikasjonen som kjører live.
Applikasjonsnivå: Klargjøring for skyen
Uavhengig av språk krever et «Zero Downtime»-miljø at applikasjonen følger 12-factor-prinsippene.
Konfigurasjon og miljøvariabler
I et dynamisk cluster kan man ikke stole på .env-filer på disk. All konfigurasjon må injiseres som miljøvariabler fra Kubernetes ConfigMaps og Secrets.
- Node.js/Python/Go: Leser direkte fra miljøet (
process.env/os.environ). - PHP: Pass på at konfigurasjon som caches under bygging ikke inneholder hardkodede stier som endrer seg i produksjon.
Kø-systemer og serialisering
En ofte oversett felle er asynkrone jobber (RabbitMQ, Redis, Kafka). Når en ny versjon deployes, kan det ligge jobber i køen som er serialisert med den gamle kodestrukturen. Plukker en worker med ny kode opp en gammel jobb-payload, kan applikasjonen krasje.
Løsning: Bruk versjonerte køer, eller sørg for at job-payloads alltid er bakoverkompatible. Ved store endringer må køen tømmes før oppdatering.
12-Factor App Prinsipper for ZDD
Aldri hardkod credentials eller miljøspesifikke verdier. Alt injiseres via ConfigMaps og Secrets.
Hver pod skal kunne dø og erstattes når som helst. Ingen lokal state på disk.
Applikasjonen eksponerer seg selv via en port. Ingen avhengighet til ekstern webserver.
Rask oppstart og graceful shutdown. Håndter SIGTERM korrekt.
Resultater og forretningsverdi
Dette ga konkrete resultater:
Deployment-frekvens: Fra månedlig til 8.5 ganger per dag. Utviklere deployer nå små endringer kontinuerlig.
Lead time: Fra 5 dager til 45 minutter — fra commit til produksjon.
Tilgjengelighet: 99.99 % oppetid det første året, selv gjennom store omskrivinger.
Kultur: «Deployment-frykten» forsvant. Tirsdag kveld er ikke lenger «vakt-kveld», men frikveld.
Konklusjon
Null nedetid er ikke magi, og det er heller ikke noe man får «gratis» bare ved å velge Kubernetes. Det er resultatet av en gjennomtenkt arkitektur som kombinerer solide CI/CD-pipelines, deklarativ infrastruktur (GitOps) og god forståelse av applikasjonens livssyklus.
For bedrifter som må levere 24/7, er dette ikke bare en IT-investering — det er et konkurransefortrinn.
Hos PXL hjelper vi selskaper med å forbedre deployment-flyten sin. Vi har bred erfaring med å sette opp skalerbare, feiltolerante miljøer for applikasjoner bygget i alt fra PHP og Python til Go og Node.js — fra containerisering og CI/CD-design til Kubernetes-drift og overvåkning.