Când căutarea simplității creează complexitate în conductele CI bazate pe containere

Într-un club de carte GitLab, am citit recent „ Legile simplității ”, o carte grozavă pe un subiect care m-a fascinat profund de mulți ani. Cartea conține un acronim care exprimă abordările de generare a simplității: SHE, care înseamnă „shrink, hide, inbody”. Aceste trei abordări pentru generarea simplității au toate un atribut comun: toate creează iluzii – nu eliminări.

Am văzut această iluzie repetându-se în multe, multe tărâmuri de urmărire de mulți ani. Chiar și în limbajul uman, dezvoltarea vocabularului, jargonul și acronimele încapsulează pur și simplu lumi de complexitate care încă există, dar pot fi mai ușor de făcut referire într-o formă compactă care realizează SHE în lumea conceptelor.

Orice iluzie are o limită sau o cortină unde în fața cortinei complexitatea poate fi rezolvată urmând reguli simple, dar, în spatele cortinei, complexitatea trebuie gestionată de un manager de scenă.

De exemplu, când spectacolul de magie creează spectrul tăierii oamenilor în jumătate, ceea ce pare a fi o simplă cutie este de fapt un instrument extrem de elaborat. Nu numai asta, dar procesul de fabricație pentru o cutie simplă reală și pentru cutia de ferăstrău sunt semnificativ diferite în ceea ce privește complexitatea. Producerea complexității și rezultatul acesteia sunt, în esență, compromisul pentru ceea ce ar fi complexitatea din lumea reală de a tăia oamenii în jumătate și de a-i face să se vindece și să se ridice nevătămați imediat după.

Pentru a aduce acest lucru în domeniul abilităților tehnice, luați în considerare că atunci când utilizați o componentă terță parte sau API pentru a adăuga funcționalitate, trebuie doar să cunoașteți parametrii pentru a obține rezultatul dorit. Persoanele care întrețin acea componentă sau API-ul trebuie să cunoască nivelul de detaliu al mecanicii cuantice despre cum să efectueze acea lucrare într-un mod fiabil și complet.

Containerele Docker sunt un mecanism pentru încorporarea complexității și sunt utilizate în aplicații scalate și în CI bazat pe container. Când un inginer de automatizare CI/CD folosește CI bazat pe container, este posibil să facă lucrurile mai complexe și mai scumpe atunci când încearcă să facă exact invers.

În esență, această postare este preocupată de modul în care se poate întâmpla ca urmărirea unei lumi mai simple prin containere să se transforme într-un antimodel – o inversare a rezultatelor dorite – de multe ori, fără ca noi să observăm că inversarea ne afectează productivitatea. Închisoarea unei paradigme este într-adevăr sigură.

A doua lege a dinamicii complexității

De-a lungul anilor, am ajuns să cred că căutarea reducerii complexității are caracteristici similare celei de-a doua lege a termodinamicii . Rezultatul net al unei schimbări între masă și energie are ca rezultat aceeași cantitate netă de masă și energie, dar raportul și forma lor s-au schimbat. În ceea ce voi inventa „A doua lege a dinamicii complexității”, complexitatea este în mod similar „conservată”, este doar reformată.

Dacă complexitatea nu este eliminată prin simplificarea eforturilor, îi reducem impactul într-un domeniu dat prin schimbarea raportului dintre complexitate și simplitate pe fiecare parte a uneia sau mai multor perdele. Dar, din păcate, complexitatea nu a murit, doar s-a ascuns și acum este provocarea de management a altcuiva. Este important să nu te gândești la asta ca la o înșelăciune. Nu există nicio îndoială că ascunderea complexității are potențialul de câștiguri masive de eficiență atunci când lumea din spatele mecanismelor de ascundere devine tărâmul abilităților de specialitate și al specialiștilor. Când externalizează cu adevărat gestionarea complexității pentru o parte, lumea devine mai simplă pentru acea parte.

