跳轉至

Tuwunel

Tuwunel 係一個部署喺 oracle-cloud 上嘅自託管服務;佢以 docker 容器執行,鏡像來自 GitHub Container Registry 上嘅 ghcr.io/matrix-construct/tuwunel:main。喺 benhoweb.com 嘅架構入面,Tuwunel 用嚟提供 Matrix Construct 生態相關服務,並以獨立 container 方式長駐,唔直接依賴宿主机上面手動執行嘅 process。

呢條目以實際部署環境為基礎,記錄 Tuwunel 嘅網絡位置、組態方式、更新同維運操作。

Overview

Tuwunel 嘅部署重點如下:

  • 服務使用官方鏡像 ghcr.io/matrix-construct/tuwunel:main
  • container 名稱固定為 tuwunel
  • 設定檔以 tuwunel.toml 形式掛入容器;
  • 資料一律保存喺命名 volume tuwunel-data
  • 服務連接外部 Docker network cf_network,固定使用 172.21.0.86
  • 唔直接映射 public port,流量主要由 edge/tunnel 層轉發;
  • 記憶體上限設定為 1g,防止 runaway process 食爆 Oracle Cloud VM;
  • 鏡像採用 distroless 設計,容器內冇 shell、curl、wget,因此唔可以依賴 container-level healthcheck。

Oracle Cloud 嘅部署唔只係「開咗部 VM 然後行 container」,更加需要有明確嘅 Docker network、volume、以及外部注入 secret 嘅方法。Tuwunel 嘅組態正正反映咗呢一套做法。

Architecture

Tuwunel 喺 VM 上同其他自託管服務共享一個名為 cf_network 嘅 external Docker bridge network。佢冇 ports: 設定,所以外界唔能夠直接經 host IP 去撞個 container;實際流量會由 edge 服務處理。

簡化架構如下:

Internet
   │
   ▼
Cloudflare Edge
   │  Cloudflare Tunnel(outbound-only)
   ▼
cf_network bridge(172.21.0.0/24)
   ├── cloudflared / reverse-proxy container
   └── tuwunel(172.21.0.86)
          └── tuwunel-data(persistent volume)

Tuwunel 之所以要墮入 cf_network,係為咗可以同 Cloudflare Tunnel 或其他代理容器以固定 IP 溝通。ipv4_address: 172.21.0.86 確保每次 recreate 之後,Tuwunel 都攞到同一個地址,唔會因為 DHCP 改變而令 proxy 設定失效。

呢種架構亦符合「自託管但唔自行公開 port」嘅安全原則:Tuwunel 本身唔需要暴露喺公網,只有 edge 先至需要對外。

Deployment

Tuwunel 嘅部署假設 Docker 同 Docker Compose plugin 已經裝好。以下係喺 Oracle Cloud VM 上使用嘅 compose 設定:

services:
  tuwunel:
    image: ghcr.io/matrix-construct/tuwunel:main
    container_name: tuwunel
    restart: unless-stopped
    mem_limit: 1g
    networks:
      cf_network:
        ipv4_address: 172.21.0.86
    environment:
      - TZ=Asia/Hong_Kong
      - TUWUNEL_CONFIG=/etc/tuwunel.toml
      # 註冊 token 由 Dockhand env 注入(env override TOML;唔寫死喺 repo)
      - TUWUNEL_REGISTRATION_TOKEN=${MATRIX_REGISTRATION_TOKEN}
    volumes:
      - ./tuwunel.toml:/etc/tuwunel.toml:ro
      - tuwunel-data:/var/lib/tuwunel
    # distroless 鏡像冇 shell/curl/wget,容器內 healthcheck 不可行;E2E 用外部 curl 驗證
    healthcheck:
      test: ["NONE"]

networks:
  cf_network:
    external: true

volumes:
  tuwunel-data:

部署步驟:

  1. 確認 cf_network 已經存在:
docker network inspect cf_network

如果未存在,可以建立一個 subnet 相容嘅 bridge network:

docker network create cf_network \
  --driver bridge \
  --subnet 172.21.0.0/24
  1. 準備 tuwunel.toml,放喺 compose 檔案同一個目錄。佢會以 read-only 方式掛入 /etc/tuwunel.toml

  2. 設定 MATRIX_REGISTRATION_TOKEN。喺 CI/CD 入面可以交由 dockhand 注入,避免將 secret 寫入 repo。

  3. 執行:

docker compose up -d tuwunel

