În ultimii ani ideea de micro-servicii a devenit tot mai populară, dovada fiind numărul foarte mare de unelte care facilitează dezvoltarea și mentenanța unor astfel de servicii. În articolul de față, vom aborda subiectul HELM și cel referitor la o modalitate prin care putem să dezvoltăm rapid micro-servicii. Înainte de a sări direct în mijlocul procesului lui HELM, aș vrea să trecem puțin în revistă câteva concepte de bază. Sunt adeptul ideologiei că oricine este capabil să folosească orice unealtă sau limbaj de programare, câtă vreme are o bază solidă la nivel conceptual.
Termenul de micro-servicii are deja un deceniu, dar cred că merită menționat faptul că în momentul de față, ne referim de fapt la un model arhitectural - arhitectura bazată pe micro-servicii. Cred că cea mai simplă modalitate de a privi problema ar fi că acest model își propune să structureze o aplicație ca o colecție de servicii mai mici, care pot funcționa pe cont propriu. Modelul a apărut ca un răspuns pentru modelul foarte popular - aplicația Monolit.
Rețeta de succes a modelului bazat pe micro-servicii (MS) se bazează, în fapt, mai mult pe niște concepte și principii prin care organizăm și construim serviciile ce constituie aplicația - de aici și încrederea mea în cele spuse mai sus. Să le luăm pe rând.
Privind la un nivel macro, trebuie doar să avem în vedere că serviciile trebuie grupate într-o formă logică, ușor de urmat și ușor de duplicat. Există la ora actuală o multitudine de tipare bune pentru organizarea MS, iar printre cele mai populare se numără organizarea după capabilitățile produsului/aplicației și organizarea în subdomenii (DDD - Domain Driven Design). Cea din urmă propune să privim produsul ca un domeniu pe care îl vom împărți in sub-domeniile afacerii urmând o categorisire simplă a acestora în CORE, SUPPORT respectiv, GENERIC.
Pentru partea de "construcție" a MS avem nevoie de puțin mai multă disciplină în a urma câteva specificații cheie. În primul rând, trebuie să conferim o capacitate de testare a acestor servicii la nivel individual. În al doilea rând, este necesar ca aceste servicii să poată fi lansate independent - ceea ce ne conduce la ultimul punct: micro-serviciile nu ar trebui să aibă inter-dependențe. Desigur, sunt cazuri în care acest ultim punct este destul e dificil de obținut, moment în care ne concentrăm puternic să le obținem pe primele două. Un mijloc facil prin care putem scăpa de multe ori de nevoia unui serviciu de a fi conștient de existența altora este standardizarea datelor. Un model standardizat de date la nivelul întregului produs ne scapă de grija interpretărilor fiecărui serviciu. Cred cu tărie că aici este punctul critic al acestui model - la eliminarea dependențelor. Dacă acest pas nu se poate parcurge cu succes, riscăm sa creăm un monolit distribuit în care nu ne mai bucurăm de avantajele monolitului și ne lovim de toate problemele impuse de MS, deoarece nu avem acea independență a serviciilor.
Pentru cei care urmează cu succes principiile listate mai sus, există o serie de stimulenți destul de avantajoși la finalul zilei. Am să trec în revistă cele mai relevante avantaje din punctul meu de vedere.
Unul din cele mai râvnite obiective în industrie la ora actuală este tranziția către continuous deployment (CD). O arhitectură bazată pe micro-servicii ne va asigura o cale ușoară către obținerea acestuia fiind un principiu fundamental. Acest aspect combinat cu ușurința testării ne va permite efectuarea schimbărilor în mod frecvent asupra serviciilor. În cele din urmă, pe această pistă, ajungem să eliminăm nevoia de a adopta un tech stack pe perioade lungi. Avem practic posibilitatea de a împrospăta sau chiar înlocui tehnologiile folosite la nivelul unui micro-serviciu cât de frecvent este nevoie.
Beneficiem de câteva mari plusuri în cadrul echipelor de developeri - procesul de on-boarding devine mai scurt în momentul în care un serviciu este dezvoltat de o singură echipă. Totodată, timpul de dezvoltare devine mai scurt și procesul mai eficient.
Nu în ultimul rând și, poate, cel mai important avantaj, este capacitatea de a izola problemele. Dacă apar probleme sau erori, acestea nu vor înceta activitatea întregii aplicații ci doar a serviciului în cauză. Aici ne putem aminti cazurile clasice din monoliți unde problemele unor funcționalități folosite sporadic aveau capacitatea de a sista procesele unei întregi aplicații.
Acum să trecem și la partea mai puțin plăcută. Cu toate că modelul tinde să rezolve multe din problemele întâmpinate în alte arhitecturi, trebuie să avem în vedere că ridică o serie de probleme noi. Complexitatea proceselor de deploy va crește, iar consumul de resurse computaționale poate crește destul de mult odată cu implementarea acestui model.
Alături de o serie de beneficii și avantaje pentru developeri , apar și o serie de provocări. Testarea end-to-end a interacțiunilor dintre MS devine automat un proces mai dificil. Implementarea unor schimbări care se ating mai multe servicii va impune forme noi de coordonare între echipe și ingineri. Pe lângă toate acestea, un developer va trebui să aibă în vedere o pătură care într-un monolit e asigurată implicit - comunicarea între servicii. Deoarece MS sunt cât se poate de independente, apare nevoia de a dezvolta activ un mecanism pentru comunicarea între servicii.
Un alt aspect important de avut în vedere este consecvența datelor. Transpunerea datelor printr-un număr ridicat de pături ale aplicației poate aduce cu sine multe probleme din cauza multiplelor pasări și procesări prin care un set de date va trece.
Acum că suntem în temă cu specificul micro-serviciilor, putem să discutăm despre unelte. Totodată, vreau să subliniez faptul că hopul cel mai greu este acela de a proiecta micro-serviciile. Uneltele precum HELM fac implementarea să fie floare la ureche.
Pornim cu începutul și expunem succint o caracterizare a Kubernetes : este un software de orchestrare de containere, adică un mediu prin care putem crea și menține un număr mare de containere înglobate în kubernetes clusters. Kubernetes a crescut foarte mult în popularitate în ultimii ani și cred că este cel mai căutat skill la momentul actual pe piață.
HELM este software prin care putem instala și menține aplicații și containere în interiorul unor kubernetes clusters. Dacă ne gândim la aplicația noastră ca fiind o prăjitură, Kubernetes devine blatul, micro-serviciile vor fi crema servită pe acel blat, iar HELM este clar ce poziție ocupă în analogia asta, nu? E mixerul cu care am făcut crema. Simplu!
Revenind pe meleagurile IT-ului, putem afirma că HELM este un fel de package manager pentru Kubernetes. Prin intermediul HELM putem să creăm un micro-serviciu, să-l instalăm într-un cluster și ulterior să îl îmbunătățim/îmbogățim, fiecare prin câte o singură comandă.
În mod practic, HELM este compus din două componente: un client și o librărie. Clientul are o interfață CLI și îl folosim pentru a transmite comenzi. Librăria conține toată logica de execuție și interacționează cu Kubernetes prin kubernetes API. Ea nu are nevoie de o bază de date pentru a stoca informații, se folosește în schimb de kubernetes secrets pentru a menține informațiile relevante.
Cele două componente sunt scrise în GO, însă fișierele pe care le vom folosi să definim micro-serviciile noastre sunt scrise într-un limbaj mult mai simplu și mai familiar - YAML.
Simplitatea lui HELM devine evidentă în momentul în care ne uităm la conceptele cheie și la parcursul unui micro-serviciu în contextul HELM. Conceptele despre care vorbesc sunt HELM Chart, HELM Config si HELM Release. Un Chart conține informațiile necesare pentru a crea o aplicație kubernetes; un Config conține informații adiționale despre Chart și comportamentul acestuia; un Release le combină pe cele două menționate anterior pentru a instala aplicația în contextul kubernetes.
În imagine putem observa cum parcursul de la cod la micro-serviciu funcțional devine extraordinar de simplu cu ajutorul HELM. Ultimul pas, cel de helm upgrade, reprezintă manipularea unui Release după ce acesta a fost instalat în Kubernetes. Dacă există modificări la nivelul imaginii docker inclusă în Chart putem foarte ușor prin această comandă să aducem noutățile în Kubernetes. Similar, dar pentru un scenariu în care vrem să revenim la o versiune anterioară, putem folosi o comandă de helm rollback.
Ca să aprofundăm puțin și partea esențială a procesului, codul sursă, am să împărtășesc un exemplu real de HELM Chart. Practic, sunt două Charturi, dar rezultatul final este un micro-serviciu de front-end (FE).
Avem un Chart bază numit generic frontend pe care îl putem folosi cu orice container de front-end. Practic acest Chart e construit ca o "matriță" care instalează o serie de servicii tipice aplicațiilor de FE și suportă, prin variabile, instalarea oricărei imagini de docker ca punct central al micro-serviciului.
frontend/
├── Chart.yaml
├── README.md
├── templates
│ ├── NOTES.txt
│ ├── _helpers.tpl
│ ├── deployment.yaml
│ ├── http-service.yaml
│ ├── ingress.yaml
│ ├── metrics-service.yaml
│ └── servicemonitor.yaml
└── values.yaml
Structura unui Chart se poate desprinde de mai sus. Minim, avem nevoie de un fișier de variabile (values.yaml), un fișier prin care să definim Chart-ul numit Chart.yaml și un director numit templates în care vom defini serviciile pe care le dorim. În exemplul nostru, vom defini detaliile despre imaginea docker și informațiile necesare acesteia în deployment.yaml. Pentru aplicațiile noastre de FE, avem nevoie și de un serviciu de kubernetes de tip http și de un serviciu de ingress pentru a direcționa traficul către container. Ultimele două servicii definite sunt direcționate către monitorizarea serviciilor pe care le instalăm și a pod-ului. Toate fișierele din templates vor trece prin GO template rendering engine pentru a crea versiunile finale - lucru ce ne permite să păstrăm totul pregătit pentru a fi definit în momentul în care utilizăm acest Chart de bază.
Luăm serviciul de http ca un exemplu:
Templates/http-service.yaml
apiVersion: v1
kind: Service
metadata:
name: "{{ template "app.fullname" . }}-http"
labels:
app: {{ template "app.name" . }}
chart: {{ .Chart.Name }}-
{{ .Chart.Version | replace "+" "_" }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
type: {{ .Values.http.service.type }}
ports:
- port: {{ .Values.http.service.externalPort }}
targetPort:{{.Values.http.service.internalPort }}
protocol: TCP
name: "{{ template "app.fullname" . }}-http"
selector:
app: {{ template "app.name" . }}
release: {{ .Release.Name }}
Acum, mai rămâne doar să utilizăm frontend pentru a instala aplicația noastră. Definim un Chart nou care va apela frontend pentru a fi utilizat. Pentru a realiza acest lucru, definim un fișier numit requirements.yaml la rădăcina structurii de fișiere.
dependencies:
- name: frontend
version: 0.3.0
repository: https://url-to-charts.repo.me
În final, e nevoie doar să oferim o valoare reală variabilelor de care avem nevoie. În mod practic, va trebui să definim numele și versiunea imaginii docker, variabilele necesare altor aplicații pentru interacțiunea cu front-endul nostru și detaliile pentru ingress.
frontend:
replicaCount: 1
nameOverride: my-first-fe
image:
repository: docker-registry-url
tag: ${IMAGE_TAG}
pullPolicy: Always
env:
- name: KEYCLOAK
value: "https://my-website.example.com"
ingress:
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: 512m
nginx.ingress.kubernetes.io
/proxy-connect-timeout: "15"
nginx.ingress.kubernetes.io
/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io
/proxy-send-timeout: "600"
enabled: true
hosts:
- "*.app.my-website.com"
tls:
- hosts:
- "*.app.my-website.com"
secretName: "app-my-website-com-tls"
La final, rămâne doar să urmăm parcursul descris la începutul discuției despre HELM și să împachetăm și să instalăm noul Chart.
Sper că această scurtă incursiune în lumea HELM a reușit să vă trezească interesul cu privire la puterea uneltelor și proceselor prin care se pot dezvolta micro-servicii. Sunt de părere că aceste unelte ocupă un loc extraordinar de important în industrie deoarece ne ajută să ne apropiem mult mai mult de acea cultură DevOps, pe care o urmărim cu toții atât de pasionați. Spun aceasta pentru că principala barieră pe care am observat-o în rândul developerilor la adoptarea ideologiilor DevOps este intimidarea adusă de infrastructură și complexitatea dezvoltării acesteia. HELM vine cu un răspuns simplu la această problemă, asigurând multitudinea de detalii de infrastructură și procese/definiții în kubernetes fără prea mult efort. Așadar, uneltele de acest tip vin să deschidă niște porți către o lume în care un developer poate fi mai autonom și totodată mai performant în procesul de creație.