Cu toate acestea, diavolul este în detalii. Dacă ipoteza „fără eliminare netă a complexității” este corectă, atunci este important unde migrează complexitatea. Dacă migrează către o altă parte a aceluiași proces care trebuie gestionată și de aceleași persoane, atunci este posibil să nu aibă ca rezultat un câștig net de eficiență. Dacă migrează dintr-un tărâm încorporat anterior, atunci, în căutarea simplității, ne putem reduce efectiv eficiența generală atunci când procesul este considerat ca un întreg.

Conducte CI bazate pe containere ca un caz util

Văd potențialul ca inversări ale eficienței să apară în munca mea zilnică din când în când, și un loc interesant în care l-am văzut în ultima vreme este în compromisul de a lega împreună module hiper-specializate de cod în containere pentru CI versus utilizarea mai generalizată. module.

În crearea conductelor pe bază de containere, experimentez potențialul unei inversări de eficiență pe care trebuie să o gestionez în mod conștient.

Containerele fac un compromis de simplitate prin design. Ei creează un mediu de rulare complet pentru un singur scop, dar, făcând acest lucru, eliberează elementele interne ale containerului atât de mult încât sarcinile generale de calcul sunt dificile în interiorul lor. Dacă treceți în spatele perdelei lor de „încorporare a complexității” în container, mediul lor simplist poate necesita un cod mai complex pentru a funcționa în interior.

În conductele GitLab CI care utilizează containere, toate scripturile joburilor rulează în interiorul containerelor care sunt specificate ca mediul lor de rulare. Când se selectează un container specializat – cum ar fi containerul alpin git sau containerul de gestionare a imaginii skopeo – codul este supus limitărilor shell-ului pe care îl folosește containerul (dacă are unul).

Containerele au fost concepute pentru a fi timpuri de execuție hiperspecializate, specifice scopului, care asigură că pot rula și rula întotdeauna rapid pentru aplicații scalate. Cu toate acestea, pentru multe containere, acest lucru înseamnă că nu există o carcasă sau o carcasă din spate foarte dezbrăcată, cum ar fi busybox sh. Adesea înseamnă, de asemenea, să nu includeți managerul de pachete pentru distribuția Linux de bază.

Din nou și din nou, m-am trezit degradând implementarea codului meu shell în moduri cheie, care îl fac mai complex, astfel încât să poată rula sub aceste shell-uri îndepărtate. În aceste cazuri, nu beneficiez de ascunderea complexității versiunilor mai noi de shell-uri avansate precum Bash v5. Una dintre zone este expansiunile avansate de shell Bash, care întruchipează o lume imensă de analiză complexă și evită o grămadă de utilități străine. Și o alta este logica avansată de comparare a declarațiilor if and case, care procesează expresii regulate fără utilități externe și realizează multe alte comparații abstracte. Există multe alte zone ale limbii în care acest lucru intră în joc, dar acestea două ies în evidență.

Deci, având o carcasă mai simplă precum busybox sh, simplitățile caracteristicilor avansate ale carcasei devin neascunse și se alătură părții mele de perdea. Acum trebuie să le gestionez în codul meu. Dar atunci, ghici ce? Niciun manager de pachete înseamnă incapacitatea de a instala alte utilitare Linux și extensii de limbi pe care le-aș putea folosi și pentru a împinge aceeași complexitate din spațiul meu. Și, desigur, înseamnă că și instalarea Bash v5 ar fi dificilă.

Așadar, propunerea de simplitate a unui container bine optimizat și specific unui scop poate inversa presupusele câștiguri de eficiență în domeniul foarte important al codului pe care trebuie să-l scriu. De asemenea, înseamnă că deseori trebuie să-mi despart codul în mai multe joburi pentru a utiliza specializările acestor containere într-o secvență sau pentru a transporta rezultatele unui container specializat într-un mediu de codare mai complet. Acest lucru crește complexitatea conductei, deoarece acum trebuie să transmit artefacte și date variabile de la un job la altul cu o serie de directive YAML suplimentare și, uneori, să implementez infrastructura (de exemplu, Runner caching ).

În cazul în care CI utilizează containere, atunci când compromisurile de simplitate transferă complexitatea la lucruri pe care nu le întrețin, cum ar fi containerele de bază, pachetele de sistem de operare și mediile shell complete, în lucrurile pe care le întrețin, cum ar fi codul CI YAML și Shell Script, atunci moștenesc și întreținerea complexității pe termen lung. În nor, știm asta ca ridicare grea nediferențiată.

