跳转至

沙箱与查询

沙箱是一个从备份或窗口内任意一秒恢复出来的私有、一次性数据库。它是默认的恢复目的地,因为查看数据永远不会给生产环境带来风险。

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

沙箱是什么

  • 一个与目标引擎和版本相同的数据库 Pod(目标上的 spec.sandbox.image),位于 Tenax 的沙箱命名空间中, 带有自动生成的凭据和默认拒绝的 NetworkPolicy。
  • 一个 TTL(sandbox.defaultTTL,最长 sandbox.maxTTL,两者都可以按策略或在整个 Chart 范围内设置)。到期后, Tenax 会删除沙箱及其存储。tenax sandbox extend <sandbox> --by 24h 可以延长时间; tenax sandbox delete <sandbox> 可以提前结束。
  • 归申请者所有;对它的每个操作都会被审计。

查询

控制台的 查询 页面和 tenax query 通过 Tenax 的查询代理执行 SQL,默认只读:

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

每个查询限时 30 秒,最多返回 1,000 行;--write 允许写入(仅限沙箱)。

在编辑器中,按 Ctrl+Enter(Mac 上为 Cmd+Enter)运行查询;如果有选中内容,则只运行选中的文本。 自动补全提供关键字、函数、表和列:输入两个字符后自动弹出,在 alias. 之后列出该表的列,在 schema. 之后列出其中的表, 按 Ctrl+Space 可在任意位置打开。Tab 或 Enter 选择一项建议,Esc 关闭补全,其他情况下 Tab 用于缩进。

控制台能理解每种引擎的 SQL(目前为 PostgreSQL 和 MySQL);结果中的值按数据库自身的输出格式显示。

资源规格

每个沙箱都是位于独立命名空间中的一个数据库 Pod。它获得的资源取自以下第一个设置了该值的地方:

  1. 恢复本身:Restore 页面上的 CPU、内存、磁盘 和 存储类 字段(留空时,灰色显示的值就是它将获得的值), tenax restore … --cpu 2 --memory 8Gi --size 50Gi --storage-class local-path,或 Restore 的 spec.destination.sandbox;
  2. 目标:BackupTarget 的 spec.sandbox.cpu、memory、sizeLimit、storageClass;
  3. 安装:Chart 的 sandbox.resources;
  4. 内置默认值:请求 100m CPU、256Mi 内存(上限 1Gi),以及节点上大小为备份数据两倍再加 512Mi(至少 1Gi)的磁盘 (emptyDir,随沙箱一起删除)。
设置 含义
CPU CPU 请求;不设 CPU 上限,因此恢复可以使用空闲核心
Memory 内存请求和上限
Disk 数据卷大小;除非设置了存储类,否则使用节点的磁盘
Storage class 数据改为放在该存储类的 PVC 上,适用于超出节点磁盘容量的恢复

Chart 的 sandbox.limits 设定 CPU、内存和磁盘的上限。控制台和 API 会拒绝超出上限的请求;如果某次恢复计算出的规格超出上限 (来自目标的设置,或大型备份自动计算出的大小),它会直接失败并给出原因,而不是在中途把磁盘写满。 运行中的沙箱保持其规格不变:如需更改,请重新恢复。

从集群内连接

沙箱的主机、端口和凭据显示在它的页面上(显示凭据,会被审计),也可以通过 tenax sandbox show 查看。 其他命名空间中的 Pod 需要带有标签 tenax.run/sandbox-client: <sandbox>;沙箱的网络策略会拒绝其他一切访问。

从集群外连接

从笔记本如何访问沙箱,由安装时的配置决定(expose.provider,见下文)。无论哪种方式:

  • 开启外部访问:在沙箱页面上点击 开启外部访问、执行 tenax sandbox expose <sandbox>,或在恢复时选择 open external access(tenax restore … --expose);需要 operator 角色。
  • 连接:执行 tenax sandbox connect <sandbox>,它会打印一个适用于任何数据库客户端的连接字符串;也可以从沙箱页面复制。
  • 关闭:点击 关闭外部访问 或执行 tenax sandbox unexpose <sandbox>。沙箱过期或被删除时也会自动关闭。
