Săriți la conținut

Blog ·

De ce un 200 de la Proxmox nu este un rezultat

Proxmox răspunde la o cerere de clonare cu un UPID și își vede de treabă. Cum se citește identificatorul și cum deosebiți o sarcină încheiată de una reușită.

De Provibr

Trimiteți o cerere de clonare către un nod Proxmox. Primiți înapoi HTTP 200 și un șir de caractere care arată ca zgomot pe linie. În acea clipă este adevărat exact un singur lucru: cererea a fost acceptată. Niciun disc nu a fost copiat, nicio mașină nu există și nici nu a eșuat încă nimic.

Aproape fiecare apel care schimbă ceva în API-ul Proxmox funcționează așa, iar acesta este un model perfect rezonabil pentru operațiuni care durează minute. Devine o problemă abia atunci când codul apelant tratează acel 200 drept rezultat, ceea ce este comportamentul implicit al oricărui client HTTP scris vreodată.

Iată cum citim noi acele sarcini și care părți s-au dovedit mai ascuțite decât par.

Șirul este un identificator și are structură

Lucrul care se întoarce este un UPID: un identificator separat prin două puncte care numește o singură sarcină în execuție. El poartă, printre alte câmpuri, nodul pe care rulează sarcina, tipul sarcinii, lucrul asupra căruia acționează și utilizatorul care a pornit-o.

Trei dintre ele sunt de rezistență.

Nodul este cel pe care trebuie să îl întrebați de stare. O sarcină aparține nodului care o rulează, iar o întrebare la nivel de cluster nu este aceeași întrebare. Luați nodul din identificator și nu din variabila pe care se întâmplă să o aveți la îndemână, pentru că cele două pot diferi, iar cazul în care diferă este tocmai cel pe care nu l-ați testat.

Tipul vă spune ce a pornit de fapt, ceea ce nu este întotdeauna ceea ce ați cerut după nume. O copiere de siguranță pornită prin punctul de acces al nodului se întoarce cu tipul de sarcină vzdump, nu cu ceva în formă de oaspete.

Ținta este identificatorul oaspetelui pentru operațiunile pe oaspeți și poate fi goală. Aceasta nu este o corupere. O sarcină poate fi în mod legitim despre un nod și nu despre un oaspete, așa că un cod care analizează acest câmp trebuie să întoarcă „niciun oaspete”, în loc să arunce o excepție sau să cadă implicit pe zero.

Analizorul nostru este în mod deliberat îngust în privința a ceea ce respinge: cere prefixul, cere cel puțin opt câmpuri separate prin două puncte și cere ca nodul și tipul să nu fie goale. Tot restul are voie să lipsească, pentru că formatul trebuie să funcționeze în continuare pentru tipuri de sarcini pe care nu le-am văzut niciodată.

Încheiat nu este același lucru cu reușit

Cereți unui nod starea unei sarcini și primiți două câmpuri care răspund la două întrebări diferite. Unul spune dacă sarcina încă rulează sau s-a oprit. Celălalt, prezent abia după ce s-a oprit, spune OK sau poartă un șir de eroare.

Să scrieți asta ca două predicate separate în loc de unul nu este pedanterie. Un singur isDone() care le confundă produce defectul în care o clonare eșuată este raportată drept clonare încheiată, iar mașina care nu există primește un rând în baza dumneavoastră de date. Noi avem două funcții: una întreabă dacă s-a oprit, cealaltă întreabă dacă s-a oprit și dacă starea de ieșire este exact OK. Orice altceva este un eșec, inclusiv stări de ieșire pe care nu le-am văzut niciodată, pentru că o stare pe care nu o recunoaștem nu este o permisiune de a presupune succesul.

Unde trăiește sarcina face parte din API, nu este un detaliu

Copiile de siguranță ne-au învățat lucrul acesta. Este firesc să vă așteptați ca o operațiune pe un oaspete să atârne de oaspete: ceva sub calea proprie a mașinii virtuale. Nu există un asemenea punct de acces. O copiere de siguranță este pornită pe nod, cu identificatorul oaspetelui ca parametru.

Aceasta nu este o ciudățenie de ocolit. Ea schimbă ce trebuie să știți înainte să puteți porni: care nod, care spațiu de stocare de pe acel nod și dacă acel spațiu de stocare are voie să găzduiască copii de siguranță. Un spațiu de stocare care acceptă numai imagini de disc vă va accepta cererea bucuros și va eșua în interiorul sarcinii, ceea ce ajunge la client sub forma unui mesaj de eroare de la un program despre care nu a auzit niciodată. Așa că filtrăm lista de ținte după ce declară spațiul de stocare că poate conține și scoatem orice spațiu de stocare care este listat, dar nu răspunde, înainte să îi oferim cuiva o alegere.

Aceeași regulă în sens invers se aplică la așezarea unui disc. Aceeași idee, tip de conținut opus, și merită păstrate ca două filtre explicite, nu ca unul singur și isteț, împărțit între ele.

Interogarea este un ritm, nu un interval

Cât timp rulează sarcina, trebuie să întrebați în mod repetat. Variantele naive sunt amândouă proaste: interogați rapid la nesfârșit și bateți o oră întreagă în API-ul de administrare al cuiva, interogați rar și fiecare operațiune scurtă pare greoaie.

Compromisul nostru este de trei încercări rapide la o secundă distanță, apoi la fiecare trei secunde. Pornirea unei mașini se încheie în fereastra rapidă și pare instantanee. O copiere de siguranță de o oră costă o cerere la fiecare trei secunde, ceea ce nu înseamnă nimic.

