# CI

## Hvad det er

CI står for continuous integration, på dansk ofte kaldt kontinuerlig
integration: en arbejdsform, hvor alle på et team lægger deres ændringer ind på
én fælles hovedlinje mange gange om ugen, og en maskine bygger og tester
resultatet hver gang, så en fejl dukker op, mens den, der lavede den, stadig
husker hvorfor.

Ordet bruges om to forskellige ting, og det sparer mange misforståelser at
holde dem adskilt. Den første er praksissen, og den handler om mennesker. Martin
Fowlers artikel om emnet, som de fleste teams ender med at citere, beskriver den
som en række vaner: hold én hovedlinje, automatisér buildet, lad buildet teste
sig selv, skub til hovedlinjen mindst én gang om dagen, byg hvert skub, ret et
brudt build med det samme, hold buildet hurtigt, skjul halvfærdigt arbejde i
stedet for at parkere det på en gren, og lad alle se, hvordan buildet står.
Artiklen fører navnet tilbage til Extreme Programming og nævner, at Grady Booch
brugte udtrykket før det.

Den anden betydning er maskinen. Når nogen siger »CI'en er rød«, mener de den
tjeneste, der kørte buildet og meldte en fejl. GitHub Actions, GitLab CI/CD og
deres slægtninge har samme grundform. En fil i repositoriet beskriver arbejdet:
`.github/workflows/*.yml` hos GitHub, `.gitlab-ci.yml` i roden hos GitLab. En
hændelse sætter det i gang, for eksempel et push, en pull- eller merge-request,
en tidsplan eller et tryk på en knap. En runner, altså en maskine sat af til
formålet, henter commit'et, kører kommandoerne fra filen og melder bestået eller
fejlet tilbage på commit'et.

En server alene er ikke continuous integration. Et team, der holder arbejdet på
grene i en måned og lader en server bygge hver gren, har automatiseret sine
builds, og det er nyttigt, men selve integrationen sker stadig på én gang til
sidst. Praksissen er den hyppige sammenfletning; maskinen er det, der gør den
sikker nok til, at man bliver ved.

Continuous delivery og continuous deployment er trinene bagefter. Delivery
holder hvert grønt build klar til udgivelse, og deployment udgiver hvert af dem
automatisk. Begge forudsætter, at CI er på plads, og derfor skrives de tre så
tit sammen som CI/CD.

## Hvorfor det betyder noget

Prisen for at flette to arbejdslinjer sammen vokser hurtigere end tiden, de har
levet hver for sig. To udviklere, der begge ændrede det samme modul i går, kan
løse konflikten på få minutter, fordi de husker, hvad de mente. To, der skiltes
for seks uger siden, sidder og genskaber hensigter ud fra diffs. Små, hyppige
sammenfletninger holder hver konflikt lille, og det er det meste af gevinsten,
før nogen maskine overhovedet er med.

Buildet ved hvert push lægger en ting til: en ren maskine, der er enig. »Det
virker på min bærbare« er en påstand om én bærbar med sine egne installerede
biblioteker og efterladte filer. En runner, der starter fra et frisk checkout
hver gang, gentager resultatet eller viser, hvor den bærbare var speciel.

Værdien ligger i hastigheden. Fowlers artikel sætter et mål på omkring ti
minutter for hovedbuildet, og begrundelsen holder i praksis. En fejl, der meldes
ti minutter efter et push, går tilbage til den, der lavede den, mens ændringen
stadig står på skærmen. En fejl, der meldes næste morgen, lander hos den, der
tilfældigvis kigger, og koster en times udgravning først.

Synlighed tæller også. Når hovedlinjens tilstand er synlig for alle, begynder
ingen på nyt arbejde oven på et brudt build uden at vide det, og at rette
buildet bliver teamets første opgave frem for en andens problem.

CI behøver heller ikke betyde en hostet tjeneste. pre-commit kører et projekts
tjek som Git-hooks før hvert commit eller push, og dokumentationen viser det
samme sæt køre på en server med `pre-commit run --all-files`. Task giver hvert
tjek et navn i én `Taskfile.yml`, så et menneske og en runner kalder den samme
kommando. Dette sites egne tjek kører netop sådan: på den maskine, sitet skrives
på, med et fuldt tjek før hvert push og uden en hostet build-tjeneste imellem.

## Sådan ser det ud i praksis

Udgivelser hvilede på manuelle trin, som hver person udførte en smule
forskelligt, indtil
[automatiserede build-, test- og udrulningspipelines afløste dem](/da/portfolio/streamlined-ci-cd-processes-saving-4-000-hours-42/),
så vejen fra en ændring til produktion blev standardiseret i stedet for
improviseret. Siden gør den sparede tid op til omkring 4.000 timer, og
udgivelserne blev både hurtigere og mere pålidelige.

