Aller au contenu

Bacs à sable et requêtes

Un bac à sable est une base privée et jetable, restaurée depuis une sauvegarde ou depuis n'importe quelle seconde de la fenêtre. C'est la destination de restauration par défaut, car regarder ne met jamais la production en danger.

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

Ce qu'est un bac à sable

  • Un pod de base de données du moteur et de la version de la cible (spec.sandbox.image sur la cible), dans le namespace des bacs à sable de Tenax, avec des identifiants générés et une NetworkPolicy qui refuse tout par défaut.
  • Un TTL (sandbox.defaultTTL, au plus sandbox.maxTTL, tous deux par politique et pour tout le chart). À son expiration, Tenax supprime le bac à sable et son stockage. tenax sandbox extend <sandbox> --by 24h donne plus de temps ; tenax sandbox delete <sandbox> y met fin plus tôt.
  • Il appartient à la personne qui l'a demandé ; chaque action sur lui est auditée.

Requêtes

La page Requête de la console et tenax query exécutent le SQL via le proxy de requêtes de Tenax, en lecture seule par défaut :

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

Chaque requête est limitée à 30 secondes et renvoie au plus 1 000 lignes ; --write autorise les écritures (bacs à sable uniquement).

Dans l'éditeur, Ctrl+Enter (Cmd+Enter sur Mac) exécute la requête, ou seulement le texte sélectionné s'il y a une sélection. L'autocomplétion propose mots-clés, fonctions, tables et colonnes : elle s'ouvre après deux caractères tapés, liste les colonnes de la table après alias. et ses tables après schema., et Ctrl+Space l'ouvre n'importe où. Tab ou Enter choisit une suggestion, Esc la ferme, et Tab indente sinon.

La console parle le SQL de chaque moteur (PostgreSQL et MySQL à ce jour) ; les résultats affichent les valeurs telles que la base les imprime.

Dimensionnement

Chaque bac à sable est un pod de base de données dans son propre namespace. Ce qu'il reçoit, d'après la première source qui le définit :

  1. la restauration : les champs CPU, Mémoire, Disque et Classe de stockage de la page Restore (les valeurs grisées sont celles qu'il reçoit si vous les laissez vides), tenax restore … --cpu 2 --memory 8Gi --size 50Gi --storage-class local-path, ou spec.destination.sandbox d'un Restore ;
  2. la cible : spec.sandbox.cpu, memory, sizeLimit, storageClass du BackupTarget ;
  3. l'installation : sandbox.resources du chart ;
  4. les valeurs intégrées : 100m de CPU, 256Mi de mémoire demandés avec une limite de 1Gi, et un disque de deux fois les données de la sauvegarde plus 512Mi (au moins 1Gi) sur le nœud (emptyDir, supprimé avec le bac à sable).
Paramètre Signification
CPU requête CPU ; pas de limite CPU, pour qu'une restauration puisse utiliser les cœurs inoccupés
Memory requête et limite mémoire
Disk taille du volume de données ; sur le disque du nœud, sauf si une storage class est définie
Storage class les données vont alors sur un PVC de cette classe, pour les restaurations plus grandes que ce que permettent les disques des nœuds

sandbox.limits du chart fixe des plafonds pour le CPU, la mémoire et le disque. La console et l'API refusent une demande au-delà, et une restauration dont la taille calculée les dépasse (un réglage de la cible, ou la taille automatique d'une grosse sauvegarde) échoue en donnant la raison, au lieu de remplir un disque à mi-chemin. Un bac à sable en cours d'exécution garde sa taille : restaurez à nouveau pour la changer.

Se connecter depuis le cluster

L'hôte, le port et les identifiants du bac à sable figurent sur sa page (Afficher les identifiants, audité) et dans tenax sandbox show. Les pods d'autres namespaces ont besoin du label tenax.run/sandbox-client: <sandbox> ; la network policy du bac à sable refuse tout le reste.

Se connecter depuis l'extérieur du cluster

La manière d'atteindre les bacs à sable depuis un poste relève de l'installation (expose.provider, ci-dessous). Dans tous les cas :

  • Ouvrez l'accès externe avec Ouvrir l'accès externe sur la page du bac à sable, avec tenax sandbox expose <sandbox>, ou en cochant open external access lors de la restauration (tenax restore … --expose) ; il faut le rôle operator.
  • Connectez-vous avec tenax sandbox connect <sandbox>, qui affiche une chaîne de connexion pour n'importe quel client de base de données, ou copiez-la depuis la page du bac à sable.
  • Fermez-le avec Fermer l'accès externe ou tenax sandbox unexpose <sandbox>. Il se ferme aussi quand le bac à sable expire ou est supprimé.
expose.provider Ce que fait tenax sandbox connect Ce qu'il faut au client
none (par défaut) Redirige un port local vers le bac à sable via votre kubeconfig, jusqu'à Ctrl+C Un kubeconfig autorisé à faire du port-forward dans tenax-sbx-<sandbox>
service Affiche l'adresse propre du bac à sable (postgres://…@sbx-7f3a.db.example.com:5432/app?sslmode=require) Rien : n'importe quel client de base de données, en TLS
cloudflare Ouvre cloudflared access tcp sur un port local après une connexion Access, jusqu'à Ctrl+C cloudflared et une adresse e-mail autorisée

