Vai al contenuto

Sandbox e query

Una sandbox è un database privato e usa e getta, ripristinato da un backup o da qualsiasi secondo della finestra. È la destinazione di ripristino predefinita, perché guardare non mette mai a rischio la produzione.

tenax restore team-a/app --at 2026-10-07T09:41:27Z --ttl 6h --wait
tenax sandbox list
tenax sandbox show <sandbox>

Cos'è una sandbox

  • Un pod di database con l'engine e la versione del target (spec.sandbox.image sul target), nel namespace delle sandbox di Tenax, con credenziali generate e una NetworkPolicy che nega tutto per impostazione predefinita.
  • Un TTL (sandbox.defaultTTL, al massimo sandbox.maxTTL, entrambi per policy e per l'intero chart). Alla scadenza, Tenax elimina la sandbox e il suo storage. tenax sandbox extend <sandbox> --by 24h concede più tempo; tenax sandbox delete <sandbox> la chiude prima.
  • Appartiene a chi l'ha richiesta; ogni azione su di essa viene registrata nell'audit.

Query

La pagina Query della console e tenax query eseguono SQL tramite il query proxy di Tenax, in sola lettura per impostazione predefinita:

tenax query <sandbox> -e "select count(*) from orders where created_at > now() - interval '1 day'"
tenax query <sandbox> -e "select * from coupons" -o csv > coupons.csv

Ogni query ha un limite di 30 secondi e restituisce al massimo 1.000 righe; --write consente le scritture (solo sandbox).

Nell'editor, Ctrl+Enter (Cmd+Enter su Mac) esegue la query, o solo il testo selezionato se c'è una selezione. Il completamento propone parole chiave, funzioni, tabelle e colonne: si apre digitando dopo due caratteri, dopo alias. elenca le colonne di quella tabella, dopo schema. le sue tabelle, e Ctrl+Space lo apre ovunque. Tab o Enter sceglie un suggerimento, Esc lo chiude, e altrimenti Tab indenta.

La console parla l'SQL di ciascun engine (oggi PostgreSQL e MySQL); i risultati mostrano i valori così come li stampa il database.

Dimensionamento

Ogni sandbox è un pod di database nel proprio namespace. Ciò che riceve viene dal primo livello che lo imposta:

  1. il ripristino: i campi CPU, Memoria, Disco e Storage class nella pagina Ripristina (i valori in grigio sono quelli che riceve se li lasci vuoti), tenax restore … --cpu 2 --memory 8Gi --size 50Gi --storage-class local-path, o spec.destination.sandbox di un Restore;
  2. il target: spec.sandbox.cpu, memory, sizeLimit, storageClass del BackupTarget;
  3. l'installazione: sandbox.resources del chart;
  4. i valori integrati: 100m di CPU, 256Mi di memoria richiesta con un limite di 1Gi, e un disco pari al doppio dei dati del backup più 512Mi (almeno 1Gi) sul nodo (emptyDir, rimosso insieme alla sandbox).
Impostazione Significato
CPU CPU richiesta; nessun limite di CPU, così un ripristino può usare i core inattivi
Memoria request e limit della memoria
Disco dimensione del volume dati; sul disco del nodo, a meno che non sia impostata una storage class
Storage class i dati vanno invece su un PVC di questa classe, per ripristini più grandi di quanto consentano i dischi dei nodi

sandbox.limits del chart imposta i tetti per CPU, memoria e disco. La console e l'API rifiutano una richiesta che li supera, e un ripristino la cui dimensione risulta superiore (per un'impostazione del target, o per la dimensione automatica di un backup grande) fallisce indicando il motivo, invece di riempire un disco a metà strada. Una sandbox in esecuzione mantiene la sua dimensione: ripristina di nuovo per cambiarla.

Connettersi dal cluster

