# Canary release

## Hvad det er

En canary release sender en ny version af en tjeneste ud til en lille del af den
rigtige trafik først, mens den version, den skal afløse, bliver ved med at
betjene alle andre. Holder den nye, vokser andelen trin for trin, indtil den
bærer det hele, og den gamle version kan udfases. Holder den ikke, går
trafikken tilbage til den gamle version, som aldrig holdt op med at køre, og kun
den lille andel nåede at mærke fejlen. Danilo Sato beskrev teknikken på Martin
Fowlers bliki i juni 2014 og nævner, at den også kaldes phased rollout eller
incremental rollout. I Kubernetes-verdenen siger man oftere canary deployment,
og på dansk ses canary-udrulning også. Argo Rollouts, en Kubernetes-controller,
der tilføjer udrulningsstrategier af den slags, kalder indstillingen `canary`.

Navnet kommer fra kulminerne. Minearbejderne tog en kanariefugl i et bur med
ned, fordi sivende giftgas ville tage livet af fuglen, før den nåede mændene, og
det var deres varsel om at komme op i tide. Den lille andel trafik spiller samme
rolle: den møder problemet først, mens der endnu er tid til at handle.

Hvem der havner i andelen, er et valg. Sato nævner en tilfældig stikprøve af
brugerne, virksomhedens egne medarbejdere før resten af verden og brugere
udvalgt efter profil eller demografi. Store systemer deler ofte langs
skillelinjer, de allerede har: én region, ét bestemt sted eller ét brand først.
Ifølge Sato brugte Facebook flere canaries, hvor den første kun var synlig for
virksomhedens egne medarbejdere og havde alle feature flags slået til, så
problemer med nye funktioner viste sig tidligt.

Forskellen fra blue-green deployment ligger i, hvordan trafikken flyttes.
Blue-green bygger en komplet ekstra kopi og flytter al trafik i ét skift, mens
den gamle kopi står klar som vejen tilbage. En canary flytter trafikken
gradvist, så to versioner i en periode betjener rigtige brugere side om side. En
rullende opdatering har også to versioner i drift på samme tid, men den skifter
instanser ud, så hurtigt de bliver klar, uden en pause til at vurdere, hvordan
den nye version klarer sig. En canary stopper ved hvert trin og venter på et
svar.

## Hvorfor det betyder noget

Nogle fejl viser sig kun i produktion: under rigtig belastning, med rigtige data
og med den rigtige blanding af klienter. Et stagingmiljø kan komme tæt på, men
bliver aldrig identisk. En canary accepterer det og begrænser i stedet skaden.
Når hver tyvende forespørgsel rammer den nye version, går en dårlig release ud
over hver tyvende forespørgsel, så længe det tager at opdage den, og løsningen
er at sende den andel tilbage.

Sato peger på en anden gevinst: kapacitetstest. Når andelen skrues langsomt op,
kan man måle, hvordan den nye version påvirker produktionsmiljøet under stigende
belastning, med en sikker vej tilbage, i stedet for at bygge et separat miljø
alene til belastningstest. Intet testmiljø kommer tættere på produktion end
produktion selv.

Haken er, at en canary aldrig er bedre end det signal, der bedømmer den. Fejl,
svartider og de forretningstal, der betyder noget, som tilmeldinger, betalinger
eller ordrer, skal kunne ses pr. version og tidligt nok til at standse næste
trin. Uden det er en canary bare en langsom udrulning med ekstra ventetid. Sato
fortæller, at IMVU var pionerer inden for det, der kaldes et »cluster immune
system«: man følger forretningstallene og ruller automatisk en release tilbage
ved en statistisk signifikant regression.

Der er også løbende omkostninger. To eller flere versioner er i drift samtidig,
og alt, hvad de deler (databasen, en cache, en kø, det API, klienterne kalder),
skal fungere med dem begge, så længe det står på. Sato råder til at holde
antallet af samtidige versioner nede på et minimum. Teknikken passer også
dårligt til software, der er installeret på brugernes egne computere eller
telefoner, hvor leverandøren ikke bestemmer, hvornår opgraderingen sker. Dér
foreslår han, at backenden understøtter begge versioner, at man holder øje med,
hvilke klientversioner der stadig er i brug, og at den gamle først fjernes, når
dens andel er faldet tilstrækkeligt.

## Sådan ser det ud i praksis

I sin enkleste form er en canary på Kubernetes to Services, én, der vælger de
stabile pods, og én, der vælger canary-podsene, plus en rute, der vægter
trafikken mellem dem. Med Gateway API ligger vægtene i en HTTPRoute:

```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: web
spec:
  parentRefs:
    - name: prod-web
  hostnames:
    - "www.example.com"
  rules:
    - backendRefs:
        - name: web-stable
          port: 8080
          weight: 95
        - name: web-canary
          port: 8080
          weight: 5
```

Vægte er forholdstal, ikke procenter: hver backend får sin vægt divideret med
summen af vægtene i reglen, så 95 og 5 sender hver tyvende forespørgsel til
canary-versionen. En udeladt vægt tæller som 1, og en vægt på 0 sender
ingenting. Gateway API's vejledning i trafikopdeling viser desuden en regel, der
matcher en request-header, `traffic: test` i eksemplet, og kun sender de
forespørgsler til den nye version, så testere kan nå den, før nogen kunde gør.
At rykke canary-versionen frem er at rette to tal; at trække den tilbage er at
sætte dens vægt til 0.

Argo Rollouts automatiserer trinnene. En `Rollout` træder i stedet for en
`Deployment` med den samme pod-template og angiver de trin, controlleren går
igennem, hver gang templaten ændres:

```yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: web
spec:
  replicas: 10
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: registry.example.com/web:1.4.2
  strategy:
    canary:
      maxSurge: "25%"
      maxUnavailable: 0
      steps:
        - setWeight: 10
        - pause:
            duration: 1h
        - setWeight: 20
        - pause: {}
```

Uden en trafikrouter tilnærmer Argo vægten med antallet af pods: ti replikaer
ved 10 procent giver én canary-pod og ni stabile. Den første pause varer en
time; den anden varer, indtil nogen beslutter sig. En release og dens to mulige
udfald ser sådan ud:

```sh
kubectl argo rollouts set image web web=registry.example.com/web:1.5.0
# ved den åbne pause: enten videre ...
kubectl argo rollouts promote web
# ... eller stop og sæt den stabile version foran alle igen
kubectl argo rollouts abort web
```

Alt i det forløb afhænger af, at man under pausen kan se, om canary-versionen
klarer sig dårligere end den, den afløser. Det er arbejdet bag ydelsen
[Site reliability & overvågning](/da/services/site-reliability/), der handler om
at få besked, mens en fejl stadig er lille. Den
[hjemmebyggede fejlovervågning og tracing](/da/portfolio/built-first-party-error-monitoring-and-tracing/)
fra 2025 viser, hvordan sådan et signal ser ud: fejl samles i tilbagevendende
problemer frem for en flad liste, og spikes og regressioner opdages med en
cooldown, så én dårlig udrulning ikke får nogen kaldt ud fyrre gange. Det er
præcis, hvad et canary-trin har brug for: en regression, der melder sig selv
inden for pausen, og ikke én, en bruger falder over næste morgen.

## Hvor det går galt

**Canary-versionen er for lille til at sige noget.** Én pod ud af ti i et stille
tidsrum betjener måske nogle hundrede forespørgsler i løbet af sin pause, og en
fejl, der rammer hver tusinde forespørgsel, viser sig aldrig. Uret er det
forkerte mål; antallet af forespørgsler, canary-versionen har betjent, er det
rigtige. Afstem andel og pause, så en sjælden fejl får chancen for at dukke op
flere gange, og hold et trin længere om natten end ved middagstid.

**Dashboardene kan ikke skelne canary fra stabil.** En canary-version, hvis
fejlrate ligger to procentpoint over den stabile versions, lægger ved en andel
på 5 procent kun cirka én fejl pr. tusind forespørgsler oven i den samlede
fejlrate. På et dashboard med én fejlrate for hele tjenesten er det usynligt.
Giv canary-pods og stabile pods hver sin label, som Argo Rollouts tilføjer via
`canaryMetadata` og `stableMetadata`, tag labelen med over i målingerne, og
sammenlign canary-versionen med den stabile i stedet for med en fast grænse. En
`analysis`, der kører i baggrunden på en Rollout, afbryder opdateringen, hvis
den fejler.

**Begge versioner skriver til de samme data.** Så længe udrulningen står på,
læser og skriver gammel og ny kode i den samme database, og med tilfældig
fordeling kan én brugers forespørgsler ramme begge inden for et minut. En ny
kolonne, som den gamle version aldrig udfylder, eller et format, den ikke kan
læse, bliver til fejl på den stabile side, altså den del, der skulle være
sikker. Satos svar er parallel change: databasen understøtter begge versioner af
applikationen under hele udrulningen, og den gamle struktur fjernes først, når
den gamle version er væk.

**En abort stopper trafikken, ikke ændringen.** `kubectl argo rollouts abort`
sender trafikken tilbage til det stabile ReplicaSet, men specifikationen i
`Rollout`-objektet peger stadig på det nye image, og hvis objektet forlader den
afbrudte tilstand, går det efter den version igen. Tilbagerulningen er først
fuldført, når templaten er sat tilbage til den forrige version. Ligger
specifikationen i et repository, skal rettelsen ske dér, så kilden og clusteret
er enige om, hvad der skal køre.

**Canary-versionen bliver også brugt som A/B-test.** Routingen er den samme, så
det er fristende at måle en ny funktions effekt på konverteringen med
canary-trafikken. Sato råder til at holde de to adskilt: en canary jager
problemer og bør være færdig på minutter eller timer, mens et eksperiment tester
en hypotese og kan kræve flere dages data for at blive statistisk signifikant.
Bedømmes canary-versionen på forretningstal, slører den effekt, eksperimentet
skal fremkalde, også dommen. Udgiv med en canary, og kør derefter eksperimentet
bag en opdeling for sig, for eksempel et feature flag, når versionen er ude.

## Læs mere

- [Canary Release](https://martinfowler.com/bliki/CanaryRelease.html)
  — Danilo Satos beskrivelse fra 2014 på Martin Fowlers bliki: valg af brugere,
  kapacitetstest, A/B-test og databasen.
- [Argo Rollouts: Canary](https://argo-rollouts.readthedocs.io/en/stable/features/canary/)
  — trin og pauser, vægte tilnærmet med antallet af pods og skalering med en
  trafikrouter.
- [HTTP traffic splitting](https://gateway-api.sigs.k8s.io/guides/user-guides/traffic-splitting/)
  — Gateway API's vejledning i vægtede backends og en canary-rute, der matcher
  på en header.
- [kubectl argo rollouts abort](https://argo-rollouts.readthedocs.io/en/stable/generated/kubectl-argo-rollouts/kubectl-argo-rollouts_abort/)
  — hvad en abort gør ved trafikken, og hvad den efterlader i specifikationen.

<https://engineer.company/da/glossary/canary-release/>
