TSM - Configuration Manager: automatizarea operațiunilor între echipele interne

Vlad-Andrei Popica - Software Engineer @ ING Hubs România


În contextul tehnic actual, produsele software sunt rezultatul unui ecosistem intern de echipe, fiecare echipă deținând o piesă din puzzle, fiecare fiind nevoită să potrivească marginile propriei piese cu cele deja așezate, în aceleași culori, cu aceeași orientare și în aceeași ramă, pentru a obține imaginea de ansamblu.

Astfel, echipele sunt nevoite să conlucreze pentru a genera schimbări în interes comun: expunerea unui endpoint nou pe un domeniu, un nou topic pe clusterul de Kafka, ajustarea unei politici de acces, activarea unei configurări la deployment, aprobarea unui nou template pentru emailuri sau crearea unui nou dashboard de monitorizare a aplicației. În practică, acest lucru se traduce în multe interacțiuni îngreunate, mesaje pe chat, găsirea persoanelor de contact din alte echipe, tichete peste tichete, emailuri și ședințe adhoc; un efort de coordonare care crește exponențial în timp odată cu numărul de echipe și de servicii.

Aceste interdependențe produc câteva efecte sistemice costisitoare. Timpul de răspuns și planificarea devin imprevizibile, pentru că schimbările depind de disponibilitatea altei echipe. Calitatea scade deoarece configurarea și intervențiile manuale introduc erori insesizabile. Vizibilitatea și auditul sunt fragmentate, pentru că deciziile se pierd în discuții sau în tichete necorelate cu ceea ce s‑a executat efectiv. De asemenea, este mereu prezent un risc operațional și de securitate: cine a schimbat ce, când și de ce.

Mai jos voi descrie un cadru de guvernanță a configurărilor, un model conceptual, implementat și testat sub forma unei platforme software într-un context enterprise. În acest cadru, echipele unei companii își exprimă intenția de a schimba starea unui alt sistem (pe care nu îl dețin) într-un mod declarativ, aceste intenții sunt validate și verificate folosind politici (cine poate schimba, cu ce condiții, cu ce aprobări), apoi sunt transformate în evenimente care sunt executate automat, astfel schimbările sunt propagate în sistemul dorit.

Concept

Un astfel de sistem este inspirat după modelul closed-loop system și implicit, după conceptele ce stau la baza Kubernetes.

Sistemele în buclă închisă (closed-loop systems) provin din teoria sistemelor și au ca idee centrală un sistem autoreglabil bazat pe feedback, care compară în permanență starea dorită (desired state) cu starea actuală (actual state) a sistemului și aplică măsuri corective pentru a reduce eroarea până la convergența celor două stări. Un exemplu al unei bucle de control este setarea unei temperaturi în cameră folosind un termostat. În momentul în care o persoană setează temperatura dorită, termostatul primește desired state, în timp ce temperatura din cameră reprezintă actual state și va da o comandă de pornire echipamentului de încălzire până când camera ajunge la temperatura menționată.

Kubernetes transpune conceptul de closed-loop sistem într-o platformă pentru orchestrarea și managementul aplicațiilor containerizate. Configurarea în K8s se exprimă declarativ, prin aplicarea resurselor specifice (Pods, Namespaces etc.), iar platforma le va interpreta și execuția va fi distribuită în fiecare nod, urmând ca fiecare controller să urmărească starea curentă, intervenind în cazul în care starea actuală nu este cea declarată inițial prin specificațiile resurselor.

Configuration Manager propune tot un model de guvernanță și de automatizare a stărilor / configurărilor. Astfel preia conceptul declarativ și de reconciliere prezentat mai sus, însă îl aplică într-un context mult mai larg și generalizat, implementat printr-o platformă software. Față de K8s, nu se orchestrează doar un ecosistem de aplicații care rulează în containere și serviciile specifice din jurul acestora, ci conceptul se abstractizează pentru orice tip de stare, pentru o varietate mult mai largă de aplicații ale unei companii.

