În mod tradițional, am tratat serverele precum tratăm animalele de companie: ne-am atașat de ele, am cheltuit bani pentru a le menține sănătoase și am petrecut timp pentru a ne asigura că li se acordă suficientă atenție, ca acestea să nu înceapă să cauzeze probleme. Nu puține au fost cazurile când un astfel de server, un "snowflake", a fost cauza singulară a întreruperii serviciilor, ajungând ca echipele de operations să se trezească în miez de noapte, să își caute buimaci laptopurile și să încerce să își dea seama ce se întâmplă pentru a stabiliza situația înainte de a se propaga impactul prin întreg sistemul.
În noua abordare, tratăm serverele precum turmele de vite ("cattle") - nu ne atașăm de ele, ne asigurăm că sunt numerotate și că sunt aliniate făcând exact ceea ce trebuie să facă. Dacă un server nu se comportă cum trebuie, acesta este scos din linie și neutralizat, pentru a nu face probleme pe viitor, lăsând locul liber altui server care îl va înlocui. Aceasta abordare reduce costul operațional asociat mentenanței infrastructurii, în special atunci când e cuplată cu concepte precum cele de "automated cluster healing" ("repararea automată a clusterelor"), de provizionare și de monitorizare.
Ofertele providerilor de infrastructură cloud fac această abordare mai viabilă decât data centerele fizice, oferind posibilitatea de a accesa resurse noi în orice moment - desigur întâlnind costuri suplimentare și uneori, anumite limite.
Există o serie de considerente ce trebuie luate în calcul înainte de a ne îmbarca în această abordare.
Arhitectura unei aplicații este unul din factorii principali ce au un cuvânt de spus în această privință. Candidatul ideal este o aplicație stateless care profită de un mecanism de buffering pentru operațiunile sale de scriere, printr-un sistem de queueing sau streaming, precum RabbitMQ sau Apache Kafka. Astfel, datele sunt păstrate local doar pentru scurt timp, înainte de a fi trimise spre sistemul de buffering.
Această abordare mai depinde de abilitatea sistemului de a asigura lansarea rapidă de noduri, printr-un proces de startup rapid, în cadrul căruia nu sunt afectate alte componente, reducând-se astfel numărul de complicații neprevăzute ce pot apărea. Acest proces e esențial în a asigura stabilitatea clusterului, restaurând un cluster degradat înspre unul de capacitate maximă într-un timp cât mai scurt cu putință. Soluțiile uzuale pentru aceasta se bazează pe imagini prebuilt pentru VM-urile pe care va rula aplicația, acestea conținând aplicația în sine, librăriile necesare pentru funcționarea ei, precum și toate serviciile auxiliare, care sunt responsabile cu monitorizarea și facilitarea procesului de discoverability. Packer, un tool produs de HashiCorp, s-a dovedit esențial în a ajunge la un timp mic de provizionare a nodurilor, permițând builduri de imagini scheduled, care sunt și cloud provider agnostic.
Abilitatea de a detecta o degradare în performanța unui cluster este un alt aspect important de luat în considerare înainte de a aplica aceasta abordare. Healthcheck-urile "monolitice" sunt ușor de pus în practică - acestea sunt de obicei configurate să monitorizeze un endpoint, un port sau statusul unui serviciu - și de obicei, își fac treaba atunci când ceva nu funcționează bine. Serverul este marcat ca unhealthy, este scos în afara clusterului, izolând sistemul de degradarea provocată de el. Mai mult, există probabilitatea ca serverul să declanșeze uneori și o alertă. De obicei, funcționează. Dar uneori, un healthcheck monolitic se dovedește imprecis sau prea lent, având într-un final impact negativ.
Aici intervin monitorizarea și metricile custom. În ziua de azi, există numeroase soluții de agregare ale metricilor, atât open source cât și comerciale, toate au abilitatea de a monitoriza elemente precum gradul de utilizare al memoriei, procesorului sau diskului.
Dar observabilitatea înseamnă mult mai mult decât a agrega și urmări metrici standard. Observabilitatea se referă la menținerea unei priviri transparente spre fiecare fațetă a platformei, inclusiv la nivel de performanță și comportament ale aplicațiilor și a întregului sistem.
Metricile customizate sunt primul pas în crearea unui sistem în care serverele care nu se comportă normal sunt identificate și izolate de restul serverelor din cluster. Metricile custom permit stabilirea unui baseline, un comportament "normal" al membrilor "turmei" - permițând identificarea și înlăturarea oricărui element disruptiv după ce acesta întrece un anumit prag.
Noi monitorizăm un volum mare de metrici custom, unele depind de tipul de aplicație - măsurarea latenței de răspuns, în cazul API-urilor, iar altele sunt business-related, permițând identificarea altor tipuri de probleme în cadrul platformei, precum servirea cu probleme al unui format de video, când celelalte sunt servite cu succes către client. Un alt plus al agregării de metrici e posibilitatea de a evalua performanța și comportamentul unui server și după data sa de "expirare". Astfel, echipa de operations poate analiza un comportament problematic în timpul programului, nesacrificând ore de somn pentru aceasta.
La Connatix, am decis că Prometheus se pliază perfect nevoilor noastre. Prometheus e un proiect Cloud Native Computing Foundation (CNCF), așadar open source, este extensibil și permite agregarea de metrici custom cu numeroasele librării făcute disponibile de comunitatea formată în jurul său. Alte plusuri ale lui Prometheus sunt managerul de alerte intern, care se integrează cu servicii precum PagerDuty și Slack, precum și query language-ul său flexibil, cu numeroase feature-uri, printre care și cel de predicție lineară a valorii unei metrici.
Repararea automată a unui cluster - sau "auto-healing" - a fost menționată anterior în articol și e un punct cheie în a menține performanța unui sistem la nivelul dorit. "Auto-healingul" e responsabil de procesul de provizionare și suplimentare adițională a numărului serverelor dintr-un cluster atunci când capacitatea clusterului scade sub nivelul dorit.
În cazul ideal, când performanța unui server se degradează și încep healthcheckurile sale să cadă, am avea deja o nouă instanță pregătită să-i ia locul, dar aceasta nu e întotdeauna valabil. Așadar, capacitatea unui cluster ajunge să scadă, iar performanța sa se degradează, până când "autohealingul" își face treaba și totul e readus la normal.
"Overloadul" presupune că utilizarea medie a resurselor unui cluster depășește limita normală, datorită faptului că workloadul clusterului e distribuit pe mai puține noduri de server decât de obicei. Uneori, când workloadul este foarte mare, acest overload este riscant, deoarece în cazul în care alte noduri devin "nesănătoase", overloadul este mult mai puternic resimțit la nivelul celor sănătoase și poate reduce capacitatea clusterului la zero.
Din fericire, există câteva metode de a reduce acest risc. Factorul de redundanță al unui cluster poate fi mărit oferind un număr mai mare de servere, fiecare cu mai puține resurse provizionate, în loc de a avea un număr mai mic de mașini ce au resurse mai multe. Astfel, dacă două noduri de server decid să o ia razna, preferăm să avem mai degrabă un cluster cu 15 servere low-spec, decât 5 servere high-spec. Procentual vorbind, impactul unui outage este mult redus și workloadul e distribuit pe mai multe noduri în cazul scenariului cu mai multe servere.
Cealaltă modalitate este ușor de implementat, dar introduce costuri adiționale. Overprovisioningul implică provizionarea unui cluster cu un număr de servere mai ridicat decât ar fi necesar pentru workloadul său. Iar mai multe servere presupun costuri mai mari. Așadar cum ne ocupăm de aceasta?
Din fericire pentru noi, providerii de infrastructură cloud oferă un mecanism de obținere a capacității computaționale dorite, asigurând totodată costuri scăzute (per total). AWS numește acest mecanism spot instances, iar Google îl oferă sub denumirea de preemptible VMs.
Sună bine, doar că situația nu e atât de roz pe cât pare. Instanțele pot fi provizionate la 20-50% din costul unei instanțe uzuale, specializate, dar sunt mai puțin durabile, din punct de vedere al disponibilității lor. Aceste spot instances au o durată de viață scurtă, de la câteva ore la câteva săptămâni, în funcție de mai mulți factori, printre care și loadul datacenterului fizic al providerului de infrastructură cloud. Cloud providerul pur și simplu vinde capacitate de compute neutilizată, la un preț redus, astfel generând ceva venit pentru capacitatea care ar rămâne altfel neutilizată. Totuși, când e nevoie de această capacitate pentru instanțe specializate, aceste instanțe spot sunt distruse, iar serverului îi e oferită o fereastră de două minute pentru a-și finaliza în siguranță volumul de muncă înainte de o închidere forțată.
Din fericire, atât AWS cât și Google permit, prin intermediul unor endpointuri de metadata, accesibile de pe aceste instanțe, să determinăm dacă mașina va fi distrusă. Adoptând o poziție provider-agnostic, încercăm să ne limităm de integrările cu providerii de infrastructură cloud, așadar aplicațiile noastre neștiind dacă o mașină pe care rulează va fi distrusă în curând. Drept soluție, am început să ne provizionăm nodurile cu un mic serviciu care verifică endpointul de metadata la fiecare câteva secunde și emite câte un semnal SIGHUP fiecărei aplicații, pentru a le înștiința că e momentul să finalizeze orice task de procesare ar avea în momentul de față. Din nou, abordarea aceasta e mult ajutată în cazul în care aplicația noastră e gândită să fie stateless.
Dintre toate clusterele noastre, cel de encodare video a beneficiat cel mai mult de această abordare. Instanțele care au atașat câte un GPU (Graphics Processing Unit) sunt de obicei scumpe - spot instances având aplicabilitate maximă din acest punct de vedere, tăind costurile semnificativ. Totuși, instanțele GPU au o foarte mare cerere, și vedem că sunt distruse la intervale destul de mici. Serviciul nostru de encodare video funcționează ca orice serviciu de procesare: rezervă un set de joburi de encodare în tabelul care servește drept queue, descarcă video-urile, le re-encodează, apoi încarcă fișierele care au rezultat într-o locație remote, finalizând cu marcarea jobului drept terminat în tabelul mai sus menționat. Pentru a beneficia de acest workflow, serviciul de encodare video dispune de un mic feature - când primește un semnal SIGHUP, anulează toate joburile de re-encodare de pe server și marchează setul de joburi ca disponibil pentru a fi preluat de altă instanță de encodare video.
Având în vedere că aceste noduri sunt distruse în funcție de loadul data centerului fizic, pot exista momente în care spot instances se găsesc destul de greu. În mod normal, acest lucru ar lăsa clusterele subnutrite de resurse, însă în funcție de modul în care se implementează procesul de "auto-healing", acesta poate modifica în mod automat configurația clusterului, trecând de la folosirea spot instances la folosirea instanțelor specializate până când capacitatea de spot crește din nou, moment în care configurația revine la starea inițială. Așadar putem garanta atât uptime ridicat, cât și costuri reduse.
Instanțele spot pot fi folosite într-un număr mare de scenarii : de la scalarea automată a mașinilor de Jenkins în funcție de queue-ul de build și momentul zilei, la folosirea lor ca mașini de build pentru imagini de Packer, procesarea joburilor în batchuri, cu load predictibil și impredictibil și până la rularea aplicațiilor stateful. Clusterele noastre de Elasticsearch funcționează pe spot instances înregistrate in Kubernetes, folosind Stateful Set și Persistent Volume Claims (PVC) - două feature-uri care permit persistarea volumului de date în cazul distrugerii unei instanțe, asigurând atașarea sa noii instanțe care îi va lua locul. Așadar, nu există pierdere de date, iar clusterul nu are nevoie să rebalanseze sau să își replice shardurile printre nodurile care au rămas.
Le folosim pentru toate API-urile noastre, împreună cu Spinnaker și Terraform. Spinnaker e o platformă de continous delivery pe care o folosim pentru a lansa noi versiuni ale API-urilor noastre, totodată asigurând creșterea gradului de încredere în release-urile noastre prin observabilitate. Spinnaker ne permite să lansăm release candidates, folosind procese de canary analysis pentru a identifica orice discrepanță sau impact negativ înainte de a-l trimite în producție. După ce se promovează release candidate-ul înspre producție, se inițiază un rolling deploy, provizionând în batchuri servere care conțin noua versiune de API, înainte de a se distruge serverele care conțin vechea versiune. Terraform ne permite să versionăm, provizionăm și să previzualizăm schimbările pe care le aducem infrastructurii și servește și ca un tool perfect pentru a detecta schimbările nedorite, aduse manual, infrastructurii. Terraform e apelat prin intermediul Spinnaker pentru a pregăti terenul noilor versiuni de clustere.
Am menționat că spot instances vin și pleacă - totuși e destul de greu să monitorizezi și să aduni metrici în acest caz, nu? Din fericire, providerii de cloud ne sar în ajutor - din nou. Prometheus se integrează nativ cu API-urile marilor provideri de infrastructură cloud și detectează și grupează serverele pe bază de taguri. Prometheus se integrează și cu un număr de tooluri de service discovery, precum Consul - de asemenea creat de HashiCorp și de asemenea folosit intern de noi.
La Connatix, folosim spot instances cât de des putem. Ne ajută să reducem costurile, să dezvoltăm mecanisme care într-un final cresc fiabilitatea, și totodată oferă provocări colegilor noștri tehnici, de la software, la operations engineers. Trecerea spre abordarea de "turme de vite, nu animale de companie" necesită dedicare, precum și timp pentru a fi implementata cum trebuie - presupune planificare în amănunt, necesită modificări la nivel de arhitectură atât de sistem cât și de aplicație, și se bazează pe implementarea bulletproof a conceptelor cheie - "auto-healing" și startup rapid al nodurilor.
Adoptarea "animalelor de companie" se rezolva rapid și oferă un sentiment de satisfacție pe termen scurt, însă responsabilitatea și costul necesitate de ele se adună pe termen mediu spre lung ajungând-se în punctul în care nimeni nu mai dorește să se atingă de ele. În schimb, în ceea ce privește "vitele" putem sta liniștiți, știind că putem să "neutralizăm" oricând cauzatorii de probleme, fără a perturba munca de zi cu zi și noapte cu noapte.