./run openbricks-piattaforma-dati-open-source-openshift
Openbricks: quando il piano di governance è crollato al contatto con la realtà
Diario tecnico di Openbricks: un lakehouse open-source su OpenShift bare-metal via GitOps. Il piano 'Ranger unifica Trino e Spark' è crollato — ecco come l'identità Keycloak è diventata la spina dorsale.
- status
- draft
- project
- openbricks
- updated
- 2026-07-07
- tags
Databricks è comodo. È anche una scatola chiusa su cloud altrui con una bolletta che cresce. La domanda che mi ha fatto partire: quanto di quella comodità si ricostruisce da pezzi open-source, in casa, sul proprio ferro? Openbricks è il tentativo — un lakehouse a 8 strati deployato al 100% via GitOps su OpenShift 4.18 bare-metal, air-gapped. E la storia vera è come il piano di governance sia morto e rinato.
Lo stack in breve
Otto strati, dall’identità allo storage: Keycloak (OIDC federato a LDAP/AD) e OPA in cima; poi il router HAProxy di OpenShift; le app (JupyterHub, MLflow, Airflow 3, Superset, Kestra); il processing (Spark 4.0.2 via operatore K8s, Trino 479); il catalogo (Unity Catalog OSS, OpenMetadata); lo storage (Delta Lake su MinIO, PostgreSQL HA via CloudNativePG, Ceph); il control plane (ArgoCD, monitoring); e il deploy GitOps. Cluster: 3 worker, ~288 core, ~768 Gi RAM.
Come scorrono i dati (la prima decisione)
Il pattern è “Unity Catalog come metastore passivo”:
- Scrittura: Spark scrive tabelle Delta col connettore UC (registra i metadati) ma scrive i byte direttamente su MinIO con le proprie credenziali.
- Lettura: Trino legge i metadati via UC Iceberg REST API e legge i byte direttamente da MinIO.
- Perché passivo: UC OSS v0.4.0 non fa credential vending verso endpoint S3-compatibili come MinIO (PR upstream ancora aperta). Quindi UC non valida né distribuisce token S3; gli engine tengono le proprie credenziali, e lo storage è protetto a livello infra. Questo tiene UC sostituibile a costo zero.
Il pivot: Ranger fuori, tre pezzi dentro
Il piano era elegante: Apache Ranger come piano di policy unico per Trino e Spark, più OPA per Kubernetes. Un solo posto per dire chi vede cosa. Poi la realtà:
Ranger 2.7 ha un plugin nativo per Trino, ma non esiste un plugin Ranger per Spark 4.0. Ranger coprirebbe l’engine di query, non quello di scrittura — quindi non può essere il “piano unico”. Rimosso dallo stack.
Rifatto come autorizzazione multi-livello, ognuno bravo in ciò che sa fare: Keycloak+oauth2-proxy per l’accesso alla piattaforma, OPA Gatekeeper per l’admission K8s, la file-based ACL di Trino per l’accesso ai dati in query, i GRANT di Unity Catalog (pronti ma disabilitati — la UI di UC non ha login Keycloak, altra issue upstream), RBAC/SCC di OpenShift per l’infra.
Come restano coerenti i permessi tra engine
Non c’è un motore ABAC unico che copra Trino e Spark. La coerenza si ottiene facendo dell’identità la spina dorsale condivisa e vincolando dove ogni engine può scrivere:
- Identità unificata a Keycloak. Ogni engine autentica la stessa persona contro lo stesso realm, dietro un solo AD. Il gruppo è la fonte di verità: team = gruppo AD, mappato nel claim
groupsdel token. - Enforcement Trino. Trino gira con
access-control.name=filepiù un group-provider. Gli umani entrano via OAuth2; i servizi non interattivi presentano un Bearer token Keycloak validato via JWKS — con una furbizia necessaria: il principal si legge da un claim dedicatotrino_user, non dapreferred_username(che per un service account collide comeservice-account-…).
http-server.authentication.type=oauth2,jwt
http-server.authentication.jwt.principal-field=trino_user # claim dedicato, non preferred_username
http-server.authentication.jwt.required-audience=trino
- Enforcement Spark. Niente authorizer Spark: si limita il raggio d’azione. L’unico servizio autorizzato a scrivere nello schema
ingestè l’identità di ingest governata; tutti gli altri hanno sola lettura sul percorso di query. La governance sta sul query path (Trino) e sull’identità di scrittura, dando permessi effettivi coerenti senza un plugin Spark.
La ACL di Trino, in forma sanitizzata — deny-by-default via catch-all a privilegi vuoti:
{ "tables": [
{ "group": "platform-ingest|admin", "catalog": "delta", "schema": "ingest",
"table": ".*", "privileges": ["SELECT","INSERT","DELETE","UPDATE","OWNERSHIP"] },
{ "group": "hr|admin", "catalog": "delta", "schema": "sensitive_hr",
"table": ".*", "privileges": ["SELECT"] },
{ "user": ".*", "catalog": "delta", "schema": "sensitive_hr",
"table": ".*", "privileges": [] } ] }
La prova che funziona: un test end-to-end sul cluster vivo — il service JWT scrive in ingest = OK; scrive fuori = PERMISSION_DENIED; JWT invalido = 401; e il login umano OAuth2 resta intatto perché i due autenticatori coesistono.
GitOps e air-gap
Tutto è app-of-apps ArgoCD: una Application radice possiede ~15 figlie, ordinate con sync-wave (foundation → processing → app). Kustomize base + overlay testing/production. Essendo air-gapped, ogni chart Helm è vendorizzato come artefatto OCI su un Quay interno — niente pull da Docker Hub al sync. I secret sono SealedSecrets committati in git; l’unico applicato a mano è la credenziale del repo verso Quay (sigillarla creerebbe un deadlock circolare).
Gotcha onesti
- UC Iceberg REST è read-only e una sua DDL usa
BINARY(16)che PostgreSQL rifiuta. - Due sistemi di gruppi disgiunti (Keycloak vs
group.txtdi Trino), additivi — un CronJob che rigeneragroup.txtda Keycloak è l’unificazione pianificata. group.txtmontato viasubPathnon fa hot-reload: serverollout restartdel coordinator.- La CA aziendale non pubblica ovunque, gestita in quattro modi diversi: JKS truststore per i componenti Java, CA cotta nell’immagine per Python (Superset), skip-verify per Go (oauth2-proxy).
- OpenShift ≠ K8s vanilla: Route non Ingress, SCC non PodSecurity,
ocnonkubectl.
Cosa sto imparando
Che “open-source” non vuol dire “gratis di sforzo”. I mattoncini sono liberi, ma il cemento che li tiene insieme — identità, policy coerenti tra engine, riproducibilità del deploy — lo devi mettere tu. È esattamente quel cemento che paghi quando paghi una piattaforma chiusa. E che un piano di architettura elegante vale finché non incontra “non esiste il plugin per la tua versione”.
Lo costruisco per un contesto reale con requisiti reali. Nomi e destinatario restano fuori: qui interessa il come, non il per chi. Prossima tappa: chiudere la governance e passare allo strato ML.