Sandboxes y consultas¶
Un sandbox es una base de datos privada y desechable restaurada desde un respaldo o desde cualquier segundo de la ventana. Es el destino de restauración por defecto, porque mirar nunca pone en riesgo producción.
tenax restore team-a/app --at 2026-10-07T09:41:27Z --ttl 6h --wait
tenax sandbox list
tenax sandbox show <sandbox>
Qué es un sandbox¶
- Un pod de base de datos con el motor y la versión del objetivo (
spec.sandbox.imageen el objetivo), en el namespace de sandboxes de Tenax, con credenciales generadas y una NetworkPolicy que lo deniega todo por defecto. - Un TTL (
sandbox.defaultTTL, como máximosandbox.maxTTL, ambos por política y para todo el chart). Cuando vence, Tenax borra el sandbox y su almacenamiento.tenax sandbox extend <sandbox> --by 24hda más tiempo;tenax sandbox delete <sandbox>lo termina antes. - Pertenece a quien lo pidió; cada acción sobre él queda auditada.
Consultas¶
La página Consulta de la consola y tenax query ejecutan SQL a través del proxy de consultas de Tenax, en solo
lectura por defecto:
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
Cada consulta tiene un límite de 30 segundos y devuelve como máximo 1.000 filas; --write permite escrituras (solo en
sandboxes).
En el editor, Ctrl+Enter (Cmd+Enter en un Mac) ejecuta la consulta, o solo el texto seleccionado si hay una
selección. El autocompletado ofrece palabras clave, funciones, tablas y columnas: se abre al escribir dos caracteres;
tras alias. lista las columnas de esa tabla, tras schema. sus tablas, y Ctrl+Space lo abre en cualquier parte.
Tab o Enter eligen una sugerencia, Esc lo cierra y, en otro caso, Tab indenta.
La consola habla el SQL de cada motor (hoy PostgreSQL y MySQL); los resultados muestran los valores tal como los imprime la base de datos.
Dimensionamiento¶
Cada sandbox es un pod de base de datos en su propio namespace. Lo que recibe se toma del primer lugar que lo define:
- la restauración: los campos CPU, Memoria, Disco y Clase de almacenamiento de la página Restore (los valores en
gris son los que recibe si los dejas vacíos),
tenax restore … --cpu 2 --memory 8Gi --size 50Gi --storage-class local-path, ospec.destination.sandboxde un Restore; - el objetivo:
spec.sandbox.cpu,memory,sizeLimit,storageClassdel BackupTarget; - la instalación:
sandbox.resourcesdel chart; - por defecto: 100m de CPU, 256Mi de memoria solicitados con un límite de 1Gi, y un disco del doble de los datos del
respaldo más 512Mi (al menos 1Gi) en el nodo (
emptyDir, que se elimina con el sandbox).
| Ajuste | Significa |
|---|---|
| CPU | solicitud de CPU; sin límite de CPU, para que una restauración pueda usar núcleos ociosos |
| Memory | solicitud y límite de memoria |
| Disk | tamaño del volumen de datos; en el disco del nodo salvo que se defina una storage class |
| Storage class | los datos van en su lugar a un PVC de esta clase, para restauraciones más grandes de lo que permiten los discos de los nodos |
sandbox.limits del chart fija topes para CPU, memoria y disco. La consola y la API rechazan una petición por encima
de ellos, y una restauración cuyo tamaño resulta superior (por un ajuste del objetivo o por el tamaño automático de un
respaldo grande) falla indicando el motivo en lugar de llenar un disco a medias. Un sandbox en ejecución conserva su
tamaño: vuelve a restaurar para cambiarlo.
Conectarse desde el clúster¶
El host, el puerto y las credenciales del sandbox están en su página (Mostrar credenciales, auditado) y en
tenax sandbox show. Los pods de otros namespaces necesitan la etiqueta tenax.run/sandbox-client: <sandbox>;
la network policy del sandbox rechaza todo lo demás.
Conectarse desde fuera del clúster¶
Cómo se llega a los sandboxes desde un portátil lo decide la instalación (expose.provider, más abajo). En todos los
casos:
- Abre el acceso externo con Abrir acceso externo en la página del sandbox, con
tenax sandbox expose <sandbox>o con open external access al restaurar (tenax restore … --expose); requiere el roloperator. - Conéctate con
tenax sandbox connect <sandbox>, que muestra una cadena de conexión para cualquier cliente de base de datos, o cópiala de la página del sandbox. - Ciérralo con Cerrar acceso externo o
tenax sandbox unexpose <sandbox>. También se cierra cuando el sandbox vence o se borra.
expose.provider |
Qué hace tenax sandbox connect |
El cliente necesita |
|---|---|---|
none (por defecto) |
Reenvía un puerto local al sandbox a través de tu kubeconfig, hasta Ctrl+C | Un kubeconfig con permiso de port-forward en tenax-sbx-<sandbox> |
service |
Muestra la dirección propia del sandbox (postgres://…@sbx-7f3a.db.example.com:5432/app?sslmode=require) |
Nada: cualquier cliente de base de datos, por TLS |
cloudflare |
Abre cloudflared access tcp en un puerto local tras un inicio de sesión de Access, hasta Ctrl+C |
cloudflared y un email autorizado |
Los sandboxes de Postgres sirven TLS con un certificado propio (autofirmado: los clientes usan sslmode=require, que
cifra sin comprobar el emisor); los de MySQL usan el certificado que genera MySQL.
Acceso a los sandboxes desde fuera del clúster (administradores)¶
Tenax no abre nada por sí solo: cada opción usa algo que tu clúster ya tiene.
Opción service: un Service de Kubernetes por sandbox¶
Cada sandbox expuesto recibe un Service construido a partir de expose.service; lo que tu clúster use para los
Services le da una dirección, y Tenax muestra esa dirección. Es lo más sencillo para los desarrolladores (cualquier
cliente de base de datos) y funciona con cualquier Kubernetes:
| Tu clúster tiene | Define | Los desarrolladores se conectan a |
|---|---|---|
| Un balanceador de carga en la nube (AKS, EKS, GKE) | type: LoadBalancer, sourceRanges: [<office or VPN CIDRs>] |
La IP del balanceador de carga |
| Lo mismo, más external-dns | además annotations: {external-dns.alpha.kubernetes.io/hostname: "sbx-{id}.db.example.com"} y hostnamePattern: "sbx-{id}.db.example.com" |
sbx-<id>.db.example.com |
| MetalLB (on-premises) | type: LoadBalancer |
Una dirección de tu red |
| El operador de Tailscale | loadBalancerClass: tailscale |
El nombre del sandbox en la tailnet; nada es público |
| Nada de lo anterior | type: NodePort, hostnamePattern: "nodes.example.com" con un nombre que apunte a tus nodos |
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
Los sandboxes contienen copias de datos de producción
Un balanceador de carga público protege un sandbox solo con su contraseña aleatoria, TLS y su límite de tiempo.
Define sourceRanges con las direcciones desde las que se conectan tus desarrolladores, o usa una opción privada
(Tailscale, un balanceador de carga interno mediante la anotación de tu nube, MetalLB en una red privada).
Mientras un sandbox está expuesto, una NetworkPolicy adicional admite expose.service.allowFrom (por defecto: todo; el
filtrado lo hace sourceRanges del balanceador, ya que su tráfico suele llegar desde direcciones de nodos) en el puerto
de la base de datos. Con el balanceador de carga integrado de k3s, cada sandbox expuesto ocupa el puerto de la base de
datos en los nodos, así que allí solo se puede exponer a la vez un sandbox de Postgres y uno de MySQL.
Opción cloudflare: un túnel detrás de un inicio de sesión de Access¶
Los túneles necesitan un token de API de Cloudflare, que pertenece a la instalación, no a un usuario. Es un Secret de Kubernetes en el namespace de Tenax, que se configura una vez como cualquier otra credencial (con tus herramientas de secretos de GitOps, por ejemplo SOPS). La consola nunca lo pide, ni lo guarda, ni lo muestra: puede modificar el DNS y las aplicaciones de Access de la zona.
- En Cloudflare, My Profile → API Tokens → Create Token → Custom token con estos permisos, para la zona en la que
están los nombres de host:
- Account: Cloudflare Tunnel: Edit
- Account: Access: Apps and Policies: Edit
- Zone: DNS: Edit
- Zone: Zone: Read
-
Guárdalo:
-
Configura el chart:
Mantén los nombres de host un nivel por debajo de la zona (sbx-{id}.example.com, no sbx-{id}.db.example.com): el
certificado gratuito de Cloudflare solo cubre *.example.com, y el cliente llega al nombre de host por HTTPS. Un puerto
de base de datos no se puede proteger con un inicio de sesión en el navegador, así que los desarrolladores ejecutan
cloudflared, y tenax sandbox connect lo hace por ellos.
Con el token, Tenax encuentra la cuenta a partir de la zona, crea un túnel llamado tenax (expose.cloudflare.tunnelName)
o lo reutiliza, y escribe el token del conector del túnel en el Secret <release>-tunnel-token, con el que arrancan los
pods de cloudflared. Revisa ese Secret cada 15 minutos, así que un token rotado en Cloudflare llega al Secret por sí
solo (reinicia los pods de cloudflared para que lo tomen). allowEmails acepta direcciones y dominios completos
(*@example.com); allowGroups acepta IDs de grupos de Access.
Un túnel que ya gestionas
Define expose.cloudflare.tunnelID para usar un túnel existente gestionado de forma remota en lugar de tenax, y
expose.cloudflared.tunnelTokenSecret con un Secret (clave token) que contenga su token de conector si prefieres
proporcionarlo tú; en ese caso Tenax no toca ese Secret. Tenax solo añade y quita sus propias reglas sbx-…, así
que las demás rutas del túnel siguen como están. Puedes definir accountID y zoneID para evitar las búsquedas.
El mismo túnel puede servir también la propia consola: añade al túnel un nombre de host público para tenax-api
(Zero Trust → Networks → Tunnels → tenax → Public hostnames) detrás de una aplicación de Access, y define
expose.cloudflared.consoleHostname con auth.provider: cloudflare-access: consulta Consola, CLI y acceso.