iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0
AI Engineering

Agent 開發, 從本地到雲端: AI 工程師的 K8s 筆記系列 第 13 篇

Day 13|把一隻 bot 從 compose 搬到 k3s:Deployment + Secret + ConfigMap 最小集

  • 分享至 

  • xImage
  •  

cover

昨天叢集起來了,今天把一隻真的 bot 搬進去。

我拿 OpenAB 艦隊裡最單純的一隻當例子:它是一個長駐的 bot 容器,需要幾個 token(機密)、幾個設定值(非機密),然後一直跑著。這正好對應 K8s 的三個最小物件:Secret、ConfigMap、Deployment。

一樣先聲明:這篇的 YAML 是我從 compose 定義翻譯過來的最小版本,image 名稱用 placeholder,寫這篇時還沒在叢集上實跑驗證(verified: false)。

從 compose 看起:你已經寫過答案了

在 compose 裡,這隻 bot 長這樣(簡化):一個 image、一個 env_file 放 token、幾個 environment 放設定、restart: unless-stopped。翻譯對照:

  • env_file 裡的 token → Secret
  • environment 裡的一般設定 → ConfigMap
  • image + restart → Deployment(replicas: 1)

Day 11 說過「compose 裡寫下的答案就是搬家時的輸入」,就是這個意思。

Secret 與 ConfigMap

Secret 我不寫成 YAML 檔。compose 用的 .env 已經有 token 了,直接 kubectl create secret generic bot-secrets --from-env-file=.env 從它產生,是從 compose 平移最省事的做法,也不會有一份明文 token 躺在 repo 裡等著被 commit。

注意:Secret 在 K8s 裡預設不是加密的,K8s 會幫你 base64 編碼存起來,但編碼不是加密,這點跟很多人的直覺不同。真正的機密管理(外部 vault、加密 etcd)超出本系列範圍,官方說明見 Secrets 與 Encrypting Confidential Data at Rest。

ConfigMap 則是非機密設定,寫成檔案 commit 進 repo 沒問題(config.yaml):

apiVersion: v1
kind: ConfigMap
metadata:
  name: bot-config
data:
  LOG_LEVEL: "info"
  BOT_NAME: "my-first-bot"

Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-first-bot
spec:
  replicas: 1
  selector:
    matchLabels: { app: my-first-bot }
  template:
    metadata:
      labels: { app: my-first-bot }
    spec:
      containers:
      - name: bot
        image: ghcr.io/example/my-bot:latest
        envFrom:
        - secretRef: { name: bot-secrets }
        - configMapRef: { name: bot-config }
        resources:
          requests: { memory: "64Mi", cpu: "50m" }
          limits: { memory: "256Mi" }

幾個我當初卡住的點:

  • selector.matchLabels 和 template.metadata.labels 必須一致,不然 Deployment 找不到自己的 Pod。
  • envFrom 一次把整個 Secret / ConfigMap 灌成環境變數,跟 compose 的 env_file 行為最接近。
  • resources 我刻意寫了。我實測的 bot 容器記憶體多在個位數到數十 MiB,所以 request 給 64Mi 很寬裕;limit 給 256Mi 是留給突發。Day 09 講過 limit 太小會被 OOMKilled,這裡先給寬一點,觀察後再收。
  • 沒有 Service。這隻 bot 是主動連出去(連 Discord / LINE 平台),不對外開 port,所以不需要 Service。如果你的 agent 是提供 HTTP API 的那種,才需要加 Service(Day 24 會做)。

送進去

先把 image 換成你自己的;照抄 example 的 placeholder 會 ImagePullBackOff。然後依序:

  1. kubectl create secret generic bot-secrets --from-env-file=.env(Secret 只從這裡來,不 apply 任何 YAML)
  2. kubectl apply -f config.yaml -f deployment.yaml
  3. kubectl get pods -l app=my-first-bot
  4. kubectl logs -l app=my-first-bot --tail=50

看到 Running 不代表成功:bot 可能起來了但 token 錯、連不上平台。所以第四步一定要跑,看它自己的 log 說什麼。如果看到的是 CrashLoopBackOff,明天見。

跟 compose 差在哪

最大的差別不是 YAML 比較長,而是:compose 的 restart: unless-stopped 是「這台機器上的這個容器掛了就重啟」;Deployment 的 replicas: 1 是「要一直維持一個這樣的 Pod,掛了就補、補不起來就一直試」。

前者綁定機器,後者綁定期望狀態。這個差別在單機 k3s 上感覺不到,到雲端多節點才會體會到。

今日一句話

compose 檔裡的每一行,都已經是 K8s 物件的答案;搬過去的工作是翻譯,不是重寫。

延伸閱讀


上一篇
Day 12|在一台 VM 上裝 k3s,10 分鐘:含踩雷(cgroup、記憶體門檻、Spot VM 重開後的自癒)
下一篇
Day 14|它為什麼一直 CrashLoopBackOff:AI 工程師最常撞的 5 種錯與看 log 的方法
系列文
Agent 開發, 從本地到雲端: AI 工程師的 K8s 筆記 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言