Les bacs à sable Postgres servent du TLS avec leur propre certificat (auto-signé : les clients utilisent sslmode=require, qui chiffre sans vérifier l'émetteur) ; les bacs à sable MySQL utilisent le certificat que MySQL génère.

Atteindre les bacs à sable depuis l'extérieur du cluster (administrateurs)

Tenax n'ouvre rien de lui-même : chaque option s'appuie sur quelque chose que votre cluster possède déjà.

Option service : un Service Kubernetes par bac à sable

Chaque bac à sable exposé reçoit un Service construit à partir de expose.service ; ce que votre cluster utilise pour les Services lui attribue une adresse, et Tenax affiche cette adresse. C'est l'option la plus simple pour les développeurs (n'importe quel client de base de données) et elle fonctionne avec tout Kubernetes :

Votre cluster a Définissez Les développeurs se connectent à
Un load balancer cloud (AKS, EKS, GKE) type: LoadBalancer, sourceRanges: [<office or VPN CIDRs>] L'IP du load balancer
Le même, plus external-dns aussi annotations: {external-dns.alpha.kubernetes.io/hostname: "sbx-{id}.db.example.com"} et hostnamePattern: "sbx-{id}.db.example.com" sbx-<id>.db.example.com
MetalLB (sur site) type: LoadBalancer Une adresse de votre réseau
L'opérateur Tailscale loadBalancerClass: tailscale Le nom tailnet du bac à sable ; rien n'est public
Rien de tout cela type: NodePort, hostnamePattern: "nodes.example.com" avec un nom correspondant pour vos nœuds 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

Les bacs à sable contiennent des copies de données de production

Un load balancer public ne protège un bac à sable que par son mot de passe aléatoire, le TLS et sa durée de vie limitée. Réglez sourceRanges sur les adresses d'où viennent vos développeurs, ou utilisez une option privée (Tailscale, un load balancer interne via l'annotation de votre cloud, MetalLB sur un réseau privé).

Tant qu'un bac à sable est exposé, une NetworkPolicy supplémentaire admet expose.service.allowFrom (par défaut : tout ; c'est le sourceRanges du load balancer qui filtre, car son trafic arrive souvent depuis les adresses des nœuds) sur le port de la base. Avec le load balancer intégré de k3s, chaque bac à sable exposé occupe le port de la base sur les nœuds : on ne peut donc y exposer qu'un bac à sable Postgres et un bac à sable MySQL à la fois.

Option cloudflare : un tunnel derrière une connexion Access

Les tunnels nécessitent un jeton d'API Cloudflare, qui appartient à l'installation et non à un utilisateur. C'est un Secret Kubernetes dans le namespace de Tenax, configuré une fois comme n'importe quel autre identifiant (avec votre outillage GitOps de secrets, par exemple SOPS). La console ne le demande jamais, ne le détient pas et ne l'affiche pas : il peut modifier le DNS et les applications Access de la zone.

  1. Dans Cloudflare, My Profile → API Tokens → Create Token → Custom token avec ces permissions, pour la zone qui contient les noms d'hôte :
    • Account: Cloudflare Tunnel: Edit
    • Account: Access: Apps and Policies: Edit
    • Zone: DNS: Edit
    • Zone: Zone: Read
  2. Stockez-le :

    kubectl -n tenax-system create secret generic tenax-cloudflare-api --from-file=token=api-token.txt
    
  3. Configurez le 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
    

Gardez les noms d'hôte à un seul niveau sous la zone (sbx-{id}.example.com, et non sbx-{id}.db.example.com) : le certificat gratuit de Cloudflare ne couvre que *.example.com, et le client joint le nom d'hôte en HTTPS. Un port de base de données ne peut pas être protégé par une connexion dans le navigateur ; les développeurs exécutent donc cloudflared, ce que tenax sandbox connect fait pour eux.

Avec le jeton, Tenax trouve le compte à partir de la zone, crée un tunnel nommé tenax (expose.cloudflare.tunnelName) ou le réutilise, et écrit le jeton de connecteur du tunnel dans le Secret <release>-tunnel-token, à partir duquel démarrent les pods cloudflared. Il vérifie ce Secret toutes les 15 minutes : un jeton renouvelé dans Cloudflare atteint donc le Secret de lui-même (redémarrez les pods cloudflared pour le prendre en compte). allowEmails accepte des adresses et des domaines entiers (*@example.com) ; allowGroups accepte des identifiants de groupes Access.

Un tunnel que vous gérez déjà

Définissez expose.cloudflare.tunnelID pour utiliser un tunnel existant géré à distance au lieu de tenax, et expose.cloudflared.tunnelTokenSecret vers un Secret (clé token) contenant son jeton de connecteur si vous préférez le fournir vous-même ; Tenax ne touche alors pas à ce Secret. Tenax n'ajoute et ne retire que ses propres règles sbx-…, si bien que les autres routes du tunnel restent telles quelles. accountID et zoneID peuvent être définis pour éviter les recherches.

Le même tunnel peut aussi servir la console elle-même : ajoutez au tunnel un nom d'hôte public pour tenax-api (Zero Trust → Networks → Tunnels → tenax → Public hostnames) derrière une application Access, et définissez expose.cloudflared.consoleHostname avec auth.provider: cloudflare-access : voir Console, CLI et accès.