Configuration

Tuwunel 主要使用兩個層面嘅設定:TOML 設定檔同環境變數。

TUWUNEL_CONFIG 指住 /etc/tuwunel.toml,即係話 Tuwunel 啟動時會讀取呢個檔案。因為 mount 咗 :ro,container 入面冇得改設定檔,改設定一定要經返宿主機。

環境變數方面,最重要係 TUWUNEL_REGISTRATION_TOKEN。呢個 token 用於 Matrix 註冊流程,佢由 MATRIX_REGISTRATION_TOKEN 傳入。呢個設計代表 secret 由環境注入,唔會寫死喺 tuwunel.toml 或者 git repo。使用者需要理解:container 環境變數嘅優先序高過 TOML 入面同名設定,所以即使 tuwunel.toml 有相關欄位,實際執行時都會由環境變數覆蓋。

TZ=Asia/Hong_Kong 確保 log 同內部時間處理使用香港時區。

資料目錄 /var/lib/tuwunel 對應 tuwunel-data volume。任何要保留嘅狀態都應該寫入呢度,而唔好寫入 container root filesystem。

Operations

Tuwunel 日常維運主要喺 Oracle Cloud VM 上面執行。

檢查服務狀態:

docker compose ps

睇 log:

docker compose logs -f --tail=200 tuwunel

手動重啟:

docker compose restart tuwunel

更新鏡像:

docker compose pull tuwunel
docker compose up -d tuwunel
docker image prune -f

注意 main tag 係一個移動 target,更新前最好確認上游鏡像 digest,避免無預警升級。

由於鏡像係 distroless,唔可以依賴 docker compose exec tuwunel sh。容器內冇 shell;如果想要檢查 process,可以用:

docker stats tuwunel
docker inspect tuwunel

Healthcheck 方面,compose 入面寫咗 test: ["NONE"],即係停用 Docker healthcheck。實際監控應由外部節點以 curl 或者其他 HTTP client 打入 Tuwunel 嘅 edge endpoint,驗證成個路徑通唔通。

備份方面,需要同時備份設定檔同 volume:

tar czf tuwunel-config-backup.tgz tuwunel.toml

docker run --rm \
  -v tuwunel-data:/data \
  -v "$PWD":/backup \
  alpine:3.18 \
  tar czf /backup/tuwunel-data-backup.tgz -C /data .

Oracle Cloud 嘅 VCN security list 唔需要開放 Tuwunel 嘅 port,因為流量係經 Cloudflare Tunnel outbound 進入。若然直接開放 host port,反而會擴闊攻擊面。

FAQ

點解唔用 container healthcheck?

ghcr.io/matrix-construct/tuwunel:main 係 distroless 鏡像,入面冇 /bin/shcurlwget,Docker healthcheck 好難正常執行。與其整一個假 healthcheck,不如直接停用,改為由外部做 E2E curl 檢查。

點解要指定 static IP 172.21.0.86

Tuwunel 同其他服務共用 cf_network。如果每次 recreate 都攞唔同 IP,proxy 或者 tunnel 設定就會失效。static IP 令服務位置確定,並可以喺唔同 container 之間以內部網域名稱或 IP 溝通。

點解 token 要用環境變數而唔寫入 TOML?

因為 tuwunel.toml 好可能被版控,或者由共享設定檔產生。Token 屬於 secret,唔應該入 repo。環境變數可以結合 dockhand、CI/CD secret manager 或者部署工具注入,重可以令同一份 TOML 喺唔同環境重用。

點解冇 ports 對外?

Tuwunel 只需要喺 cf_network 入面俾 edge/proxy 連接。直接公開 port 會令服務暴露喺公網,增加被掃描同攻擊嘅風險。Oracle Cloud 上應該保留呢個架構,唔好隨意加 ports:

點解要 mem_limit: 1g

呢個係保障機制。Oracle Cloud VM 如果同其他 self-hosted 服務共用,單一容器冇限制嘅話,記憶體壓力可能令成部 VM OOM。1g 限制可以令 Tuwunel 唔會拖冧隔離嘅 litellm 或者其他服務。

點解容器內冇得 exec

Distroless 鏡像只包含應用程式同必要 runtime,唔包含 shell、package manager 同其他工具。呢個係刻意設計,用意係減少攻擊面同 image size。若果需要深入排查,應該靠 log、docker inspect 同外部 network probe。

Source

Coverage auto (container scan)