Bucla se încheie în exact trei feluri și toate trei sunt explicite: sarcina raportează că s-a oprit, termenul-limită al comenzii trece sau întreaga comandă este anulată. Nu există o a patra ramură și niciun traseu pe care bucla pur și simplu continuă, iar aceasta este o proprietate care merită aranjată intenționat, nu sperată. Ce oprește și ce nu oprește un termen-limită este un subiect în sine; aici este de ajuns să spunem că bucla de interogare trebuie să aibă o cale de a pierde.

Cele mai multe sarcini nu vă vor spune cât au înaintat

Aceasta este partea care dezamăgește pe oricine construiește o bară de progres. Starea sarcinii vă dă în execuție sau oprit. Nu vă dă un procent.

Unele sarcini își scriu progresul în jurnalul propriu, ca text. O clonare emite linii cu un număr de octeți transferați și un procent între paranteze. Așa că pentru acelea citim coada jurnalului și luăm ultimul procent pe care îl găsim acolo. Extragerea unui procent dintr-o linie de jurnal nu este elegantă și este singurul loc unde există numărul.

Pentru tot restul afișăm o estimare, iar aici alegerea de proiectare contează mai mult decât formula. A noastră se apropie asimptotic de nouăzeci la sută pornind de la o durată așteptată, așa că bara se mișcă mereu și nu ajunge niciodată la capăt. O bară care stă la 100 la sută în timp ce nu se întâmplă nimic este mai rea decât nicio bară, pentru că îi spune celui care privește că ați terminat, iar apoi se contrazice.

Durata așteptată care alimentează acea curbă este un parametru de formă, nu un termen-limită. A noastră spune că o copiere de siguranță durează de obicei cam zece minute și o restaurare cam douăzeci, pe raționamentul că o restaurare citește arhiva și scrie discurile și deci mută aproximativ de două ori mai mulți octeți. Dacă o copiere durează mai mult decât estimarea, nu se strică nimic; curba rămâne pur și simplu politicos sub linie.

Eroarea este în jurnal, nu în stare

Când o sarcină eșuează, starea de ieșire este adesea aproape inutilă. „unable to create VM” este o categorie, nu o cauză. Cauza se află în ultimele câteva linii ale jurnalului sarcinii.

Așa că un raport de eșec este asamblat: starea de ieșire, plus coada jurnalului cu liniile de progres filtrate, mărginită la câteva linii și la câteva sute de caractere. Filtrarea liniilor de progres contează mai mult decât pare, pentru că o clonare care eșuează poate încheia cu douăzeci de linii de procente de transfer și cu motivul adevărat deasupra lor.

O regulă despre acel text, la care ținem cu strictețe: este un citat. Este ceea ce a spus hipervizorul și intră în evidențele noastre și pe ecran neschimbat. Nu îl traducem, nu îl facem ordonat și nu îl parafrazăm în ceva mai prietenos. Propoziția din jurul lui este a noastră și se traduce pentru cititor; partea citată rămâne exact așa cum a spus-o cealaltă parte, pentru că este singurul șir din tot schimbul pe care un inginer de asistență îl poate căuta.

Nu vă împletiți propriile cuvinte în propoziția mașinii

Ultima lecție este cea pe care nu ne așteptam să o învățăm de la un interogator de sarcini.

Bucla noastră de așteptare lua înainte o etichetă de text liber pentru operațiune și o împletea în trei propoziții: ceva în curs, ceva încheiat, ceva eșuat. A funcționat, până când cineva a observat că mai multe dintre acele etichete fuseseră scrise în limba noastră de birou și nu în engleză. Clienții citeau propoziții pe jumătate traduse în barele lor de progres, iar când o operațiune eșua, acea propoziție pe jumătate tradusă era stocată permanent în câmpul de eroare.

Remediul nu a fost să corectăm etichetele. A fost să eliminăm posibilitatea: operațiunea este acum o enumerare, iar fiecare caz poartă identificatori pentru „în curs”, „încheiat” și „eșuat”, nu cuvinte. Nu a mai rămas niciun text de greșit, iar propoziția se construiește în limba cititorului chiar la final. Păstrarea sinceră a acelor cataloage se dovedește a fi o disciplină în sine.

Lista de verificare

Pentru orice API care răspunde la o modificare cu un identificator în loc de un rezultat:

  1. Analizați identificatorul, nu doar îl stocați. El vă spune unde să întrebați, iar acel unde nu este întotdeauna unde ați întrebat.
  2. Separați încheiat de reușit, în sistemul de tipuri dacă se poate.
  3. Tratați o stare finală nerecunoscută drept eșec, niciodată drept succes.
  4. Dați buclei de interogare un ritm și o cale garantată de a se încheia.
  5. Arătați progresul pe care îl aveți cu adevărat și o estimare sinceră pe care să nu o puteți confunda niciodată cu progresul pe care îl aveți.
  6. Citați cealaltă parte cuvânt cu cuvânt în erori și construiți-vă propria propoziție în jurul citatului, nu înăuntrul lui.

Niciuna dintre acestea nu este dificilă. Pe toate șase le-am greșit mai întâi.

Începeți cu o gazdă și un agent

Creați un cont, instalați agentul cu o singură comandă și conectați primul dumneavoastră host Proxmox. Perioada de probă durează 14 zile și nu cere date de plată.