
POV:你的機器才剛剛被無預警重開,10 幾隻 bot 在 22 秒內自己回復,但 tmux session 掛了,裡面有紀錄你相關監控的討論跟工作。
昨天講了一隻 bot 長大到多個 bot 逐漸失控。今天改用我的雲端 VM 正在跑的設定檔,來回答更基本的一件事:在還沒碰 K8s 之前,container 到底替我擋掉了哪些事?又有哪些事它擋不住?
我們來看一個真的在跑的服務範例。這是其中之一 LINE bot docker compose:
services:
lineoa-course:
image: ghcr.io/openabdev/openab:0.9.0-beta.10-codex
restart: unless-stopped
env_file: [lineoa-course.env]
volumes:
- ./config.toml:/etc/openab/config.toml:ro
- codex-home:/home/node/.codex
ports:
- "127.0.0.1:8092:8080"
healthcheck:
test: ["CMD-SHELL", "pgrep -x openab || exit 1"]
interval: 30s
cap_drop: [ALL]
cloudflared:
image: cloudflare/cloudflared:latest
restart: unless-stopped
command: tunnel --config /etc/cloudflared/config.yml run
depends_on: [lineoa-course]
然後我們來逐一敘述痛點,還有可以怎麼透過 docker compose 解決。
通常 AI Agent 專案會有很多依賴:RAG Inference Engine、Whisper STT Engine、FastChat/vLLM Engine,再加一個 Next.js 前端。以前用 venv、conda、nvm 管理引擎版本,但是 library、環境變數、~/.cache 底下的東西也都要考慮,這表示也常常會漏掉。
而在 image: …:0.9.0-beta.10-codex 直接解釋這個:bot 需要什麼,一開始會打包成 image tag容器的 root filesystem,如果跑起來之後,要另外裝/升級 libraries,在重新開啟之後,就又回復到原本的 image。所以要保留必要的套件與版本,就要在 image 相關的底下作設定。
另外還有個要分享的:目前 volumes 只掛載兩個部分,一份唯讀的 config、一個具名 volume 放 bot 自己的狀態。但是不會放上 source code、跟其他 bot 的共用資料,這隻 bot 一概看不到。這就是透過 container 能夠隔離出不同 bot 的好處
同一台機器上,我現在有兩個 qdrant 在跑(RAG 研究環境、LINEOA 研究環境),還有十幾個 web 服務。首先就會先需要解決 port 相撞的問題。
Container 把這件事收斂成設定檔裡的一行。"127.0.0.1:8092:8080" 這行有兩個意思:容器內固定聽 8080(程式不用改),對外是 8092;而且只綁定本機不直接對外。另一個研究環境訂定慣例:host 端一律用 5 開頭的五位數,54001:4001、56333:6333,方便區分不同目的的專案。
資源設定,docker compose 可以設 mem_limit。我的 RAG 研究環境有設:
docker inspect openab-lineoa-course \
--format '{{.HostConfig.RestartPolicy.Name}} {{.HostConfig.Memory}}'
# unless-stopped 0
docker stats --no-stream --format '{{.Name}}\t{{.MemUsage}}'
特別看一下 0:表示...我的 bot 全部沒設記憶體上限。 原因是一開始覺得 bot 很省(實測多在個位數到數十 MiB),所以沒特定設定管理。通常這個狀況,最後還是會遇到問題,導致需要重新檢視資源配置這些設定。
還有一個參數:mem_limit 是理論「上限」,不是「保證」。設了 6 GiB,不代表作業系統會替它留 6 GiB;如果其他 Container 或 Process 把記憶體吃光,它一樣會被波及。
以我的例子,開發機器是 Spot VM,隨時可能被 Terminate。就在昨天早上,又重開了一次。接著我用 docker inspect 看每個容器的 StartedAt:所以 bot 都在 22 秒後回來。
docker inspect $(docker ps -q) \
--format '{{.Name}} {{.State.StartedAt}}' | sort -k2
回頭看 docker compose: restart: unless-stopped:所以機器重開,Container 也會自動重開;再配上 healthcheck 的 pgrep -x openab,docker ps 會直接告訴我哪隻是 healthy、哪隻只是活著。
回頭對照如果尚未 Containerize 的服務。同一台機器上,我的 tmux 工作區和 coding agent session 是靠自己寫的還原腳本,重開之後有一部分沒回來(細節留到 Day 18)。
mem_limit 只是理論上限,不代表保證;我的 bot 連上限都沒設。depends_on 只管啟動順序;bot 起來但還沒 ready,tunnel 就已經在轉流量了。要等到 healthy 得另外寫 condition: service_healthy。rag-engine:4001 這種),但範圍預設只在同一台機器、同一個 compose 專案裡。這四件事,就是 K8s 存在的理由。但我還是建議:先理解這篇 docker compose 簡單的範例,確認三個痛點真的被解決了,再去碰 K8s。
接著,明天會提到如果「沒設上限」的代價。
Container 幫你把「這個服務需要什麼」寫下來;K8s 幫你把「這些服務之間的關係」寫下來。今天我們先學會服務需要什麼。
restart、healthcheck、depends_on 與 condition: service_healthy、mem_limit
docker stats