L’infrastruttura di calcolo di SMARTOUR ospita software prodotto da partner diversi, con cicli di rilascio scollegati tra loro: abbiamo scelto di isolare ogni partner in uno spazio dedicato e di dichiarare l’intera piattaforma come codice. Il vincolo era questo fin dall’inizio, e ha condizionato ogni decisione successiva sullo stack.

Contesto

SMARTOUR è un progetto di ricerca in cui più organizzazioni consegnano deliverable software che devono convivere sulla stessa infrastruttura. Un partner sviluppa un servizio di geolocalizzazione, un altro un’API per i dati territoriali, un terzo un frontend per la pubblica amministrazione. Nessuno conosce in anticipo i requisiti di runtime degli altri, e i tempi di consegna non coincidono.

Un host condiviso tradizionale, con i servizi che girano sullo stesso sistema operativo, avrebbe imposto un coordinamento continuo sulle versioni di librerie, runtime di linguaggio e dipendenze di sistema. La containerizzazione toglie quel coordinamento alla radice: ogni deliverable arriva con le proprie dipendenze impacchettate, e all’host non resta che eseguire l’immagine. Il formato del container segue le specifiche dell’Open Container Initiative — image-spec e runtime-spec, alla versione 1.0 dal 2017 — così non ci si lega al formato proprietario di un singolo runtime.

Architettura

Lo strato di orchestrazione è Kubernetes, alla 1.18 uscita a marzo 2020 e descritta dai maintainer come una release di rifinitura più che di nuove funzioni. Non l’abbiamo scelto per la novità: Kubernetes offre un modello dichiarativo in cui lo stato desiderato del sistema è un insieme di oggetti YAML versionabili, e un control loop riconcilia di continuo lo stato reale verso quello dichiarato. Per un’infrastruttura che a fine progetto deve poter essere ricostruita da terzi, questa proprietà pesa più di qualsiasi singola funzione.

Sopra Kubernetes abbiamo messo Rancher 2.4 per la gestione multi-cluster. Rancher 2.4 gestisce migliaia di cluster da un solo control plane e introduce CIS Scan, che verifica i cluster RKE rispetto ai benchmark del Center for Internet Security. In SMARTOUR non servono migliaia di cluster, ma serve un punto unico da cui assegnare i namespace, governare i ruoli con il controllo degli accessi basato sui ruoli (RBAC, role-based access control) e dare a ogni partner una vista ristretta al proprio spazio senza concedergli l’intero cluster.

L’isolamento per partner poggia su due primitive native di Kubernetes:

  • il Namespace, che separa logicamente le risorse e fa da scope per nomi, quote e policy;
  • la NetworkPolicy, che limita il traffico tra pod in base a selettori di etichette, così il servizio di un partner non raggiunge quello di un altro senza un permesso esplicito.

A queste affianchiamo ResourceQuota e LimitRange, per evitare che un deployment mal configurato si mangi le risorse degli altri. È un isolamento a livello di software, non di kernel: i container condividono il kernel del nodo, e su questo limite torno più avanti.

La pipeline è GitLab CE. La 13.0, uscita il 22 maggio 2020, porta il registry dei container integrato e Auto DevOps. Ci interessa tenere tre cose in un unico strumento self-hosted: repository Git, registry privato delle immagini e CI/CD. Un partner fa push del codice, la pipeline costruisce l’immagine, la pubblica nel registry interno, e il deployment su Kubernetes legge da lì. Nessuna immagine passa per servizi esterni, e questo semplifica il modello di fiducia su un’infrastruttura che ospita codice di terzi.

Per il packaging dei deployment usiamo Helm 3, uscito a novembre 2019. La versione 3 toglie Tiller, il componente server-side che in Helm 2 girava nel cluster con privilegi ampi: ora Helm rende i chart lato client e si appoggia direttamente all’RBAC del kubeconfig. Su un cluster condiviso tra partner, togliere un componente con permessi estesi a tutto il cluster riduce la superficie d’attacco in modo concreto.

L’osservabilità è Grafana, con le metriche raccolte da Prometheus. Per i log centralizzati abbiamo scelto Loki, il sistema di aggregazione di Grafana Labs che indicizza solo un insieme di etichette per stream invece dell’intero contenuto: su un cluster multi-partner questo tiene basso il costo di storage dei log e lascia comunque filtrare per namespace, quindi per partner.

Il punto critico

La parte che ha richiesto più attenzione è il confine di sicurezza tra partner, più dell’orchestrazione in sé. Namespace e NetworkPolicy isolano le risorse logiche e il traffico di rete, ma i container di tutti i partner condividono lo stesso kernel del nodo. Una vulnerabilità di container escape — la classe di bug per cui un processo dentro il container raggiunge l’host — rende permeabile l’intero isolamento.

La mitigazione è difensiva e a più strati: PodSecurityPolicy per vietare container privilegiati e l’uso di hostPath, immagini costruite su base minimali per ridurre la superficie, e separazione dei workload sui nodi con taint e toleration quando un partner ha requisiti di isolamento più stringenti. Nessuna di queste misure elimina la condivisione del kernel; ne abbassano la probabilità di sfruttamento. Per un isolamento a livello di kernel servirebbero sandbox come gVisor o macchine virtuali leggere come Kata Containers: le abbiamo valutate e scartate, per non scaricare altra complessità operativa su un team piccolo.

Implicazioni

Lo stack è tutto open source e auto-ospitato, e questo è un requisito del progetto più che una preferenza. Un deliverable di ricerca finanziato deve poter essere ricostruito e verificato da terzi dopo la consegna: ogni componente con licenza proprietaria o dipendente da un servizio cloud chiuso sarebbe un ostacolo alla riproducibilità. Kubernetes, Rancher, GitLab CE, Helm, Prometheus, Grafana e Loki hanno licenze che ne consentono il riuso e la ridistribuzione, e l’hardware è on-premise.

La conseguenza operativa è che la responsabilità di sicurezza e aggiornamento ricade tutta sul team, senza la copertura di un provider gestito. Gli aggiornamenti di Kubernetes seguono una cadenza di tre release minori l’anno, ciascuna con un orizzonte di supporto limitato, e questo impone un piano di manutenzione da dimensionare fin dall’inizio.

Limiti

Lo stato attuale è prototipale. L’isolamento per namespace basta per partner che collaborano dentro lo stesso progetto, ma non equivale a un confine multi-tenant tra parti che non si fidano l’una dell’altra. Il control plane di Kubernetes resta un singolo punto di fallimento finché non è replicato, e in questa fase non gira in alta affidabilità. La raccolta dei log con Loki è recente e la sua maturità operativa va ancora verificata sotto carico reale. Sono scelte coerenti con un deliverable di ricerca, non con un servizio in produzione continua, e la distinzione va tenuta esplicita.

L’evoluzione di questa piattaforma verso un’architettura cloud-native è ripercorsa in un insight di noze sul percorso di SMARTOUR verso Kubernetes: https://www.noze.it/insights/smartour-kubernetes/.


https://kubernetes.io/blog/2020/03/25/kubernetes-1-18-release-announcement/ https://www.infoq.com/news/2020/04/rancher-kubernetes-clusters/ https://about.gitlab.com/releases/2020/05/22/gitlab-13-0-released/ https://helm.sh/blog/helm-3-released/ https://github.com/opencontainers/image-spec

Immagine di copertina: Logo di Kubernetes: una ruota di timone navale stilizzata a sette raggi, bianca su sfondo blu esagonale — logo di Google, Inc., CC BY 4.0 — https://commons.wikimedia.org/wiki/File:Kubernetes_logo_without_workmark.svg