Modulul 7 a fost despre practicile care transformă arhitectura în sisteme care rulează. Strategii de branching, trunk-based development, CI, CD, infrastructure as code: fiecare lecție a adăugat un strat la imaginea felului în care o echipă livrează efectiv software. Lecția asta adaugă stratul de runtime pe care trăiesc majoritatea platformelor de date moderne, fie că echipa l-a ales deliberat sau l-a moștenit de la restul companiei. Kubernetes.
Încadrarea contează. Kubernetes nu e o unealtă de date. E un orchestrator distribuit de uz general care planifică containere pe o flotă de mașini. Inginerii de date ajung să-l folosească pentru că restul organizației o face deja, pentru că ecosistemul cloud-native a converger spre el și pentru că alternativele (administrează-ți propriile VM-uri, lipește la un loc servicii specifice cloud-ului, rulează pe o platformă Spark gestionată precum Databricks sau EMR) au costurile lor. Întrebarea corectă e rar „ar trebui să folosim Kubernetes”. Întrebarea corectă e „dat fiind că Kubernetes e substratul, cum rulăm bine workload-uri de date pe el și la ce ne înrolăm”.
Lecția asta parcurge întrebarea în trei părți: la ce e bun Kubernetes, la ce e rău și ce-l face alegerea necesară pentru majoritatea echipelor de date medii spre mari. Secțiunea din mijloc acoperă operator pattern și cele două integrări care contează cel mai mult în practică, Spark pe Kubernetes și Airflow pe Kubernetes.
Ce este de fapt Kubernetes
Dă la o parte marketingul și Kubernetes e un set de primitive puse cap la cap cu grijă.
Un pod e unul sau mai multe containere planificate împreună pe același nod. Pod-urile sunt unitatea de deploy. Împart networking-ul și o cantitate mică de stocare locală.
Un node e o mașină (de obicei un VM) care rulează pod-uri. Un cluster e un set de noduri plus un control plane care decide unde se planifică pod-urile, le repornește când eșuează și expune API-urile cu care vorbește restul sistemului.
Un deployment e o declarație de stare dorită: „vreau trei replici ale acestui pod, cu această imagine, această alocare de CPU și memorie, această variabilă de mediu”. Predai deployment-ul către cluster. Treaba clusterului e să facă realitatea să corespundă declarației. Dacă un pod crapă, clusterul pornește unul nou. Dacă schimbi imaginea, clusterul rulează versiunea nouă în timp ce o ține în viață pe cea veche destul de mult cât să eviți downtime-ul.
Un service e o adresă de rețea stabilă în fața unui set de pod-uri. Pod-urile vin și pleacă; service-ul rămâne. Clienții vorbesc cu service-ul, service-ul face load-balancing peste pod-uri.
Modelul e declarativ până jos. Descrii ce vrei; clusterul reconciliază spre el. Bucla aceea de reconciliere e nucleul Kubernetes și e și nucleul operator pattern, locul unde intră în scenă tooling-ul de date.
Ce e bun
Standardizare între medii. Un cluster Kubernetes pe AWS (EKS), Google Cloud (GKE), Azure (AKS) sau on-premises arată la fel din punct de vedere al deployment-ului. Același YAML merge în toate, cu un strat mic de diferențe specifice cloud-ului în load balancere, storage classes și IAM. Echipele care au trecut printr-o migrație multi-cloud sau cloud-spre-on-prem știu cât de rară e astfel de portabilitate.
Auto-scaling. Kubernetes poate scala pod-uri pe baza CPU-ului, memoriei sau metricilor custom prin Horizontal Pod Autoscaler. Clusterul însuși poate scala adăugând noduri prin Cluster Autoscaler sau Karpenter. Pentru workload-uri de date cu vârfuri (un job Spark care are nevoie de două sute de executori timp de zece minute și apoi de nimic) elasticitatea asta e povestea operațională care face clusterele per-job accesibile.
Self-healing. Un pod care crapă e replanificat automat. Un nod care devine inaccesibil are pod-urile evacuate și replanificate în altă parte. Sistemul absoarbe o clasă de eșecuri care, într-un setup de VM-uri făcut manual, ar suna pe cineva la trei dimineața.
Operator pattern. Kubernetes te lasă să-ți definești propriile resurse custom și să scrii un controller (un operator) care le urmărește și reconciliază spre starea dorită. Asta e ceea ce transformă Kubernetes dintr-un planificator de containere într-o platformă pentru construirea de abstracțiuni de nivel mai înalt. O resursă SparkApplication, o resursă PostgresCluster, o resursă KafkaTopic: fiecare e un kind custom pe care un operator știe să-l materializeze în pod-uri, servicii și configurație. Din perspectiva utilizatorului, creezi un SparkApplication; operatorul se ocupă de driver pod, executor pods, service accounts, configurația de logging, curățenia la finalizare.
Ecosistem. Fiecare unealtă de infrastructură modernă vine cu o integrare Kubernetes, de obicei un Helm chart. Prometheus, Grafana, Jaeger, ArgoCD, Flux, Vault, cert-manager, external-dns: stiva cloud-native standard se compune peste Kubernetes. O echipă care adoptă Kubernetes moștenește implicit un toolchain matur.
Ce e rău
Complexitate operațională. Rularea unui cluster Kubernetes sănătos e o slujbă. Control plane-ul are propria bază de date (etcd), propriul strat de networking, propria rotație de certificate, propria poveste de upgrade. Chiar și echipele pe oferte gestionate (EKS, GKE, AKS) petrec timp serios cu operațiunile de cluster: dimensionarea node group-urilor, upgrade-urile de versiune, alegerile de plugin de rețea, integrarea IAM. O echipă de date care împarte o echipă de platformă cu restul companiei e ok. O echipă de date care își rulează propriul cluster dedicat trebuie să-și bugeteze costul operațional.
Networking. Pod-la-pod, pod-la-service, ingress, egress, network policies, service mesh. Fiecare e propriul subiect. Debug-ul unei conexiuni care ar trebui să meargă dar nu merge înseamnă să înțelegi care strat e responsabil: plugin-ul CNI, definiția service-ului, network policy, ingress controller, load balancer-ul de cloud, rezolvarea DNS. Majoritatea inginerilor de date învață părțile pe care le ating și evită restul.
Proliferare YAML. O singură aplicație ar putea avea nevoie de un deployment, un service, un config map, un secret, un ingress, un horizontal pod autoscaler, un service account și un role binding. Astea sunt șapte fișiere YAML pentru un hello-world. Helm charts și Kustomize există ca să gestioneze proliferarea și ajută, dar adevărul de bază e că Kubernetes e verbos. Echipele care scriu YAML brut se îneacă. Echipele care adoptă din timp o unealtă de templating rămân la suprafață.
Contabilitatea resurselor. Fiecare pod declară requests de CPU și memorie (cantitatea pe care planificatorul o rezervă) și limits (maximul pe care pod-ul îl poate folosi înainte să fie throttled sau ucis). Setarea acestora corect e o disciplină. Setezi requests prea mici și pod-urile sunt planificate pe noduri suprasolicitate și devin vecini gălăgioși. Le setezi prea mari și clusterul rămâne fără loc în timp ce nodurile stau goale. Setezi limits prea mici și kernelul îți ucide executorul Spark în mijlocul unui shuffle cu o eroare de out-of-memory care, din perspectiva aplicației, arată ca o cădere de nod. Valorile potrivite vin din observație și iterație; valorile implicite sunt aproape întotdeauna greșite.
Ce e necesar: tooling-ul de date pe Kubernetes
Motivul pentru care majoritatea echipelor de date ajung pe Kubernetes nu e entuziasmul pentru platformă. E faptul că uneltele de date pe care vor să le ruleze au integrări de prim rang cu Kubernetes, și acele integrări sunt acum mai bune decât alternativele.
Spark pe Kubernetes. Kubernetes Operator for Spark (originar de la Google, acum menținut de comunitate) e modul modern de a rula Spark. Fiecare job Spark devine o resursă custom SparkApplication. Operatorul creează un driver pod, driver-ul cere executor pods de la cluster, executorii rulează munca, iar la finalizare totul e curățat. Job-ul a rulat pe un cluster efemer de pod-uri, pod-urile sunt duse, iar clusterul recuperează capacitatea. Ăsta e modelul „cluster per-job” care era stângaci de obținut cu YARN și e acum implicit cu Kubernetes.
Avantajele se compun. Izolarea resurselor e per-pod. Versiuni diferite de Spark pot rula una lângă alta. Aceeași stivă de observabilitate care urmărește restul clusterului urmărește și job-urile Spark. Atribuirea costurilor e per-namespace. Eșecurile sunt eșecuri de pod, cu aceeași poveste de restart și replanificare ca toate celelalte.
Airflow pe Kubernetes. Airflow are două integrări relevante. KubernetesExecutor rulează fiecare task în propriul pod, în loc de un proces de worker în interiorul unui worker Celery cu durată lungă. Asta dă izolare de resurse per-task, scalare per-task și flexibilitate de imagine per-task (un task poate specifica imaginea de container de care are nevoie, ceea ce decuplează upgrade-urile Airflow de upgrade-urile codului de task). KubernetesPodOperator e mai general: e un operator Airflow care lansează un container arbitrar, ceea ce-i permite unui DAG să orchestreze orice unealtă cu o imagine Docker, nu doar lucruri cu operatori Airflow nativi.
Combinația e puternică. Un DAG Airflow care rulează pe KubernetesExecutor poate folosi KubernetesPodOperator ca să lanseze un job Spark (prin Spark Operator), un dbt run, un script Python și un job Flink, fiecare în propriul container cu propriul buget de resurse, toate orchestrate ca un workflow Airflow normal.
Argo Workflows. Un motor de workflow nativ Kubernetes, o alternativă la Airflow pentru echipele care vor ca orchestrator-ul lor să fie la fel de nativ Kubernetes ca restul stivei. Fiecare pas într-un workflow Argo e un pod. DAG-ul e o resursă custom. Totul reconciliază ca toate celelalte din cluster. Argo e alegerea pentru echipele care găsesc modelul Python-first al Airflow inutil și vor workflow-urile lor definite în YAML alături de celelalte resurse Kubernetes.
Flink pe Kubernetes. Același pattern ca la Spark. Flink Kubernetes operator gestionează resurse FlinkDeployment. Fiecare deployment e un cluster Flink de pod-uri. Job-urile de streaming cu durată lungă se mapează pe deployment-uri cu durată lungă; job-urile batch ad-hoc se mapează pe unele cu durată scurtă.
Pattern-ul e consecvent. Uneltele de date au căzut de acord că Kubernetes e substratul și au construit operatori care transformă primitivele native Kubernetes în abstracțiunile de workload care le pasă inginerilor de date. Câștigul arhitectural nu e Kubernetes în sine; e că uneltele au converger spre un runtime comun.
Un cluster Kubernetes tipic pentru date
flowchart TB
subgraph cluster[Kubernetes cluster]
subgraph ns_airflow[Namespace: airflow]
AF_WEB[Webserver]
AF_SCHED[Scheduler]
AF_TASK[Task pods<br/>per DAG task]
end
subgraph ns_spark[Namespace: spark-operator]
SO[Spark Operator]
SD[Driver pods]
SE[Executor pods]
end
subgraph ns_mon[Namespace: monitoring]
PROM[Prometheus]
GRAF[Grafana]
LOKI[Loki]
end
subgraph ns_jobs[Namespace: data-jobs]
DBT[dbt run pods]
PY[Python job pods]
end
end
AF_SCHED --> AF_TASK
AF_TASK --> SO
AF_TASK --> DBT
AF_TASK --> PY
SO --> SD
SD --> SE
PROM -.scrapes.-> AF_SCHED
PROM -.scrapes.-> SD
PROM -.scrapes.-> SE
GRAF --> PROM
Diagramă de creat: o versiune îngrijită a layout-ului de cluster de mai sus, cu patru namespace-uri delimitate clar, săgețile de control flow de la Airflow la pod-urile de workload, Spark Operator-ul care materializează driver și executor pods, și namespace-ul de monitorizare care strânge metrici de la toate. Punctul vizual e că namespace-urile sunt unitatea de separare și că operator pattern e ce face pod-urile de workload să apară și să dispară.
Forma se generalizează. Majoritatea clusterelor Kubernetes pentru date arată așa: câteva namespace-uri pentru componentele de platformă (Airflow, Spark Operator, monitorizare, uneori Argo sau Flink), unul sau mai multe namespace-uri pentru workload-urile efective și un control flow care merge de la Airflow (sau Argo) prin operatori, care la rândul lor creează pod-urile de workload. Echipa de platformă deține namespace-urile de platformă. Inginerii de date lucrează în namespace-urile de workload.
Când să NU folosești Kubernetes
Trei cazuri în care Kubernetes e răspunsul greșit.
Workload-uri pe un singur VM. Un job mic de ETL care rulează o dată pe zi pe o singură mașină nu are nevoie de un orchestrator care planifică containere pe o flotă. O intrare cron pe un VM, sau o funcție serverless, e mai simplă și mai ieftină. Kubernetes adaugă complexitate operațională care se amortizează doar când ai destule workload-uri și destulă scară ca s-o amortizezi.
Echipe minuscule fără infra dedicată. O echipă de date de două persoane fără suport de platformă o să-și petreacă o treime din timp cu operațiunile de cluster dacă rulează propriul Kubernetes. Aceeași echipă pe o platformă Spark gestionată (Databricks, Snowflake, BigQuery cu Dataform) schimbă cost-per-job pentru aproape zero muncă de operațiuni. Schimbul ăla e de obicei corect la scară mică.
Workload-uri cu echivalente serverless. AWS Lambda, Google Cloud Run, AWS Batch și Azure Container Instances există dintr-un motiv. Un workload care se încadrează în constrângerile lor (mărime, runtime, toleranță la cold-start) e adesea mai ușor de rulat pe ele decât pe Kubernetes. Regula de degetul mare arhitecturală: Kubernetes e pentru workload-uri care beneficiază de un cluster; serverless e pentru workload-uri care nu.
Sumarul cinstit
Majoritatea echipelor de date din companiile medii spre mari ajung pe Kubernetes fie că vor sau nu. Restul companiei rulează acolo. Echipa de platformă îl suportă. Ecosistemul cloud-native îl presupune. Uneltele de date au integrări de prim rang pentru el. Să lupți cu curentul ăla rar merită.
Mișcarea corectă, dat fiind asta, e să te aplici pe uneltele standard în loc să reinventezi. Folosește Spark Operator în loc să-ți scrii propriul glue de submit Spark-on-k8s. Folosește KubernetesExecutor pentru Airflow în loc de CeleryExecutor. Folosește o ofertă gestionată (EKS, GKE, AKS) în loc să rulezi propriul control plane. Adoptă din timp o unealtă de templating (Helm, Kustomize). Setează corect requests și limits de resurse și revizitează-le când workload-ul se schimbă.
Scopul e același pe care Modulul 7 l-a construit pe parcurs: fă lucrul corect să fie lucrul ușor. Kubernetes adaugă un cost initial abrupt și se întoarce cu o coadă plată lungă de beneficii operaționale. Pentru majoritatea echipelor de date la scară, schimbul merită; pentru echipe mici, de obicei nu. Să știi pe care parte a liniei ești tu e decizia arhitecturală spre care restul lecției s-a pregătit.
Lecția următoare închide Modulul 7 cu un studiu de caz: pipeline-ul de deployment al Stripe și ce dezvăluie practicile lor de inginerie publicate despre CI/CD când sistemul gestionează bani reali la scară. Modulul 8 va începe cu o privire mai adâncă asupra orchestrării, cu Airflow ca exemplu fir-roșu și Kubernetes ca substrat dedesubt.