În mod interesant, proliferarea containerelor specializate poate necesita, de asemenea, mai multe resurse ale mașinii și poate prelungi timpul de procesare, deoarece containerele sunt preluate din registre și încărcate, iar artefactele și codul sursă sunt copiate în și din fiecare container bazat pe job.

Țintă de simplitate: eficiență

Este ușor să pierdeți din vedere cantitatea de efort uman și ingeniozitate aplicată pentru cunoașterea și gestionarea structurii de codificare, mai degrabă decât pentru rezolvarea problemelor reale de automatizare ale conductei CI. Complexitatea netă a conductei poate însemna, de asemenea, că este greu să-l înțelegi, chiar dacă lucrezi în el în fiecare zi – iar pentru noii veniți la bord, pot trece multe săptămâni până când aceștia înțeleg pe deplin cum funcționează sistemul.

Desigur, îmi pot crea propriile containere pentru conductele CI, dar acum am adăugat complexitatea dezvoltării containerelor și actualizările continue ale acestora, pentru ca codul conductei mele să fie operațional și să rămână sănătos. Sunt încă în spatele cortinei pentru acel container. Pentru echipele al căror software nu este în sine containerizat, perspectiva de a învăța să construiască containere doar pentru CI poate crea o mulțime de fricțiuni de înțeles în adoptarea unui proces de dezvoltare CI bazat pe containere. Această frecare poate fi inutilă dacă facem o adaptare euristică cheie.

Trecând firul strâns deasupra perdelei

Deci, cum pot gestiona tensiunile acestor lumi multiple de complexitate atunci când vine vorba de conducte bazate pe containere pentru a încerca să evit inversările de eficiență în complexitatea netă a conductei?

Este simplu. Voi descrie metoda și apoi cheia euristica aplicată greșit și cum să o ajustez.

  1. Consider că beneficiile principale ale CI bazat pe container sunt a) izolarea dependenței în funcție de job (astfel încât să nu aveți o specificație masivă și fragilă a mașinii de construire a CI pentru a gestiona toate cerințele posibile de construire) și b) starea curată a agentului de construire CI. prin obținerea unei copii curate a containerului pentru fiecare lucrare. Aceste beneficii nu implică nevoia de a respecta planificarea resurselor containerelor de microservicii, iar acest lucru creează un antipattern în productivitatea mea.

  2. Folosesc frecvent un container Bash 5 (versiune atașată dacă este necesar) în care toată complexitatea pe care o încorporează capabilitățile avansate de shell pentru mine rămâne în spatele cortinei.

  3. În loc să rulez un container hiper-minimalizat pentru un anumit utilitar, fac o instalare de rulare a acelui utilitar (gaf!) într-un container care are shell-ul meu bogat. Folosesc fixarea versiunii în timpul instalării dacă simt că siguranța versiunii este primordială pentru utilitar. În mod alternativ, dacă un timp de execuție foarte dorit de un anumit tip este dificil de configurat și nu are un pachet, caut un container care are un manager de pachete care se potrivește cu o versiune pachetată a timpului de execuție și, de asemenea, îmi permite să instalez limbajul meu de scriptare avansat dacă Necesar.

  4. Dacă, și numai dacă, timpul net al instalărilor de rulare necesare depășește timpul net al conductei pentru a încărca un șir de containere specializate (cu gestionarea artefactelor) plus timpul meu pentru a dezvolta și gestiona o dependență a conductei sub forma unui container personalizat, atunci iau în considerare posibilitatea de a crea un container specific conductei.

  5. Prin acest proces apare și un principiu de echilibrare. Deoarece am făcut instalări în timp de execuție ca practică de dezvoltare, de fapt am făcut deja MVP ce ar trebui să fi instalat un container specific pipeline. Pot copia literalmente liniile de instalare într-un fișier Docker dacă doresc. De asemenea, pot observa dacă am comunități între mai multe conducte, unde este logic să creez un container utilitar cu mai multe conducte.

