跳轉到

沙盒與查詢

沙盒是一個從備份或時間窗內任意一秒還原出來的私有、一次性資料庫。它是預設的還原目的地,因為檢視資料永遠不會給正式環境帶來風險。

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 與存取。