Container 部署讓 collector 完全隔離於 host 環境,開源使用者用 docker run 一行部署,不需要安裝 Go 或管理 binary 版本。但 SQLite 在 container 中有特殊的 I/O 和持久化考量 — overlay filesystem 的寫入延遲和 container 生命週期對資料持久性的影響需要在部署設計中處理。

Dockerfile 設計

Multi-stage build 把編譯環境和執行環境分離。Build stage 用 Go 官方 image 編譯 binary,runtime stage 只包含 binary 和必要的 CA 憑證。

 1FROM golang:1.22-alpine AS build
 2WORKDIR /src
 3COPY go.mod go.sum ./
 4RUN go mod download
 5COPY . .
 6RUN CGO_ENABLED=0 go build -o /collector ./cmd/collector
 7
 8FROM alpine:3.20
 9RUN apk add --no-cache ca-certificates tzdata
10COPY --from=build /collector /usr/local/bin/collector
11RUN adduser -D -u 1000 monitor
12USER monitor
13EXPOSE 8080
14ENTRYPOINT ["collector"]

最終 image 包含 Go binary(~15MB)+ alpine base(~7MB)+ ca-certificates,總大小目標 < 25MB。用 scratch 替代 alpine 可以再小 7MB,但失去 shell debug 能力。

SQLite 在 Container 中的 I/O 考量

Docker 的 overlay2 storage driver 在每次 fsync 時經過 overlay 層。SQLite 的 WAL mode 依賴 fsync 確保寫入持久性 — 每筆 transaction commit 觸發一次 fsync。Overlay 層增加的延遲讓每筆 fsync 慢 20-40%(取決於 host 的 storage driver 和檔案系統)。

Volume mount 繞過 overlay

把 SQLite 的資料目錄掛載為 host volume(-v /host/data:/data),SQLite 直接寫 host 檔案系統、繞過 overlay 層。寫入效能和同機部署的 binary 版本相當。

不用 volume mount 的風險:container 刪除時 overlay 層的資料一起消失。docker rm = 所有事件資料消失。即使只是 docker run 新版本的 image 也會建立新 container,舊 container 的資料不會自動遷移。

Volume Mount 設計

兩個目錄分開掛載,職責和權限不同:

MountContainer 路徑Host 路徑(範例)權限內容
資料/data./monitor-dataread-writeSQLite DB + WAL + 匯出檔
設定/config./monitor-configread-onlyretention config + rule config + sensor config

Container 內用非 root user(UID 1000)執行。Host 的 volume 目錄 ownership 需要對應:

1mkdir -p monitor-data monitor-config
2chown 1000:1000 monitor-data

Graceful Shutdown

docker stop 送 SIGTERM → collector 收到後執行 shutdown 序列:

  1. 停止接受新的 HTTP request(listener close)
  2. 等待 in-flight request 完成(5 秒 context timeout)
  3. Flush pending writes(尚未寫入 storage 的事件,5 秒)
  4. 停止定期 job(downsample / purge / rule engine 定期評估)
  5. SQLite WAL checkpoint(TRUNCATE mode,15 秒)
  6. 關閉 DB connection
  7. 退出

步驟 2-5 合計超時上限 25 秒。這個序列對應 Backend 5.6 Platform Lifecycle Contract 的 shutdown → drain 狀態:步驟 1-2 是 drain(停接新工作、等在途完成),步驟 3-6 是 shutdown(flush 狀態和釋放資源)。Collector 屬於短 request API 的 workload 類型(drain 窗口 5-30 秒),但多了 WAL checkpoint 步驟,讓 shutdown 時間可能超過一般 HTTP 服務。PID 1 信號處理的設計考量(exec form、避免 shell 攔截 SIGTERM)見 Backend 5.1 PID 1 與信號處理

docker stop 預設等 10 秒後送 SIGKILL。如果 WAL checkpoint 在大量未 checkpoint 的資料下需要超過 10 秒,Docker Compose 可以調 stop_grace_period: 30s

