Skip to main content

Null nedetid.
Full kontroll.

Zero Downtime Deployment

Deployments
99.99% uptime
api-gateway v2.4.1production
2m ago
web-frontend v3.1.0production
14m ago
worker-service v1.8.3staging
28m ago
auth-service v4.0.2production
1h ago
12Pods
<30sRollback
-85%Deploy
Rollback1
Scale pods2
Canary deploy

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.

YtelsesresultaterZDD

Deployment-frekvens

255× raskere
Før1x/mnd
Etter8.5x/dag

Lead time (commit → prod)

160× raskere
Før5 dager
Etter45 min

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

Tech Stack3 technologies
GitLabBackend

Intern kildekode, CI/CD og sikkerhetsskanning

ArgoCDBro

GitOps-synkronisering til Kubernetes

KubernetesFrontend

Container-orkestrering med rolling updates

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

  1. CI (Build): Utvikler pusher kode. Pipeline kjører tester, bygger Docker-image og scanner for sårbarheter.
  2. CD (Update): Hvis bygget er vellykket, oppdaterer CI-jobben versjonstaggen i et separat manifest-repo.
  3. 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:

maxUnavailable: 0 er nøkkelen. Det forteller Kubernetes at det aldri skal være færre pods tilgjengelig enn det som er definert. K8s tvinges til å spinne opp en ny, frisk pod og vente til den er klar (Ready) før den får lov til å terminere en gammel.

maxSurge: 25% lar clusteret bruke ekstra ressurser midlertidig for å spinne opp de nye podene raskt.

deployment.yamlYAML
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: 8080

Graceful 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:

  1. Kubernetes fjerner podens IP fra lastbalansererne.
  2. 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».

Når K8s bestemmer at poden skal dø, starter den IP-fjerning og kjører samtidig preStop-hooken. Denne kommandoen gjør ingenting annet enn å vente i 15 sekunder.

Applikasjonen fortsetter å svare på trafikk, men mottar gradvis mindre etter hvert som lastbalansererne oppdateres. Når sleep er ferdig, sendes SIGTERM, og applikasjonen avslutter kontrollert.

graceful-shutdown.yamlYAML
spec:  containers:  - name: api    lifecycle:      preStop:        exec:          command: ["/bin/sh", "-c", "sleep 15"]    # Graceful shutdown i applikasjonen    terminationGracePeriodSeconds: 30

Probes: 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.

Liveness-proben er bevisst enkel — den sjekker bare om prosessen responderer. Readiness-proben verifiserer at applikasjonen faktisk er klar til å håndtere forespørsler, inkludert database-tilkobling.

initialDelaySeconds gir applikasjonen tid til å starte før K8s begynner å sjekke helse.

probes.yamlYAML
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: 3

Databasen: 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.

Eksempelet viser Expand-Contract-mønsteret i Laravel. Først legger vi til den nye kolonnen, så oppdaterer vi modellen til å håndtere begge, og til slutt fjerner vi den gamle.

expand-contract-migration.phpPHP
// 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

01 / 04Konfigurasjon i miljøet

Aldri hardkod credentials eller miljøspesifikke verdier. Alt injiseres via ConfigMaps og Secrets.

02 / 04Stateless prosesser

Hver pod skal kunne dø og erstattes når som helst. Ingen lokal state på disk.

03 / 04Port binding

Applikasjonen eksponerer seg selv via en port. Ingen avhengighet til ekstern webserver.

04 / 04Disposability

Rask oppstart og graceful shutdown. Håndter SIGTERM korrekt.

Resultater og forretningsverdi

Dette ga konkrete resultater:

Deployments255×
8.5/dag
Før 1/mnd
Lead time160×
45 min
Før 5 dager
Oppetid
99.99%
Før 99.5%
NattarbeidEliminert
0
Før 12 timer

«Deployment-frykten» forsvant. Tirsdag kveld er ikke lenger «vakt-kveld», men frikveld.

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.

Ofte stilte spørsmål

Trenger du DevOps-ekspertise?

La oss hjelpe deg med å bygge en robust, skalerbar infrastruktur. Vårt team spesialiserer seg på Kubernetes, CI/CD og sky-native arkitekturer.