En mislykket genudgivelse sletter aldrig en fungerende backend
Den 25. september slettede en mislykket genudgivelse en apps backend, som havde været live og fungerende i omkring to et halvt timer. Koden, der fortryder en mislykket udgivelse, fjernede hver eneste serverapp, udgivelsen havde berørt, også dem der allerede kørte, før den startede. Nu slettes kun apps, som en udgivelse har oprettet. En app, der allerede fandtes, bliver sat tilbage på præcis den version, den kørte før. To opfølgende rettelser samme dag sikrede, at fortrydelsen også kører, når hostingplatformens eget deploy-værktøj fejler, og at en sund app ikke bliver markeret som fejlet, bare fordi den sov.
Sådan publiceres en backend
Når din Mythex-app har en backend (en API eller server og ikke kun statiske filer), bygger udgivelsen den ud fra en Dockerfile (en opskrift på, hvordan appen pakkes i et image), og ruller imaget ud på maskiner hos vores hostingplatform. Derefter venter et sundhedstjek, til den nye version rent faktisk er oppe og bliver ved med at køre, før udgivelsen markeres som live.
Hvis noget fejler undervejs, køres en rollback for at fortryde det. At få rollbacken rigtig er vigtigere end næsten noget andet i udgivelsen, fordi den kører netop når noget allerede er ved at gå galt.
Problem 1: rollbacken slettede det, den skulle have gendannet
Rollbacken holdt en liste over de serverapps, en udgivelse havde deployet til, og ved fejl ødelagde den dem alle. Det er korrekt ved en første udgivelse: appen blev oprettet for et øjeblik siden, intet afhænger af den, og sletning efterlader tingene, som de var.
Det er præcis forkert ved en genudgivelse. Appen fandtes allerede og betjente rigtige besøgende. Den 25. september blev en apps API, der havde været live i omkring to et halvt timer, slettet af en mislykket genudgivelse.
Der lå en anden fejl gemt i samme kode. Hvor sletningen ikke gik igennem, blev det defekte nye image liggende på maskinerne, mens vi fortalte ejeren, at deres nuværende side stadig var live. Når sundhedstjekket kan fejle, er det nye image allerede rullet ud på alle maskiner. At gøre ingenting er ikke det samme som at fortryde.
Rettelsen: slet kun det, du selv oprettede, og gendan resten
Hver udgivelse registrerer nu for hver serverapp, den berører:
- om denne udgivelse oprettede den, og
- hvis ikke, hvilket image den kørte lige før.
Ved fejl får hver app én af tre behandlinger:
- Oprettet af denne udgivelse: slet den, som før.
- Fandtes allerede, tidligere image kendt: deploy det tidligere image igen med samme konfiguration. Ingen build, kun at imaget flyttes tilbage.
- Fandtes allerede, tidligere image ukendt: lad den være. En eksisterende app slettes aldrig, selv når vi ikke kan gendanne den.
Disse regler er en lille ren funktion med egne tests, så beslutningen ikke kan glide tilbage til »slet alt« uden at en test fejler.
Problem 2: deploy-værktøjet kunne fejle, før vi havde skrevet ned, hvordan man fortryder
Kort efter fandt vi et hul i rettelsen. Hostingplatformens deploy-værktøj kører sine egne røgtests: hvis de nye maskiner crasher, afslutter det med en fejl. Men det sker efter, at det nye image allerede er på maskinerne.
Vores kode registrerede kun en app til rollback, når værktøjet returnerede succes. Når værktøjet selv fejlede deployet, var appen ikke på rollback-listen endnu, så intet blev gendannet. På vores staging-miljø lod en mislykket genudgivelse det defekte image køre.
Rettelsen: registrer fortrydelsen, før du starter
Appen registreres nu til rollback, med information om, om den blev oprettet, og hvilket image den kørte, så snart den er klargjort, og før noget rulles ud på den. Uanset hvad der fejler bagefter, ved rollbacken, hvad der skal sættes tilbage. Vi verificerede lokalt: en mislykket genudgivelse fejlede i deploy-værktøjets egne røgtests, og den forrige version blev ved med at svare.
Problem 3: en sovende app blev fejlet som »aldrig startet«
Publiserede apps på Mythex går i dvale, når de er inaktive, for at spare dine credits. En genudgivelse efterlader en sovende maskine stoppet, så sundhedstjekket skal vække den. Det bad platformen om at starte maskinen én gang og ignorerede svaret.
Den 25. september gik den ene startanmodning ingen steder hen. En sund API lå stoppet hele de fem minutter sundhedstjekket varede og blev fejlet med »1 maskine startede aldrig (sidste tilstand: stopped)«.
Platformen nægter nogle gange at starte en maskine. Mens vi reproducerede problemet, returnerede den en konflikt »lease currently held«, uden at vi provokerede den, og den afviser starter, mens en maskine opdateres.
Rettelsen: bliv ved med at spørge, og bank på døren
Sundhedstjekket gør nu følgende:
- spørger igen om at starte en stoppet maskine hvert 20. sekund mens det venter,
- logger, hvad platformen svarede, når den afviser, og
- sender én almindelig anmodning til appen, sådan som en besøgende ville gøre. Platformens proxy vækker en sovende maskine, når en anmodning kommer ind.
Vi reproducerede det på den rigtige platform med en midlertidig app, hvis første start blev afvist. Før rettelsen fejlede udgivelsen efter 91 sekunder. Efter rettelsen blev den publiceret på 20 sekunder. En app, der virkelig crasher ved opstart, fejler stadig, som den skal.
Det vi lærte
- Rollback skal kunne skelne mellem nyt og eksisterende. »Fortryd« for noget, du har oprettet, er sletning. »Fortryd« for noget, der allerede var der, er gendannelse. At behandle dem ens er, hvordan en sikkerhedsmekanisme ender med at slette en fungerende app.
- Skriv ned, hvordan du fortryder, før du handler. Hvis fortrydelsen kun registreres, efter et trin lykkes, har et trin, der fejler halvvejs, intet at fortryde med.
- Ignorér aldrig svaret på en anmodning, du er afhængig af. Ét ignoreret »nej« gjorde en sund app til en mislykket udgivelse.
En beslægtet rettelse samme dag: at fange apps, der crasher lige efter udgivelse. Hvis det er en udgivelse på din side, der fejler, finder du hjælp i fejlfinding ved udgivelse i dokumentationen.