På et projekt med en langtlevende database er skemaet beskrevet to gange: én
gang af den initielle opsætning og én gang af migrationerne, som tæller 1.022,
fra nummer 036 til nummer 1102.
[Et CI-job bygger databasen begge veje ved hver ændring](/da/portfolio/kept-the-schema-honest-across-1022-migrations-99/),
én gang fra det friske skema og én gang med alle migrationer afspillet i
rækkefølge, og sammenligner både struktur og seedede data. Enhver uenighed får
buildet til at fejle med forskellen printet ud, så et friskt miljø og et
langtlevende er den samme database, fordi det bliver tjekket, ikke antaget.

CI kan også holde to halvdele af en applikation enige. Ét sted genereres
API-beskrivelsen og frontendens klient i en kæde, der starter ved databasen og
ender i en typet TypeScript-klient på 44.076 linjer.
[Et kontrakt-tjek i CI sammenligner det, frontenden sender, med det, API'et forventer](/da/portfolio/built-a-request-schema-validation-contract-with-automated-61/),
og får buildet til at fejle ved afvigelse. Resultatet, siden melder, er netop
det, CI findes for: et felt, der ændres på kun den ene side, fejler i et build,
minutter efter ændringen, i stedet for at nå en bruger som en fejl i browseren.

## Hvor det går galt

De fleste CI-opsætninger forfalder snarere end at gå i stykker. Hvert af disse
mønstre begynder småt og ender med et build, teamet har lært at arbejde udenom.

**Buildet tager længere tid end arbejdet.** Når et push er fyrre minutter om at
melde tilbage, holder folk op med at vente, samler flere ændringer i ét push og
pusher sjældnere, hvilket er det modsatte af praksissen. Mål, hvor tiden går,
før der købes mere hardware. Cache afhængighederne, kør de hurtige tjek først,
og flyt de langsomme suiter til et senere trin eller en tidsplan, så
hovedbuildet holder sig omkring ti minutter.

**Hovedlinjen står rød en hel dag.** Et brudt build, som ingen retter, bliver
normaltilstanden, og en ny fejl gemmer sig bag den gamle. Reglen, der forhindrer
det, er enkel: den, der brød det, retter det nu eller ruller ændringen tilbage
og retter den i ro. Et grønt build er det, alle andre bygger videre på.

**Grene lever i ugevis, mens serveren bygger hver af dem.** Hver gren lyser
grønt, og problemerne kommer samlet den dag, de flettes. Det er automatiseret
bygning uden integrationen. Flet ind i hovedlinjen mindst dagligt, og hold
halvfærdige funktioner slået fra i stedet for at holde dem adskilt.

**Serveren tjekker én liste og den bærbare en anden.** To lister med kommandoer,
der vedligeholdes i hånden, glider fra hinanden. Så består en ændring lokalt og
fejler på serveren, eller omvendt, og ingen stoler på nogen af dem. Giv tjekkene
ét indgangspunkt, og kald det fra begge steder. Med pre-commit kan serveren køre
præcis de hooks, hver udvikler kører:

```yaml
# .gitlab-ci.yml
check:
    image: python:3.13
    script:
        - pip install pre-commit
        - pre-commit run --all-files --show-diff-on-failure
```

**En ustabil test lærer alle at trykke på »kør igen«.** En test, der fejler én
gang ud af tyve af grunde, som intet har med ændringen at gøre, træner teamet i
at genstarte frem for at læse. Bagefter får en ægte fejl samme behandling. Sæt
testen i karantæne, ret den eller slet den, og betragt en genstartsknap, der
bliver trykket på tit, som et fund i sig selv.

## Læs mere

- [Continuous Integration](https://martinfowler.com/articles/continuousIntegration.html)
  — Martin Fowlers lange artikel: hver praksis, buildet på ti minutter, og hvor
  navnet kom fra.
- [Understanding GitHub Actions](https://docs.github.com/en/actions/get-started/understand-github-actions)
  — workflows, hændelser, jobs og runners, som GitHub selv definerer dem.
- [GitLab CI/CD](https://docs.gitlab.com/ci/) — filen `.gitlab-ci.yml`,
  pipelines, stages, jobs og runners med GitLabs egne ord.
- [pre-commit](https://pre-commit.com/) — tjek erklæret én gang i en
  konfigurationsfil, kørt som Git-hooks og igen som et trin på en CI-server.
- [Task](https://taskfile.dev/) — en `Taskfile.yml`, der giver hvert tjek ét
  navn, så et menneske og en runner skriver den samme kommando.

<https://engineer.company/da/glossary/ci/>