Într-un proiect recent, respectarea acestor principii m-a determinat să evit containerul skopeo și să îl instalez în containerul Bash 5 folosind un manager de pachete.

Dacă echipa dvs. este foarte interesată de Python sau PowerShell ca limbaj CI, ar fi logic să începeți cu versiunile recente ale acelor containere. Ideea nu este Bash avansat – ci o versiune avansată a limbajului dvs. general de scripting CI care vă împiedică să creați soluții în codul dvs. pentru probleme care sunt bine rezolvate în runtime disponibile public.

Rețineți că această ajustare este foarte, foarte concentrată pe containerele din conductele CI , care, prin natura lor, reflectă cerințele generale de procesare a calculului, în care sunt necesare multe operațiuni foarte diferite într-o conductă. Nu susțin această abordare pentru aplicațiile de microservicii adevărate în care, prin proiectare, un anumit serviciu are un scop și caracteristici foarte definite și, la scară, beneficiază masiv de eficiența mașinii a granularității hiperminimalizate, specifice scopului.

Euristice aplicate greșit

Adesea, atunci când un model are un punct de inflexiune în care devine un antimodel, se datorează aplicării greșite a euristicii tărâmului greșit. În acest caz, cred că modelele normale de containerizare pentru aplicațiile de microservicii sunt bine întemeiate, dar se aplică în mod restrâns „calculării hiperspecializate proiectate” a unei granule pe care o numim „un microserviciu” (rețineți că cuvântul „micro” se aplică domeniului de aplicare). a activităților de calcul). Important este că se aplică deoarece procesul în sine este conceput ca fiind hiper-specializat în jurul unei sarcini foarte specifice. Conținutul containerului (dependențele incluse), principiul imuabilității (fără modificare a timpului de execuție) și resursele de calcul ale timpului de execuție pot fi gestionate extrem de minim din cauza domeniului mic și foarte specific al activităților de calcul care au loc în cadrul procesului.

Aceasta este în esență întruchiparea principiului aplicației 12 factori numit „ VIII. Concurență ”, care afirmă că scalarea ar trebui să fie scalarea orizontală a aceluiași proces minimizat, nu scalarea verticală a resurselor de calcul în interiorul unui proces dat. Dacă sistemul experimentează de 10x lucrări pentru o anumită activitate, creăm 10 procese, nu solicităm 10x memorie și 10x CPU într-un singur proces care rulează. Arhitectura microserviciilor controlează strâns cantitatea de muncă din fiecare cerere, astfel încât să fie hiper-previzibilă în ceea ce privește cerințele de resurse de calcul și, prin urmare, scalabilă prin adăugarea de procese identice.

Calculul CI, prin natura sa, este opusul hiper-specializat. În construirea, testarea, pachetul, implementarea etc., etc., există multe variații uriașe ale resurselor necesare ale mașinii de memorie, CPU, I/O de rețea și acces la disc de mare viteză și, mai important, dependențe incluse. Natura de calcul generalizată apare și din cauza intrărilor diferite, astfel încât același proces definit ar putea avea nevoie de mult mai multe resurse datorită naturii datelor brute de intrare. De exemplu, variația volumului de intrare (de exemplu, mult față de puține articole de date) sau variația densității de intrare (de exemplu, procesarea fișierelor binare versus fișiere text).

Procesul care este containerizat este cel care deține atributul de calcul generalizat (burstful pe cel puțin unele resurse de calcul) sau hiper-specializat (definiție restrânsă a muncii de făcut și, prin urmare, resurse de calcul bine-cunoscute per unitate de muncă finalizată). Containerizarea unui proces care prezintă cerințe de calcul generalizate este utilă, dar planificarea resurselor acelui container ca și cum containerizarea acestuia ar fi transformat cerințele de calcul în hiper-minimalizate este punctul de inflexiune în care devine un antipattern, erodând de fapt beneficiile căutate pe care le avem. s-a pus să creeze.

În modelul pe care îl folosesc pentru valorificarea containerelor în CI, slăbirea principiilor de hiperspecializare, imutabilitate (instalări fără execuție) și resurse de calcul foarte înguste ale microserviciilor reflectă pur și simplu lumea reală în acel calcul CI în ansamblu prezintă natura de caracteristici de calcul generalizate, nu hiperspecializate.

