Manjkajoča swap datoteka, ki je zamrznila naše strežnike
Na 24. september je eden od naših produkcijskih strežnikov nehal odgovarjati. Obremenitveni uravnoteževalnik ga je označil kot nezdravega, naše orodje za oddaljene ukaze pa do njega ni moglo dostopati. Nič se ni zrušilo in noben proces ni bil ubit zaradi prevelike porabe pomnilnika. Strežnik je bil v thrashingu: ker ni bilo swap prostora, containerji pa so smeli porabiti več pomnilnika, kot ga je imel stroj, je Linux ves čas izločal in znova bral datoteke, ki jih je potreboval za delovanje, zato skoraj ni napredoval. Dodajanje 2 GB velike swap datoteke na vsak strežnik je težavo odpravilo. Zanimivejša lekcija pa se je nanašala na health checke.
Kako je izgledalo »zamrznjeno«
- Health checki obremenitvenega uravnoteževalnika so na tem strežniku potekli v časovni omejitvi.
- Ukazi, poslani prek AWS Systems Manager, so ostali v stanju »Pending« in se nikoli niso izvedli.
- V dnevnikih ni bilo ubijanja zaradi pomanjkanja pomnilnika. Noben proces ni umrl.
Teden prej smo produkcijo premaknili na manjše ARM strežnike z 2 GB RAM-a vsakega. Naš staging strežnik, ki je še manjši, je na enak način zamrznil med razpakiranjem container slike med deployem.
Zakaj ni bilo napake pri pomnilniku?
Linux ubije proces šele, ko resnično zmanjka pomnilnika, ki ga lahko sprosti. Do te točke sprošča pomnilnik iz predpomnilnika strani, to je kopij datotek, ki jih hrani v RAM-u, vključno s kodo vsakega delujočega programa.
Na naših strežnikih so bile skupne meje pomnilnika containerjev okoli 3,3 GB na stroju z okoli 1,8 GB uporabnega pomnilnika. Običajno vsi ne porabijo svoje meje hkrati. Ko so, je jedro ves čas izločalo kodo programov iz predpomnilnika, da je naredilo prostor, nato pa jo je moralo z diska ponovno prebrati takoj, ko se je ta koda znova zagnala. Ta cikel, thrashing, lahko stroj naredi skoraj popolnoma neodziven, čeprav tehnično nikoli ne zmanjka pomnilnika. Zato se nič ne ubije in nič ne okreva.
Brez swapa jedro nima kam dati pomnilnika, ki je dodeljen, a redko uporabljen, zato ga mora vzeti iz predpomnilnika.
Popravek
Bootstrap skripta strežnika zdaj ustvari 2 GB swap datoteko z nizko nastavitvijo swappiness (10) ob vsakem zagonu ali popravilu strežnika. Nizek swappiness pomeni, da jedro še vedno raje hrani delovni pomnilnik v RAM-u, vendar se lahko redko uporabljene strani premaknejo na disk, namesto da bi silile kodo programov iz predpomnilnika. Popravek smo uveljavili v živo na obeh produkcijskih strežnikih in stagingu. Deploy pipeline ob vsakem produkcijskem deployu znova objavi bootstrap skripto, tako da tudi zamenjan strežnik dobi swap.
Bolj skrb vzbujajoč del: nezdrav strežnik je ostal
Naša skupina strežnikov je uporabljala EC2 health checke, torej »ali stroj deluje?«, ne health checkov obremenitvenega uravnoteževalnika, torej »ali odgovarja na zahteve?«. Zato je bil strežnik, ki ga je uravnoteževalnik opustil, za skupino še vedno zdrav. Nikoli ga ne bi zamenjali. Produkcija bi tiho tekla na enem strežniku, dokler nekdo ne bi opazil in ponovno zagnal drugega.
Preklop skupine na health checke uravnoteževalnika se sliši kot očitna rešitev, vendar pri nas ni preprosta. Med deployem strežniki namerno vračajo napako na svojem health endpointu, da jih uravnoteževalnik izprazni (glejte kako smo dosegli nič 502 napak pri deployih). Skupina, ki bi strežnike zamenjala na ta signal, bi jih zamenjala sredi vsakega deploya.
Zato smo v kontrolni seznam za deploy dodali korak: po vsakem deployu preverimo stanje vsakega cilja na uravnoteževalniku, ne le tega, ali je deploy job uspel. Če ukazi za strežnik obtičijo v »Pending«, strežnik thrasha, zato ga ponovno zaženemo. Že je zunaj prometa.
Kaj smo iz tega izvlekli
- »Ne da bi se zrušil« ni isto kot »zdrav«. Thrashing ne proizvede napake, ampak tako ekstremno počasnost, da stroj preneha odgovarjati.
- Majhni strežniki potrebujejo swap. Tudi malo, z nizkim swappinessom, daje jedru prostor za hladen pomnilnik.
- Meje pomnilnika, ki skupaj presegajo kapaciteto stroja, so stava. Včasih jo izgubiš. Vedi, kdaj si jo sprejel.
- Vedi, kateremu health checku zaupa tvoje samodejno skaliranje. Health check, ki le sprašuje »je VM vklopljen?«, ne bo zamenjal strežnika, ki je nehal delovati.