沙箱与查询¶
沙箱是一个从备份或窗口内任意一秒恢复出来的私有、一次性数据库。它是默认的恢复目的地,因为查看数据永远不会给生产环境带来风险。
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。它获得的资源取自以下第一个设置了该值的地方:
- 恢复本身:Restore 页面上的 CPU、内存、磁盘 和 存储类 字段(留空时,灰色显示的值就是它将获得的值),
tenax restore … --cpu 2 --memory 8Gi --size 50Gi --storage-class local-path,或 Restore 的spec.destination.sandbox; - 目标:BackupTarget 的
spec.sandbox.cpu、memory、sizeLimit、storageClass; - 安装:Chart 的
sandbox.resources; - 内置默认值:请求 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 应用。
- 在 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
-
保存它:
-
配置 Chart:
让主机名位于区域下一级(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 与访问。