En mislykket republisering sletter aldri en fungerende backend
Den 25. september slettet en mislykket republisering backenden til en app som hadde vært live og fungerende i omtrent to og en halv time. Koden som angrer en mislykket publisering fjernet alle server-apper publiseringen hadde berørt, også de som allerede var i drift før den startet. Nå slettes bare apper en publisering har opprettet selv; en app som allerede fantes, settes tilbake til nøyaktig den versjonen den kjørte før. To oppfølgingsrettelser samme dag sørget for at angringen også kjører når hostingplattformens egne deploy-verktøy feiler, og at en frisk app ikke feilmerkes bare fordi den sov.
Slik publiseres en backend
Når Mythex-appen din har en backend, altså et API eller en server og ikke bare statiske filer, bygger publiseringen den fra en Dockerfile (en oppskrift for å pakke appen i et image) og ruller imaget ut på maskiner hos hostingplattformen vår. Deretter venter en helsesjekk på at den nye versjonen faktisk starter og holder seg oppe, før publiseringen merkes som live.
Hvis noe feiler underveis, kjøres en tilbakerulling for å angre det. Å få den riktig er viktigere enn nesten alt annet i publisering, fordi den kjører nettopp når noe allerede går galt.
Problem 1: tilbakerullingen slettet det den skulle ha gjenopprettet
Tilbakerullingen holdt en liste over server-appene en publisering hadde deployet til, og ved feil ødela den alle sammen. Det er riktig for en første publisering: appen ble opprettet et øyeblikk tidligere, ingenting avhenger av den, og sletting etterlater ting slik de var.
Det er helt feil for en republisering. Appen fantes allerede og betjente ekte besøkende. Den 25. september ble et app-API som hadde vært live i omtrent to og en halv time, slettet av en mislykket republisering.
Det lå en andre feilmodus gjemt i samme kode. Der slettingen ikke gikk gjennom, ble det ødelagte nye imaget liggende på maskinene, mens vi fortalte eieren at nettstedet fortsatt var live. Når helsesjekken kan feile, er det nye imaget allerede rullet ut på alle maskiner. Å gjøre ingenting er ikke det samme som å angre.
Løsningen: slett bare det du opprettet, gjenopprett resten
Hver publisering registrerer nå, for hver server-app den berører:
- om denne publiseringen opprettet den, og
- hvis ikke, hvilket image den kjørte like før.
Ved feil får hver app én av tre behandlinger:
- Opprettet av denne publiseringen: slett den, som før.
- Fantes allerede, tidligere image kjent: deploy det tidligere imaget med samme konfigurasjon. Ingen bygging, bare imaget flyttes tilbake.
- Fantes allerede, tidligere image ukjent: la den være. En eksisterende app slettes aldri, selv når vi ikke kan gjenopprette den.
Reglene er en liten ren funksjon med egne tester, så avgjørelsen ikke kan glide tilbake til «slett alt» uten at en test feiler.
Problem 2: deploy-verktøyet kunne feile før vi hadde skrevet ned hvordan vi angrer
Kort tid etter den rettelsen fant vi et hull i den. Hostingplattformens deploy-verktøy kjører egne røyktester: hvis de nye maskinene krasjer, avslutter det med en feil. Men det gjør det etter at det nye imaget allerede ligger på maskinene.
Koden vår registrerte bare en app for tilbakerulling når verktøyet returnerte suksess. Når verktøyet selv feilet deployet, sto ikke appen på tilbakerullingslisten ennå, så ingenting ble gjenopprettet. I staging lot en mislykket republisering det ødelagte imaget fortsette å svare.
Løsningen: registrer angringen før du starter
Appen registreres nå for tilbakerulling, med om den ble opprettet og hvilket image den kjørte, så snart den er klargjort og før noe rulles ut på den. Uansett hva som feiler etterpå, vet tilbakerullingen hva den skal legge tilbake. Vi verifiserte lokalt: en mislykket republisering feilet i deploy-verktøyets egne røyktester, og den forrige versjonen fortsatte å svare.
Problem 3: en sovende app ble feilmerket som «startet aldri»
Publiserte apper i Mythex sover når de er inaktive, for å spare kreditter. En republisering etterlater en stoppet maskin i dvale, så helsesjekken må vekke den. Den ba plattformen starte maskinen én gang, og ignorerte svaret.
Den 25. september gikk den ene startforespørselen ingen veg. Et friskt API lå stoppet gjennom hele fem minutters helsesjekk og ble feilmerket med «1 machine never started (last state: stopped)».
Plattformen avviser av og til en start. Mens vi reproduserte dette, returnerte den en «lease currently held»-konflikt uten at vi utløste den, og den avviser starter mens en maskin oppdateres.
Løsningen: spør igjen, og bank på døren
Helsesjekken gjør nå følgende:
- ber om å starte en stoppet maskin på nytt hvert 20. sekund mens den venter,
- logger hva plattformen svarte når den avviser, og
- sender én vanlig forespørsel til appen, slik en besøkende ville gjort. Plattformens proxy vekker en sovende maskin når en forespørsel kommer inn.
Vi reproduserte dette på den ekte plattformen med en midlertidig app der første start ble avvist. Før rettelsen feilet publiseringen etter 91 sekunder. Etter rettelsen ble den publisert på 20 sekunder. En app som virkelig krasjer ved oppstart, feiler fortsatt, som den skal.
Det vi tok med oss
- Rollback må skille mellom nytt og eksisterende. «Angre» for noe du opprettet er sletting. «Angre» for noe som allerede fantes, er gjenoppretting. Å behandle dem likt er hvordan en sikkerhetsmekanisme sletter en fungerende app.
- Skriv ned hvordan du angrer før du handler. Hvis angringen bare registreres etter at et steg lykkes, har et steg som feiler halvveis ingenting å angre med.
- Ignorer aldri svaret på en forespørsel du er avhengig av. Ett ignorert «nei» gjorde en frisk app til en mislykket publisering.
En beslektet rettelse samme dag: fanging av apper som krasjer rett etter publisering. For hva som skjer når en publisering feiler på din side, se feilsøking av publisering i dokumentasjonen.