免費不等於沒有代價,這篇記錄一次評估雲端部署、最後決定暫緩的完整過程。
一開始的打算跟大多數教學一樣:開一座 Oracle OKE (Kubernetes Engine) 叢集,享受託管式 K8S 的便利——Control Plane 由 Oracle 顧,我們只管 Worker Node。這個系列最初也是照著「最終會部署到 OKE」的假設在寫。
實際去建立叢集時,oci ce cluster create 直接回報 LimitExceeded。奇怪的是這個帳號從來沒建立過叢集,用量應該是 0 才對。查證方式很直接——oci limits value list 直接問 OCI 這個帳號在這個服務上的實際額度:
oci limits value list --service-name container-engine --compartment-id <tenancy-ocid>
回傳的 cluster-count 是 0,不是「用完了」,是這個帳號的方案從一開始就沒有這個服務。到 Console 的 Limits 頁面把 Service 篩選器打開找 Container Engine,結果是「No matches found」——OKE 這個服務選項在篩選清單裡根本不存在。非升級版的 Always Free 帳號,Container Engine for Kubernetes 完全不在提供範圍內,不是額度用完,是這個方案本來就沒開放。
既然拿不到託管式 K8S,另一條路是自己在一台 Always Free 的運算實例上裝 K3s——Rancher 開發的輕量化 K8S 發行版。
順便接續 Day 9 開頭那個 K8S numeronym 的梗:K3s 的名字其實也是雙關,不是真的照字母數砍出來的縮寫。Rancher 官方 FAQ 的說法是——他們想做一個記憶體佔用「減半」的 Kubernetes 發行版,而 Kubernetes 已經被縮寫成 K8s(10 個字母砍成 K+8+s),那乾脆把數字也砍半,8 減一半接近 3,就叫 K3s。所以 K3s 不是「Kubernetes 中間剩 3 個字母」,是刻意選一個比 8 小的數字,暗示「這是一個更小的 K8s」,梗接梗,滿有意思的。
K3s 常被拿來跟標準 K8S(例如 OKE 背後跑的那套)比較:
| K8S(標準 / OKE) | K3s | |
|---|---|---|
| 二進位大小 | 完整元件各自獨立 | 打包成單一 binary(<100MB) |
| 預設儲存 | etcd | SQLite(單節點)/可換成外接 etcd |
| 資源需求 | 較高,通常需要多顆 vCPU | 輕量,單顆 vCPU、1GB 記憶體就能跑 |
| 內建元件 | 不含 Ingress Controller | 內建 Traefik、內建 Klipper LoadBalancer |
| 適合場景 | 生產叢集、多節點高可用 | Edge、IoT、單節點demo、CI 測試環境 |
| API 相容性 | 標準 | 完全相容標準 K8S API,kubectl 指令一致 |
K3s 對「用一台免費的小機器跑起一個能對外服務的叢集」這個需求來說是合理的選擇——它跟標準 K8S 的操作介面完全一樣,只是把 Control Plane 做得更輕,塞得進一顆 OCPU 的機器。
先確認 Always Free 的運算額度本身沒被 OKE 那個問題波及:
oci limits value list --service-name compute --compartment-id <tenancy-ocid> --all
standard-a1-core-count、standard-a1-memory-count 都還在(Ampere A1,ARM 架構,最多 4 顆 OCPU / 24GB)。準備拿其中一部分額度建一台運算實例,跑單節點 K3s。
第一次嘗試建立運算實例,得到的是 QuotaExceeded: bootVolumeQuota Service limit reached。Always Free 的區域儲存額度是 200GB,照理說綽綽有餘。查下去才發現:舊 OKE 叢集刪除時,Worker Node 的 Boot Volume 跟 CSI 動態配置出來的 PV 並沒有跟著一起清乾淨——兩顆 47GB 的 Boot Volume、兩顆 50GB 的資料 Volume,全部沒有掛載在任何實例上,卻仍然算在額度裡,合計 194GB,把 200GB 幾乎吃滿。確認這 4 顆 Volume 確實沒有任何 attachment 之後,逐一刪除,釋放額度。
額度騰出來後,第二個坑馬上出現:InternalError: Out of host capacity。這是 Always Free 的 Ampere A1 在熱門區域一位難求的知名問題——免費的 ARM 運算資源太搶手,建立請求常常直接被拒絕,單純是當下沒有容量分給你。
寫了一支腳本自動重試(每次失敗間隔 30~45 秒再撞一次),2 OCPU / 12GB 的規格連續重試了 16 個小時、404 次,換成更小的 1 OCPU / 6GB 規格再試,又是好幾個小時、上百次,一樣沒有成功。
A1 一直撞不到容量,換個角度:Always Free 還有另一組免費運算額度——2 台 VM.Standard.E2.1.Micro(x86_64,1 OCPU / 1GB)。這個規格不像 Ampere A1 那麼熱門,兩台都順利建立成功,湊成一個雙節點 K3s 叢集(Server + Agent),總共 2 vCPU / 2GB。
第一個問題很快出現:兩台實例在同一個 Subnet,理所當然以為節點間可以互通,結果 Agent 一直卡在加入失敗。查下去才發現,這個 Subnet 沿用的是舊 OKE 留下的 Security List,Egress 規則被限制得極窄(只允許特定 NodePort 對內網一小段 CIDR),連對外的網路請求都被擋掉,更別說 K3s API(6443)、Flannel VXLAN(8472/udp)這些跨節點必要的通訊埠。補上對外 Egress 與站內全開的 Ingress 規則後,網路層打通了。
但緊接著的第二個問題才是真正的重點:Agent 節點的連線行為從「連不到」變成「連到但被斷開」,追查 Server 節點的 K3s 日誌,看到的是一長串:
level=warning msg="Slow SQL: SELECT ... FROM kine ..." duration=13.780160273s
level=error msg="apiserver was unable to write a JSON response: http: Handler timeout"
level=error msg="Post-timeout activity" timeElapsed="1m55.393463006s" method="GET" path="/api/v1/pods"
K3s 預設用 SQLite(透過 kine 這層轉譯)當資料庫,單一節點、單一 OCPU 的情況下,一個簡單的查詢動輒要 5~14 秒,有一次 API Server 甚至等了快 兩分鐘才逾時。這台機器連 Control Plane 自己都撐不住——不是記憶體不夠(雖然 1GB 確實也很緊),是這顆 1 OCPU 的效能等級,扛不動 K3s 對 CPU 的即時性要求,哪怕還沒部署任何一個應用 Pod。
這是一個和前面三個「額度限制」性質不同的坑:前面是「拿不到資源」,這次是「拿到了資源,但這個資源的等級撐不起要跑的東西」——免費層的規格說明只會告訴你 vCPU 數字,不會告訴你這顆 vCPU 實際的運算能力夠不夠用,這種事只有真的跑起來才會知道。
既然這 30 天要靠本機撐過去,補一下怎麼打開它——步驟很簡單:
kubeadm 拉起一個單節點叢集,第一次啟動大約需要幾分鐘裝好之後回到左側選單的 Kubernetes 項目,就能直接看到叢集狀態:

▲ Docker Desktop 的 Kubernetes 面板:Cluster 狀態 Active,Cluster type 是 kubeadm,單節點,版本 v1.34.1
這裡幾個資訊值得注意:Cluster type: kubeadm——代表 Docker Desktop 底層就是用標準的 kubeadm 拉起叢集,不是什麼閹割版,kubectl 對它下指令跟對雲端叢集下指令完全沒有語法差異;Nodes: 1——單節點,Control Plane 跟 Worker 是同一台機器,這也是為什麼本機開發特別適合拿來練功,沒有多節點網路配置要煩惱。裝完後 kubectl config get-contexts 會多一個 docker-desktop context,切過去就能開始下指令。
Docker Desktop 有一支 CLI(docker desktop),可以查狀態、列出 Kubernetes 用到的 image,但實測下來,這個版本沒有提供「enable kubernetes」的子指令:
$ docker desktop kubernetes --help
Usage: docker desktop kubernetes COMMAND
Manage Kubernetes
Commands:
images List Kubernetes images used by Docker Desktop
只有 images,沒有 enable/disable。真正能純指令切換的方法,是直接改設定檔。Windows 上 Docker Desktop 的設定存在:
%APPDATA%\Docker\settings-store.json
裡面有個 KubernetesEnabled 欄位(實測這台機器目前是 true):
$ grep -i kubernetes settings-store.json
"KubernetesEnabled": true,
純指令啟用的流程:
docker desktop stop
# 用 CLI 工具(jq、PowerShell 的 ConvertFrom-Json/ConvertTo-Json 都行)
# 把 settings-store.json 裡的 "KubernetesEnabled" 改成 true
docker desktop start
啟動完不用開 GUI,直接用 CLI 確認狀態:
$ docker desktop status
Name Value
Status running
SessionID 6662814f-05e0-4a2a-99c2-5c395ae4d8cd
$ kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO
* docker-desktop docker-desktop docker-desktop
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
docker-desktop Ready control-plane 90d v1.34.1
實際跑起來的畫面:

▲ 純指令驗證叢集狀態:context 是 docker-desktop、node 狀態 Ready、Docker Desktop 引擎 running
kubectl get nodes 看到 Ready 就代表叢集真的活著,這一步是 GUI 版本和純指令版本共通的最終驗證方式。
節點活著只是第一步,真正要看的是上面有沒有東西在跑。查一下 Wafer BI 實際部署的 Namespace:

▲ kubectl get pods -n k8sdemo -o wide:api-gateway、postgres、user-service、wafer-backend 四個服務都是 Running,已經穩定跑了 15 天
這才是這篇文章真正想證明的事——雖然沒有對外公開的網址,但本機這座叢集是貨真價實在跑的,不是空殼。
到這裡要做一個務實的判斷:容量問題是 OCI 那端的資源狀態,不是我這邊能控制的變數,繼續空等下去只會拖垮整個系列的進度。決定是:這 30 天接下來的所有實戰,都在本機 Docker Desktop 的 K8S 上進行——指令、YAML、行為都跟真正的雲端叢集一致,唯一的差別是沒有對外公開的網址。
雲端部署仍然是目標,只是不是「現在」。這幾天建立的 Helm Chart(Day 10 開始)本來就是為了讓部署可以直接搬遷而設計的——不管日後是 Always Free 的 A1 容量恢復(背景重試持續進行中)、申請到 OKE,還是換一顆付費的小機器,同一份 Chart 直接 helm install 上去就是了,不需要重寫任何東西。如果 30 天結束後真的把它部署上雲,會回來這篇補上連結。
這篇沒有一張「成功了」的截圖,取而代之的是一連串真實撞到的限制——帳號方案本身不含 OKE、孤兒 Volume 吃光儲存額度、ARM 運算容量連續兩天缺貨,換小規格機器後又發現 CPU 效能撐不住 K3s 的 Control Plane。免費層的真實樣貌往往比廣告頁面上寫的更複雜,「有資源」跟「這個資源夠用」也是兩件不同的事,這是評估任何「免費方案」時都值得記住的一課。明天繼續往下走:用 Helm 管理 Wafer BI 的部署文件。