Design

Elementul principal al platformei îl reprezintă conceptul de resursă **(Resource)**, inspirat din mecanismul de extindere al Kubernetes prin ideea de CustomResources.

O resursă reprezintă o abstractizare, este o descriere declarativă a unei părți configurabile dintr-un produs, iar aceasta descrie starea dorită (desired state) a unei componente. Ca structură, o resursă cuprinde o secțiune de metadata, care conține identificatori și informații despre echipa (Consumers) care deține resursa respectivă și implicit dorește să creeze respectiva configurație pe produsul altei echipe (Producers). Principala secțiune este aceea de specificații (spec), unde se exprimă desired state, mai exact configurația / starea nouă a sistemului. O altă parte dintr-o resursă este cea de status, care reprezintă starea actuală a unui sistem (disponibilă după ce resursa a fost creată în cadrul platformei), astfel actorii platformei pot vizualiza în permanență diferența dintre specificațiile dorite (spec) și starea de fapt.
Structura unei resurse este validată și impusă printr-o definiție (ResourceDefinition) care exprimă exact tipul de resursă, schema care conține proprietățile acesteia și informații despre echipa care deține și administrează acel grup de resurse. Aici se stabilesc proprietățile relevante, ce câmpuri sunt obligatorii și ce combinații sunt permise. Prin versionarea definițiilor, pot coexista evoluții ale aceluiași tip, astfel încât resursele mai vechi rămân valide până la migrare.




























Fig.1. Exemple de Resource Definition și Resource de tip HelloWorld

Actorii principali ai platformei sunt două tipuri de echipe dintr-o companie: prima este cea de Producers - echipa care administrează un produs în cadrul companiei și care primește cereri constante de a schimba configurări de la alte echipe, astfel aceștia doresc să-și automatizeze procesele folosind ConfigurationManager; cea de-a doua este cu Consumers - echipa care este dependentă de produsul creat de producers și trebuie să interacționeze cu aceștia pentru sincronizare. Astfel, în cadrul platformei, producers vor crea resource definitions, reprezentând tipul și structura de configurare a produsului lor, la care toate resursele create de consumers trebuie să adere.

Pentru a exemplifica aspectele descrise mai sus pe un scenariu concret, putem porni de la cazul unui Kafka Cluster. În cadrul unei companii întâlnim frecvent situația în care mai multe echipe utilizează Kafka ca soluție pentru arhitecturi event‑driven; iar o echipă specializată (Team Kafka) are responsabilitatea de a administra infrastructura clusterului Kafka și de a furniza funcționalități sub formă de servicii.

O altă echipă care dezvoltă un modul de plăți din companie (Team Payments) are nevoie de un nou subiect (topic) de Kafka pentru publicarea tranzacțiilor finalizate generate de produsul lor. În locul unor interacțiuni între cele două echipe prin email, mesagerie sau sistemele de ticketing, Team Kafka creează un nou tip de resursă numit Topic, în care echipa stabilește, sub forma unui nou ResourceDefinition, ce informații sunt necesare pentru a configura automat un topic pe clusterul de Kafka: topicName, description, partitions, environment, cleanupPolicy, retentionPolicy, avroSchema. ( Sunt câteva exemple de proprietăți ce ar putea fi definite.) Astfel, Team Payments va folosi structura definită anterior și va putea aplica o nouă resursă de tip Topic care să reprezinte întocmai topicul cu proprietățile de care au nevoie pentru aplicația de plăți.

În schema de mai jos putem vedea o imagine de ansamblu a platformei și interacțiunea dintre componente. În centru se află serviciul principal - îl putem denumi ConfigManagerService - care are rolul de orchestrator al resurselor. Acesta este un serviciu care expune un API prin care se execută operațiile (creare, modificare, ștergere, vizualizare) pe resurse, dar și pe definițiile specifice. Prin urmare, interacțiunea cu sistemul se poate realiza printr-un CLI (asemenea kubectl) sau prin aplicații de frontend specializate. Astfel, ConfigManagerService primește și validează resursele pe baza definițiilor existente, le persistă într-o bază de date de resurse, aceasta reprezentând source of truth pentru starea actuală și starea dorită a componentelor.