SQLite 的 WAL 設計支援 crash recovery — SIGKILL 後 WAL 檔案仍在,下次開啟 DB 時自動 replay。但非 graceful shutdown 可能丟失 channel 中尚未寫入的事件(已收到 HTTP 202 但還在 buffer 中的事件)。

資源限制

資源建議值(自用)建議值(小團隊)理由
Memory256MB512MBCollector + SQLite page cache + Go runtime
CPU0.5 核1 核I/O bound、CPU 通常不是瓶頸
磁碟volume mount 容量volume mount 容量保留策略控制、和 host 磁碟共享

Memory 限制設太緊會觸發 OOMKill — container 突然消失且無 log。設定 memory limit 前先觀察 collector 的 baseline 記憶體使用(docker stats),再乘以 1.5 安全係數。CPU request/limit 的設定策略(guaranteed vs burstable QoS)和 memory limit 與 OOM 的判讀見 Backend 5.1 Resource Limit

Docker Compose 範例

 1services:
 2  collector:
 3    image: tarrragon/monitor:latest
 4    ports:
 5      - "8080:8080"
 6    volumes:
 7      - ./monitor-data:/data
 8      - ./monitor-config:/config:ro
 9    environment:
10      - MONITOR_STORAGE=sqlite
11      - MONITOR_DB_PATH=/data/events.db
12    restart: unless-stopped
13    stop_grace_period: 30s
14    deploy:
15      resources:
16        limits:
17          memory: 256M
18          cpus: '0.5'
19    healthcheck:
20      test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"]
21      interval: 30s
22      timeout: 5s
23      retries: 3

restart: unless-stopped 讓 container 在 crash 或 host 重啟後自動恢復。healthcheck 讓 Docker 偵測 collector 是否真的在回應 — 只有 process 活著但 HTTP 不回應的場景也會被標記為 unhealthy。

和同機部署的效能對照

指標同機 binaryContainer + volume mountContainer 無 volume(overlay)
寫入吞吐(Mac SSD)~5,000/sec~4,500/sec(-10%)~3,000/sec(-40%)
寫入吞吐(Linux VPS)~3,000/sec~2,700/sec(-10%)~1,800/sec(-40%)
查詢延遲baselinebaseline(volume = 直接讀 host)+20%(overlay 讀取開銷小)
啟動時間< 100ms< 500ms(container 啟動開銷)同左
記憶體額外開銷0~10-20MB(container runtime)同左

Volume mount 後效能差異只有 ~10%(Go HTTP handler 的 overhead 大於 volume mount 的 overhead)。不用 volume mount 時 overlay fs 的 fsync 開銷顯著 — 寫入吞吐降 40%。

何時用 container、何時用 binary

場景建議理由
開源使用者快速試用Containerdocker run 一行、不需裝 Go
長期自用部署Binary + systemd效能最佳、無 container overhead
CI/CD 測試環境Container可拋棄式、每次乾淨環境
Kubernetes 部署Containerpod spec 標準化
Raspberry Pi / 邊緣設備Binary低資源環境避免 container overhead

斷網環境的部署考量

Collector 在斷網環境(air-gapped)裡的部署跟連網環境的主要差異有三點。第一,SDK 的 endpoint 從外部 URL(https://collect.example.com)改為內網地址(http://collector.internal:8080),SDK 設定檔裡的 endpoint 要能按環境切換。第二,Collector 的 container image 無法從 Docker Hub 拉取——需要透過 content ferry 搬運映像、推送到內網的 private registry(Harbor 或 Docker Registry),Dockerfile 的 base image 來源也要改指 private registry。第三,Collector 的 storage backend 只能用本地磁碟或 NFS,不能用雲端物件儲存——SQLite backend 在斷網環境反而是優勢(零外部依賴),儲存容量規劃要在部署前就確定,因為斷網環境的磁碟擴容流程可能需要數週。

SDK 的 offline buffer(見SDK 設計:offline-buffer)在斷網環境更重要——如果 Collector 重啟或暫時不可達,SDK 端的 buffer 是唯一能保住事件的機制。

斷網環境的 infra 層監控(Prometheus / Grafana / Loki)設定見斷網環境的監控與可觀測性

下一步路由