Un alt domeniu în care acest lucru pare adevărat este tehnologiile de gestionare a configurației de stat dorite – cunoscute și sub denumirea de „Configurare ca cod”. Este foarte simplu dacă există componente sau rețete preexistente pentru tot ceea ce trebuie să faceți, dar de îndată ce trebuie să construiți unele pentru dvs., intrați într-o lume a creării de cod imperativ împotriva unei granițe API declarative (există „întruchiparea ” cortina – limita declarativă API). În general, dacă nu ați fost nevoit să implementați cod imperativ pentru a procesa declarativ, această lume nouă necesită o experiență semnificativă pentru a deveni competentă.

Noi euristici de luat în considerare

  1. În general, favorizați limitele simplității care vă reduc munca, mai ales în domeniul ridicării grele nediferențiate. În domeniul CI bazat pe container, aceasta include un limbaj de codare bogat și un manager de pachete pentru a dobândi o complexitate suplimentară care încorporează utilități rapid și ușor.

  2. În general, fiți suspicios față de un antipattern subiacent dacă trebuie să petreceți o cantitate excesivă de timp codând și menținând soluții de soluționare în serviciul simplității. În domeniul CI bazat pe containere, acestea ar fi containere care sunt ultra-minimalizate în jurul caracteristicilor de performanță ale microserviciilor atunci când nu se extind ca un serviciu permanent în CI.

  3. În general, retrageți-vă și examinați complexitatea netă a codului și a cadrelor care va trebui să fie întreținute de dvs. sau de echipa dvs. și verificați dacă ați făcut compromisuri care au un impozit net negativ asupra eficienței dvs. Când complexitatea care poate fi gestionată de mașini intră în spațiul dvs. de lucru cu frecvență înaltă, atunci aveți un antipattern masiv de eficiență umană.

  4. Este frecvent ca atunci când hueristicile aplicate creează eficiență umană negativă, ele creează și eficiență negativă a mașinii. Urmăriți acest efect în proiectele dvs. Diagrama din postare arată că containerele supra-minimalizate pot duce cu ușurință la utilizarea mult mai multe dintre ele – toate având și deasupra capului mașinii.

Dacă cele de mai sus rezonează, inginerii de conducte CI ar putea dori să ia în considerare slăbirea euristicii „microservicii” de hiperspecializare, ultra-minimalizare și imuabilitate (fără instalări dinamice) pentru containerele de conducte CI, pentru a se asigura că nivelul de complexitate net real al codul pe care trebuie să-l mențină este în echilibru și productivitatea lor este păstrată.

Anexă: Exemple de lucru ale acestei idei

  • AWS CLI Tools in Containers are atât Bash, cât și PowerShell Core (pe sistemul de operare Linux), astfel încât un set de containere să se potrivească preferințelor shell-ului de automatizare ale inginerilor de automatizare CI din moștenirea Linux și Windows.

  • Fișierul CI se instalează dinamic yq în containerul Bash, dar apoi instalează jq mai grele și skopeo numai dacă este nevoie de munca implicată, ceea ce demonstrează o modalitate de a fi mai eficient chiar și atunci când se dorește instalări în timpul execuției.

  • Biblioteci de coduri de script Bash și PowerShell în Pure GitLab CI YAML arată cum să aveți biblioteci de cod de script CI disponibile pentru fiecare container dintr-o conductă fără a încapsula bibliotecile într-un container și cu complexitate CI YAML minimizată în comparație cu ancorele, referințele sau extinderile YAML . În timp ce metoda este puțin dificilă de configurat, de atunci încolo se plătește prin decuplarea bibliotecilor de scripting de orice alt artefact.

  • Ci/CD Extension Freemarker File Templating arată că instalarea este foarte rapidă și afectează doar o lucrare și totuși versiunea fixează utilitarul instalat.

„Înțelegerea celei de-a doua legi a dinamicii complexității poate ajuta la reducerea complexității care vine odată cu căutarea simplității în conductele CI bazate pe containere.” – Darwin Sanoy

Faceți clic pentru a tweet