Host, porta e credenziali della sandbox sono nella sua pagina (Mostra credenziali, registrato nell'audit) e in tenax sandbox show. I pod di altri namespace richiedono l'etichetta tenax.run/sandbox-client: <sandbox>; la network policy della sandbox rifiuta tutto il resto.

Connettersi dall'esterno del cluster

Come si raggiungono le sandbox da un portatile lo decide l'installazione (expose.provider, più sotto). In ogni caso:

  • Apri l'accesso esterno con Apri accesso esterno nella pagina della sandbox, tenax sandbox expose <sandbox>, o apri accesso esterno durante il ripristino (tenax restore … --expose); serve il ruolo operator.
  • Connettiti con tenax sandbox connect <sandbox>, che stampa una stringa di connessione per qualsiasi client di database, oppure copiala dalla pagina della sandbox.
  • Chiudilo con Chiudi accesso esterno o tenax sandbox unexpose <sandbox>. Si chiude anche quando la sandbox scade o viene eliminata.
expose.provider Cosa fa tenax sandbox connect Cosa serve al client
none (predefinito) Inoltra una porta locale alla sandbox tramite il tuo kubeconfig, fino a Ctrl+C Un kubeconfig autorizzato al port-forward in tenax-sbx-<sandbox>
service Stampa l'indirizzo della sandbox stessa (postgres://…@sbx-7f3a.db.example.com:5432/app?sslmode=require) Niente: qualsiasi client di database, via TLS
cloudflare Apre cloudflared access tcp su una porta locale dopo un login di Access, fino a Ctrl+C cloudflared e un indirizzo email autorizzato

Le sandbox Postgres servono TLS con un certificato proprio (autofirmato: i client usano sslmode=require, che cifra senza verificare l'emittente); le sandbox MySQL usano il certificato generato da MySQL.

Raggiungere le sandbox dall'esterno del cluster (amministratori)

Tenax non apre nulla da solo: ogni opzione usa qualcosa che il tuo cluster ha già.

Opzione service: un Service Kubernetes per sandbox

Ogni sandbox esposta riceve un Service costruito da expose.service; ciò che il tuo cluster usa per i Service le assegna un indirizzo, e Tenax mostra quell'indirizzo. È l'opzione più semplice per gli sviluppatori (qualsiasi client di database) e funziona con qualsiasi Kubernetes:

Il tuo cluster ha Imposta Gli sviluppatori si connettono a
Un load balancer cloud (AKS, EKS, GKE) type: LoadBalancer, sourceRanges: [<office or VPN CIDRs>] L'IP del load balancer
Lo stesso, più external-dns anche annotations: {external-dns.alpha.kubernetes.io/hostname: "sbx-{id}.db.example.com"} e hostnamePattern: "sbx-{id}.db.example.com" sbx-<id>.db.example.com
MetalLB (on-premises) type: LoadBalancer Un indirizzo della tua rete
L'operator Tailscale loadBalancerClass: tailscale Il nome tailnet della sandbox; niente è pubblico
Niente di tutto ciò type: NodePort, hostnamePattern: "nodes.example.com" con un nome corrispondente per i tuoi nodi nodes.example.com:<node port>
expose:
  provider: service
  service:
    type: LoadBalancer
    sourceRanges: ["203.0.113.0/24"]   # who may connect; leave empty only on a private network

Le sandbox contengono copie dei dati di produzione

Un load balancer pubblico protegge una sandbox solo con la sua password casuale, il TLS e il suo limite di tempo. Imposta sourceRanges sugli indirizzi da cui arrivano i tuoi sviluppatori, o usa un'opzione privata (Tailscale, un load balancer interno tramite l'annotazione del tuo cloud, MetalLB su una rete privata).

Mentre una sandbox è esposta, una NetworkPolicy aggiuntiva ammette expose.service.allowFrom (predefinito: tutto; il filtro lo fa sourceRanges del load balancer, dato che il suo traffico arriva spesso dagli indirizzi dei nodi) sulla porta del database. Con il load balancer integrato di k3s ogni sandbox esposta occupa la porta del database sui nodi, quindi lì si può esporre una sola sandbox Postgres e una sola sandbox MySQL alla volta.

Opzione cloudflare: un tunnel dietro un login di Access

I tunnel richiedono un token API di Cloudflare, che appartiene all'installazione, non a un utente. È un Secret Kubernetes nel namespace di Tenax, configurato una volta come qualsiasi altra credenziale (con gli strumenti GitOps per i secret, per esempio SOPS). La console non lo chiede, non lo conserva e non lo mostra mai: può modificare il DNS e le applicazioni Access della zona.

  1. In Cloudflare, My Profile → API Tokens → Create Token → Custom token con questi permessi, per la zona in cui si trovano gli hostname:
    • Account: Cloudflare Tunnel: Edit
    • Account: Access: Apps and Policies: Edit
    • Zone: DNS: Edit
    • Zone: Zone: Read
  2. Salvalo:

    kubectl -n tenax-system create secret generic tenax-cloudflare-api --from-file=token=api-token.txt
    
  3. Configura il chart:

    expose:
      provider: cloudflare
      cloudflare:
        zone: example.com
        apiTokenSecret: tenax-cloudflare-api
        hostnamePattern: "sbx-{id}.example.com"
        allowEmails: ["*@example.com"]      # who may connect to an open tunnel
      cloudflared:
        enabled: true
    

Tieni gli hostname un livello sotto la zona (sbx-{id}.example.com, non sbx-{id}.db.example.com): il certificato gratuito di Cloudflare copre solo *.example.com, e il client raggiunge l'hostname via HTTPS. Una porta di database non può essere protetta da un login nel browser, quindi gli sviluppatori eseguono cloudflared, cosa che tenax sandbox connect fa per loro.

Con il token, Tenax trova l'account a partire dalla zona, crea un tunnel chiamato tenax (expose.cloudflare.tunnelName) o lo riutilizza, e scrive il connector token del tunnel nel Secret <release>-tunnel-token, da cui partono i pod cloudflared. Controlla quel Secret ogni 15 minuti, così un token ruotato in Cloudflare arriva da solo nel Secret (riavvia i pod cloudflared perché lo recepiscano). allowEmails accetta indirizzi e domini interi (*@example.com); allowGroups accetta id di gruppi Access.

Un tunnel che gestisci già

Imposta expose.cloudflare.tunnelID per usare un tunnel esistente gestito da remoto al posto di tenax, e expose.cloudflared.tunnelTokenSecret su un Secret (chiave token) con il suo connector token se preferisci fornirlo tu; in quel caso Tenax non tocca quel Secret. Tenax aggiunge e rimuove solo le proprie regole sbx-…, quindi le altre route del tunnel restano invariate. accountID e zoneID possono essere impostati per saltare le ricerche.

Lo stesso tunnel può servire anche la console: aggiungi al tunnel un hostname pubblico per tenax-api (Zero Trust → Networks → Tunnels → tenax → Public hostnames) dietro un'applicazione Access, e imposta expose.cloudflared.consoleHostname con auth.provider: cloudflare-access: vedi Console, CLI e accesso.