Fig. 2. Arhitectura ConfigurationManager

Dar să presupunem un scenariu în care Team Kafka nu dorește ca toate echipele să aibă acces la crearea unui topic pe clusterul de Kafka, să aibă posibilitatea să ajusteze permisiunile doar pentru echipele din anumite departamente specifice. Astfel, ConfigManagerService poate fi integrat cu un policy engine, care să asigure guvernanța platformei printr-o serie de policies definite la nivelul fiecărui tip de resursă. Un exemplu open-source este OpenPolicyAgent, prin care politicile sunt exprimate în mod declarativ și decuplate de logica de implementare. În acest mod, Team Kafka creează un policy în care specifică exact departamentele care pot crea o resursă de tip Topic, iar în momentul în care resursa este creată de către Team Payments, informațiile echipei sunt verificate automat.

Dacă condițiile din policy sunt îndeplinite și resursa este validată, ConfigManagerService o persistă ca stare dorită și o publică spre execuție către controllerul responsabil. Similar cu modelul K8s, controllerul are rolul de a executa configurația exprimată prin resursă și de a menține desired state: citește specificația din resursă, compară cu starea reală și aplică schimbările necesare.
Responsabilitatea pentru implementarea acestui controller revine producers (în exemplu, Team Kafka). Aceștia furnizează logica care interpretează resursa și efectuează operațiile pe sistemul țintă (clusterul de Kafka pentru crearea topicului dorit), precum și evoluțiile ulterioare ale comportamentului. În funcție de scenariu (în exemplul menționat nu este neapărat necesar), controllerul poate implementa integral bucla de reconciliere: observă starea curentă, o compară cu specificația din resursă, calculează diferențele și aplică pașii necesari până la convergență, actualizând statusul și reluând procesarea ori de câte ori apar modificări sau condiții de reîncercare. Astfel, resursa devine contractul, iar controllerul asigură că sistemul ajunge și rămâne în starea dorită, fără intervenții manuale. După aplicarea resursei sau în orice moment al executării, controllerul trimite status înapoi la ConfigManagerService, iar resursa este actualizată și persistată cu starea actuală.

Modelul descris se poate extinde cu o varietate de alte concepte și integrări. Revenind la exemplu anterior, o altă echipă responsabilă de invoicing în companie dorește să consume tranzacțiile emise pe noul topic creat de Team Payments. În acest caz, funcționalitatea oferită de echipa care administrează clusterul de Kafka se poate extinde cu o nouă resursă de tipul TopicSubscription, care să permită unei echipe să consume evenimente de pe un topic specificat. Noua resursă nu dublează informația, ci reutilizează configurații existente, astfel se pot construi lanțuri de dependențe explicite între resurse, în care o resursă folosește specificații din alte resurse, iar controllerele rezolvă ordinea corectă de aplicare și verifică existența și compatibilitatea referințelor (Resource A --> Resource B --> Resource C).

Aceeași abordare permite și compunerea resurselor, crearea unei instanțe principale rezultă în multiple resurse generate (1 Parent Resource -> n Child Resources), toate derivate din specificația inițială și legate prin relații de proprietate. Un exemplu din zona infrastructurii este o resursă VirtualMachine, care poate genera resurse derivate de tipul Network, DiskSpace, Domain, CPU, Memory etc. Totodată, pentru aplicarea unor anumitor tipuri de resurse nu este de ajuns doar un set de politici și reguli, dar și de verificarea și aprobarea manuală a unei persoane că aceasta se poate executa. În consecință, poate fi nevoie de integrarea unui workflow de aprobare care să implementeze acest four-eyes principle.