Container 部署設計
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 設計
兩個目錄分開掛載,職責和權限不同:
| Mount | Container 路徑 | Host 路徑(範例) | 權限 | 內容 |
|---|---|---|---|---|
| 資料 | /data | ./monitor-data | read-write | SQLite DB + WAL + 匯出檔 |
| 設定 | /config | ./monitor-config | read-only | retention config + rule config + sensor config |
Container 內用非 root user(UID 1000)執行。Host 的 volume 目錄 ownership 需要對應:
1mkdir -p monitor-data monitor-config
2chown 1000:1000 monitor-dataGraceful Shutdown
docker stop 送 SIGTERM → collector 收到後執行 shutdown 序列:
- 停止接受新的 HTTP request(listener close)
- 等待 in-flight request 完成(5 秒 context timeout)
- Flush pending writes(尚未寫入 storage 的事件,5 秒)
- 停止定期 job(downsample / purge / rule engine 定期評估)
- SQLite WAL checkpoint(TRUNCATE mode,15 秒)
- 關閉 DB connection
- 退出
步驟 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 中的事件)。
資源限制
| 資源 | 建議值(自用) | 建議值(小團隊) | 理由 |
|---|---|---|---|
| Memory | 256MB | 512MB | Collector + SQLite page cache + Go runtime |
| CPU | 0.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: 3restart: unless-stopped 讓 container 在 crash 或 host 重啟後自動恢復。healthcheck 讓 Docker 偵測 collector 是否真的在回應 — 只有 process 活著但 HTTP 不回應的場景也會被標記為 unhealthy。
和同機部署的效能對照
| 指標 | 同機 binary | Container + volume mount | Container 無 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%) |
| 查詢延遲 | baseline | baseline(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
| 場景 | 建議 | 理由 |
|---|---|---|
| 開源使用者快速試用 | Container | docker run 一行、不需裝 Go |
| 長期自用部署 | Binary + systemd | 效能最佳、無 container overhead |
| CI/CD 測試環境 | Container | 可拋棄式、每次乾淨環境 |
| Kubernetes 部署 | Container | pod 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)設定見斷網環境的監控與可觀測性。
下一步路由
- SQLite 效能基準的詳細數字 → SQLite Backend 效能基準
- 可插拔 Storage Backend 架構 → 規模演進
- Container runtime 通用原則(base image 選擇、build 可重現性、PID 1 信號處理)→ Backend 5.1 Container 與 Runtime
- 生命週期合約(startup / readiness / drain / shutdown 的責任分類)→ Backend 5.6 Platform Lifecycle Contract
- 容器化資源設計的通用原則 → DevOps 容器化資源設計
- 服務探活和自動恢復 → DevOps 服務探活
#monitoring #collector #docker #container #deployment #sqlite