WeaMind 專案解析:從單機 LINE Bot 到 K8s 叢集

📌 這是 WeaMind 系列 的第 5 篇。
本系列以真實世界專案為背景,記錄重要技術實作與經驗分享。
WeaMind 是一個查詢台灣天氣的 LINE Bot——點擊選單或輸入地名就能查天氣,是我花了數個月共 150+ 小時催生的 Side Project。
以下會從兩個層面介紹這個專案:
- 第一部分是應用層(你直接看到、用到的那一面),聊聊這個 Bot 本身的技術設計和工程品質追求。
- 第二部分是基礎設施層(支撐 Bot 在雲端運行的底層環境),談談為什麼我要把它部署到 K8s 叢集上。
從本文開始,這個寫了近 5 年的 Python 後端部落格,寫作重心將逐漸從後端開發轉向雲端與 DevOps。
這是一個全新的開始。
第一部分:一個天氣 Bot 的誕生
最好的 side project 往往來自於自己的痛點。
我個人是中央氣象局「生活氣象 App 」的重度使用者,但每次要在不同行政區之間切換查天氣,操作都很麻煩。
這個痛點存在很久了,一直希望有更好的手機查詢體驗。考慮到氣象資料有公開資料與 API,我就想說:「乾脆自己做一個 LINE Bot 吧!」
除此之外,它必須是一個能展示我真實 Infra 能力的作品——不只是把 Bot 寫好,而是從程式碼到雲端部署,都有真實工程水準的那種。
多年前,從法律公務員轉職軟體工程師,我正是花了兩個月做了這個「給嗜讀者的 Python Flask 網頁服務」才順利找到工作。
三個第一次
這是我第一次做 LINE Bot——要學習令人痛苦的 LINE Bot SDK - Python😂——強烈懷疑它就是 Java 版本直接「翻譯」過來,程式碼非常不 Pythonic!
第一次用 FastAPI(工作上都是 Django)、第一次大量使用 AI(GitHub Copilot)輔助開發。
三個「第一次」疊在一起,學習曲線並不低。但正因為有 Copilot 全程參與,一個人也能在合理時間內把這些東西做出來——還做得有模有樣!
老實說,如果沒有 AI 輔助,我大概不會想一口氣挑戰「三個第一次」。
這大概就是 AI 時代的開發樣貌。
功能介紹
WeaMind 的核心功能很直覺:直接輸入行政區名稱就能查天氣,像是「大安區」、「中壢」;也可以設定住家和公司地址,之後一鍵查詢——後者才是重點,因為它真正解決了我切換行政區的難題。
還有地圖查詢功能,在 LINE 地圖上選任意位置就能查。系統也會記錄最近查過的 5 個地點,方便快速重複查詢。
WeaMind UI
我相信這就是一個好的 Bot 該做的事:簡單易用、直指痛點。
應用層架構設計
做過 LINE Bot 的人都知道,它有幾個特別的「坑」:
- webhook 必須在時限內回應(具體要多久之內是動態的,官方不會告訴你),否則 LINE Platform 會重送請求——用戶此時會收到重複的回覆,影響體驗。
- 無論有心還是無意,少部分用戶可能會瘋狂連點按鈕XD。而 LINE Platform 並未提供類似 rate limit 的機制,後端對每個請求都要處理——你的 db 正在燃燒🔥
- webhook handler 邏輯不同於一般 API 開發,天然容易變成一團大泥球——解析、查詢、回覆全擠在一起,功能一多就很難看懂與維護。
WeaMind 對這三個問題都有明確的解法:
- Fast ACK Webhook:花數十毫秒先回應 LINE Platform,業務邏輯交給 FastAPI 的
BackgroundTasks非同步運行,用戶感受到的回應在 2 秒以內——實際只需要幾百毫秒,但 Hetzner 主機對台灣的延遲很高😂 - Redis 分散式鎖:按鈕操作有 2 秒鎖定以防止連點,文字查詢則不受影響。
- DDD 模組劃分:
core、user、line、weather四個領域,並以 router → service → model 三層分離,邊界清晰不互相污染。
更多細節可參考 WeaMind 專案 README 的「開發者技術亮點」。
工程品質的「刻意」追求
自己的專案,可以用自己的標準。
我親愛的偏執狂
100% 的 type hints 覆蓋——使用 Pyright、94% 的單元測試覆蓋率、pre-commit 強制檢查、完整的 CI pipeline。
相關文章:
這些都不是偶然,而是刻意選擇的結果。在自己的專案裡,終於沒有人會說「這個可以之後再補」。
你可能會好奇「為什麼測試覆蓋率不是 100%?現在不是有 AI 了嗎?讓它一口氣寫完不就好了?」
我的看法是,覆蓋率畢竟只是測試品質的其中一個面向而已。為了讓覆蓋率達到百分之百,而寫一大堆不好讀、不好維護的機械性測試邏輯,這不符合我的開發價值觀。
哪怕是在 AI 時代,凡事也宜適可而止。
偏執狂的工具鏈與 CI
工具鏈基本上是,想的到的一口氣全上了——說好的適可而止呢XD
uv 管理 Python 虛擬環境;Ruff 一個工具取代了 Flake8、Black、isort 三個;Pyright 做型別檢查。
相關文章:
安全掃描建了三層,角色不同:Bandit(靜態分析)、pip-audit(CVE 檢查)、detect-secrets(敏感資料防護)。這是我以前比較生疏的部分,這次特別加強。
每次 push 都會跑完 Ruff → Pyright → Bandit → pip-audit → pytest + Codecov 的完整流程。
第一階段 CI 通過後,最後會自動推送新版 image 到 GHCR。
第二部分:從單機到叢集
講完應用層,你可能會想:
這個 LINE Bot 跑在單機 VM 上就能正常運作了,幹嘛還要搞 K8s?
原因很簡單:因為這個專案從一開始就「不只是」為了做一個 LINE Bot——它是我為了 DevOps 轉職所做的刻意練習。
K3s on Hetzner
我在 Hetzner 上另外開了三台 VM 組成 K3s 叢集,加上一台付費的 Load Balancer——都是錢啊啊啊!🤯
原本那台跑 Docker Compose 的 VM,在叢集建成後角色順勢轉變為堡壘機兼資料層,負責跑 PostgreSQL 和 Redis,同時作為 SSH 跳板與管理叢集——雖然我大部分時候都是在本機透過ProxyJump連到 Control Plane 上的 API Server。
持續演進的平台
對我來說,這套 Kubernetes 部署不是做完就收工的作品。接下來,我還想在上面實作可觀測性、Helm 等 DevOps 必修課題。
任何我想學的 DevOps 技能,都可以在這上面繼續疊加。它顯然比任何線上課附的 lab 都有趣且真實多了——想想就有點小興奮🤩
Infra 關鍵架構決策
叢集是 K3s 三節點(1 控制平面 + 2 工作節點),跑在 Hetzner 私有網路內;資料層(PostgreSQL + Redis)留在堡壘機,透過內網連接。
流量路徑:LINE Platform 的 webhook 請求會先到 Hetzner Load Balancer(TCP 443 passthrough),再由 Traefik Ingress Controller 完成 TLS 終止,最後轉送到 line-bot Service 與 line-bot Pod。
為什麼選 K3s?
K3s 是由 Rancher(現為 SUSE)推出的輕量級 Kubernetes 發行版,專為資源受限的環境設計。
K3s 這個名稱意味「比 K8s 少 5 個字元的複雜度」——本質上就是一個更精簡、更易部署的 Kubernetes。
採單一 binary 檔、內建 Traefik Ingress Controller,並通過 CNCF 認證。
對於單人維運的小型叢集,它可能是最接近真實環境(相比於 Minikube、Kind)而且最務實的選擇。
為什麼選 Hetzner?
當然是因為便宜!雖然它在今年 4 月就要漲價了🥲
表格是整個架構的所有成本(每月/歐元,漲價前、稅前):
| 節點 | 規格 | 月費 |
|---|---|---|
| 原 VM(堡壘機兼資料層) | 4C/8GB | €6.5 |
| K8s 控制平面 | 2C/4GB | €3.5 |
| K8s 工作節點 × 2 | 2C/4GB | €7 |
| Hetzner Load Balancer | — | €5 |
| 合計 | €22/月 |
總體開銷,差不多等於訂閱一個 ChatGPT Plus 的月費。
但同樣的機器若要部署在 DigitalOcean 或者 GCP 上,所需的價格大概是 4 到 6 倍。(即使 Hetzner 漲價後也要 3 到 5 倍)
總之,我愛 Hetzner,德國人萬歲!
資料層為什麼留在 VM?
簡潔與穩定性優先。
PostgreSQL 和 Redis 現階段不需要水平擴展——以後大概也不需要XD,放進 K8s 反而要處理 StatefulSet 和 PV 的管理複雜度。
而且用內網連接,延遲幾乎可以忽略不計。
LINE Webhook URL 切換
這是一個意外好用的設計!
我原本的想法是:LINE webhook 固定打 api.kyomind.tw(你直接點擊網址,會回應{"message":"Welcome to WeaMind API"})上的 webhook endpoint,平常 DNS 的 A record 指向 Hetzner Load Balancer。
若叢集出問題需要 rollback,就手動把 A record 改回舊 VM 的 IP 地址,這樣就能快速切換回單機環境。
但這樣的問題是 DNS 傳播有延遲,少則幾分鐘、多則數小時,rollback 並不即時。
後來發現一個更簡單的做法:保留 api.kyomind.tw 指向舊 VM,另外新建一個 k8s.kyomind.tw 指向 Hetzner Load Balancer——兩個環境各自有固定網址。
切換時,只要在 LINE Platform 後台把 webhook URL 從一個換成另一個,秒級生效,不需要動 DNS。
這讓 K8s 環境和原本的單機環境可以並行運行,測試和 rollback 都很方便。
結語
WeaMind 對我來說不只是一個天氣 Bot。
應用層是五年後端經驗的集大成之作——終於有機會按自己的標準,從頭到尾做一次。基礎設施則是跨入 DevOps 的第一步,用真金白銀搭出來的練兵場🐥
如同我在自己的 Landing Page 說的:
AI 與 Infra 是開發者的未來。
而 WeaMind,則是未來的開端。
想看程式碼的讀者,歡迎逛逛 GitHub,並給我一個 ⭐️ 唷!
- 應用層:WeaMind
- 基礎設施:weamind-infra
想試用 WeaMind,可掃描下方 QR Code 或搜尋 LINE ID
@370ndhmf加入好友
你的支持是我持續分享的動力