expose.provider tenax sandbox connect 做什么 客户端需要
none(默认) 通过你的 kubeconfig 把本地端口转发到沙箱,直到按下 Ctrl+C 一个允许在 tenax-sbx-<sandbox> 中执行 port-forward 的 kubeconfig
service 打印沙箱自己的地址(postgres://…@sbx-7f3a.db.example.com:5432/app?sslmode=require) 无需任何东西:任何数据库客户端,通过 TLS 连接
cloudflare 在 Access 登录后,于本地端口上打开 cloudflared access tcp,直到按下 Ctrl+C cloudflared 和一个被允许的邮箱

Postgres 沙箱使用自己的证书提供 TLS(自签名:客户端使用 sslmode=require,只加密而不校验签发者); MySQL 沙箱使用 MySQL 自动生成的证书。

从集群外访问沙箱(管理员)

Tenax 自己不会开放任何东西:每个选项都利用你的集群中已有的设施。

选项 service:每个沙箱一个 Kubernetes Service

每个对外暴露的沙箱都会获得一个按 expose.service 构建的 Service;你的集群为 Service 提供地址的机制会给它分配地址, Tenax 则显示这个地址。这对开发者来说最简单(任何数据库客户端都能用),并且适用于任何 Kubernetes:

你的集群有 设置 开发者连接到
云负载均衡器(AKS、EKS、GKE) type: LoadBalancer,sourceRanges: [<office or VPN CIDRs>] 负载均衡器的 IP
同上,外加 external-dns 另外设置 annotations: {external-dns.alpha.kubernetes.io/hostname: "sbx-{id}.db.example.com"} 和 hostnamePattern: "sbx-{id}.db.example.com" sbx-<id>.db.example.com
MetalLB(本地部署) type: LoadBalancer 你网络中的一个地址
Tailscale Operator loadBalancerClass: tailscale 沙箱在 tailnet 中的名称;不对公网开放
以上都没有 type: NodePort,hostnamePattern: "nodes.example.com",并为你的节点配置对应的名称 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

沙箱中保存着生产数据的副本

公网负载均衡器只能靠随机密码、TLS 和时间限制来保护沙箱。请把 sourceRanges 设为开发者所在的地址, 或者使用私有方案(Tailscale、通过云厂商注解创建的内部负载均衡器、私有网络上的 MetalLB)。

沙箱对外暴露期间,会有一个额外的 NetworkPolicy 在数据库端口上放行 expose.service.allowFrom(默认:全部放行; 过滤由负载均衡器的 sourceRanges 完成,因为其流量通常来自节点地址)。在 k3s 内置的负载均衡器上,每个暴露的沙箱都会 在节点上绑定数据库端口,因此在那里同一时间只能暴露一个 Postgres 沙箱和一个 MySQL 沙箱。

选项 cloudflare:位于 Access 登录之后的隧道

隧道需要一个 Cloudflare API 令牌,它属于这套安装,而不属于某个用户。它是 Tenax 命名空间中的一个 Kubernetes Secret, 像其他凭据一样只需设置一次(使用你的 GitOps 密钥工具,例如 SOPS)。控制台从不索取、保存或显示它: 它可以修改该区域中的 DNS 和 Access 应用。

  1. 在 Cloudflare 中,进入 My Profile → API Tokens → Create Token → Custom token,为主机名所在的区域授予以下权限:
    • Account: Cloudflare Tunnel: Edit
    • Account: Access: Apps and Policies: Edit
    • Zone: DNS: Edit
    • Zone: Zone: Read
  2. 保存它:

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

让主机名位于区域下一级(sbx-{id}.example.com,而不是 sbx-{id}.db.example.com):Cloudflare 的免费证书只覆盖 *.example.com,而客户端通过 HTTPS 访问该主机名。数据库端口无法用浏览器登录来保护,因此开发者需要运行 cloudflared, 而 tenax sandbox connect 会替他们完成这一步。

有了令牌,Tenax 会根据区域找到账号,创建一个名为 tenax 的隧道(expose.cloudflare.tunnelName)或复用已有的隧道, 并把隧道的连接器令牌写入 Secret <release>-tunnel-token,cloudflared Pod 就从这里启动。它每 15 分钟检查一次该 Secret, 因此在 Cloudflare 中轮换的令牌会自动同步到 Secret(重启 cloudflared Pod 即可生效)。allowEmails 接受邮箱地址和整个域名 (*@example.com);allowGroups 接受 Access 组 ID。

使用你已在管理的隧道

设置 expose.cloudflare.tunnelID 即可使用一个现有的远程管理隧道来代替 tenax;如果你更想自己提供连接器令牌, 可以把 expose.cloudflared.tunnelTokenSecret 设为保存该令牌的 Secret(键为 token),Tenax 便不会再动这个 Secret。 Tenax 只添加和删除它自己的 sbx-… 规则,因此隧道的其他路由保持不变。可以设置 accountID 和 zoneID 以跳过查找。

同一个隧道也可以为控制台本身提供服务:在隧道中为 tenax-api 添加一个公共主机名(Zero Trust → Networks → Tunnels → tenax → Public hostnames),放在一个 Access 应用之后,并在设置 auth.provider: cloudflare-access 的同时设置 expose.cloudflared.consoleHostname:参见控制台、CLI 与访问。