一句話結論:用 k3d 可以在一台機器上用 Docker 容器模擬多節點的 Kubernetes,但要記得兩件事:PV 的資料要掛到主機上,還有 k3s 本來就內建 local-path-provisioner,不要再裝一套。

遇到的問題

需求很單純:一個可以隨便玩、隨便砍、但資料不會跟著消失的 Kubernetes 環境。

放在自己的機器上,不用每個月付雲端的錢。

最省事的選擇其實是 minikube 或 kind。

但我這次要的是多節點,想試 Pod 排程、節點故障這些行為,而且要接近真實的 k3s。

k3d 剛好符合:它把 k3s 包在 Docker 容器裡,一個容器就是一個節點,建一個 3 節點叢集大概一分鐘。

這篇從一台乾淨的 Ubuntu 開始,走完整個建置過程,也會講到中間踩到的幾個坑。

手把手步驟

步驟一:裝 Docker

k3d 的節點是 Docker 容器,所以 Docker 是第一個要裝的。

用 Docker 官方的 apt 套件庫來裝(不是 Ubuntu 預設套件庫裡那個舊版):

sudo apt-get update sudo apt-get install -y ca-certificates curl sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc sudo tee /etc/apt/sources.list.d/docker.sources <<EOF Types: deb URIs: https://download.docker.com/linux/ubuntu Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") Components: stable Architectures: $(dpkg --print-architecture) Signed-By: /etc/apt/keyrings/docker.asc EOF sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io \ docker-buildx-plugin docker-compose-plugin 

這是照 Docker 官方文件現在寫的方式:金鑰直接存成 docker.asc,套件庫用 .sources 檔。

網路上很多教學還在用比較舊的 gpg --dearmor 加 docker.list 那一套,現在應該也還能用。

新裝的話,建議跟著官方文件走。

然後把自己加進 docker 群組,這樣之後不用每次 sudo:

sudo usermod -aG docker "$USER" 

這一步做完要登出再登入一次,群組才會生效。

要注意:docker 群組等於給了該使用者 root 等級的權限,Docker 官方文件有特別提醒。

單人使用的機器可以接受這個取捨;共用的機器請改用官方介紹的 rootless 模式。

步驟二:先設定 Docker 的 log 輪替

這一步很容易被跳過,但建議在建叢集之前就做。

Docker 預設不會限制容器 log 的大小,叢集跑久了,每個節點容器的 log 會一直長大。

sudo tee /etc/docker/daemon.json >/dev/null <<'EOF' { "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "5" } } EOF sudo systemctl restart docker 

意思是:每個容器的 log 最多 5 個檔案,每個最多 10 MB。

如果你的機器上已經有 /etc/docker/daemon.json,不要直接覆蓋,用 jq -s 'add' 把兩份合併。

小提醒:Docker 重啟之後,已經存在的 k3d 叢集不一定會自己起來,要用 k3d cluster start <名稱> 把它叫起來。

步驟三:裝 kubectl、helm、k3d

# kubectl curl -fsSLo /tmp/kubectl \ "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" sudo install -m 0755 /tmp/kubectl /usr/local/bin/kubectl # helm curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash # k3d(固定版本,避免哪天自動升級後行為變了) curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | TAG=v5.8.3 bash 

確認都裝好了:

docker --version kubectl version --client helm version k3d version 

步驟四:準備一個放 PV 資料的資料夾

這一步先講結論,後面「踩到的坑」會解釋為什麼。

先在主機上建一個資料夾,之後會把它掛進每個節點:

sudo mkdir -p /srv/k3s-storage sudo chown "$USER":"$USER" /srv/k3s-storage 

路徑請換成你自己的。如果你有第二顆比較大的硬碟,放在那顆上會更好。

步驟五:寫叢集設定檔

k3d 支援用一份 YAML 描述整個叢集,比一長串命令列參數好維護,也方便放進 Git。新建 cluster.yaml:

apiVersion: k3d.io/v1alpha5 kind: Simple metadata: name: dev servers: 1 agents: 2 image: rancher/k3s:v1.31.5-k3s1 ports: - port: 80:80 nodeFilters: - loadbalancer - port: 443:443 nodeFilters: - loadbalancer volumes: - volume: /srv/k3s-storage:/srv/k3s-storage nodeFilters: - all options: k3d: wait: true timeout: "300s" kubeconfig: updateDefaultKubeconfig: true switchCurrentContext: true k3s: extraArgs: - arg: --disable=traefik nodeFilters: - server:* runtime: serversMemory: "6GB" agentsMemory: "6GB" 

逐段說明一下。

節點數量

servers: 1、agents: 2,也就是 1 個控制平面節點加 2 個工作節點,共 3 個。

k3s 版本

image 固定在某個版本。不固定的話,k3d 會用它的預設版本,哪天升級後行為可能就不一樣了。

對外埠

ports 把主機的 80 和 443 轉進叢集,之後放 Ingress 才有入口。

資料夾掛載

volumes 把剛剛那個資料夾掛進所有節點。

Traefik

k3s 預設內建 Traefik 當 Ingress Controller。

如果之後想自己選別的,就用 --disable=traefik 先關掉。沒有特別偏好的話,直接用 Traefik 也完全可以。

記憶體上限

runtime.serversMemory 和 agentsMemory 用來限制每個節點容器的記憶體,這個很容易漏掉。

沒設的話,每個節點容器都會回報「整台主機的記憶體」當自己的上限,三個節點加起來就是三倍的假容量。

排程器以為資源很多,實際一起吃的時候,主機自己會被擠到沒記憶體。

請依機器大小設定,並且留一些給主機本身。

步驟六:建叢集

k3d cluster create --config cluster.yaml 

大概一分鐘後完成。確認:

kubectl get nodes 

應該會看到 3 個節點,狀態都是 Ready:

NAME STATUS ROLES AGE VERSION k3d-dev-server-0 Ready control-plane,master 1m v1.31.5+k3s1 k3d-dev-agent-0 Ready <none> 1m v1.31.5+k3s1 k3d-dev-agent-1 Ready <none> 1m v1.31.5+k3s1 

步驟七:把 local-path-provisioner 的資料指到主機上(重點)

k3s 內建一個 local-path-provisioner,PVC 建立後,它會在節點上建一個資料夾存資料。預設路徑是節點容器裡的 /var/lib/rancher/k3s/storage。

而這個路徑沒有掛到主機上。

也就是說,只要節點容器被刪掉重建,PV 的資料就跟著消失了。這對一個「隨時會砍掉重練」的 k3d 叢集來說非常危險。

修法是把它的設定改指向步驟四掛進去的那個資料夾:

kubectl wait --for=condition=available deployment/local-path-provisioner \ -n kube-system --timeout=120s kubectl patch configmap local-path-config -n kube-system --type merge \ -p '{"data":{"config.json":"{\"nodePathMap\":[{\"node\":\"DEFAULT_PATH_FOR_NON_LISTED_NODES\",\"paths\":[\"/srv/k3s-storage\"]}]}"}}' kubectl rollout restart deployment local-path-provisioner -n kube-system kubectl rollout status deployment local-path-provisioner -n kube-system --timeout=60s 

再順手把它設成預設的 StorageClass(k3s 通常已經設好了,這行只是確保):

kubectl patch storageclass local-path \ -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}' 

之後建立的 PVC,資料就會落在主機的 /srv/k3s-storage 底下。你可以建一個測試 PVC 驗證一下,再 ls /srv/k3s-storage 看看有沒有出現資料夾。

另一種做法:直接用 k3s 的官方旗標

翻 k3s 文件才發現,官方其實有提供更直接的旗標:在 server 節點加上 --default-local-storage-path,就能指定預設的儲存路徑。放進 cluster.yaml 大概是這樣:

options: k3s: extraArgs: - arg: --default-local-storage-path=/srv/k3s-storage nodeFilters: - server:* 

本篇用的是上面 patch ConfigMap 的做法,這個旗標沒有實際跑過,要用的話請先在臨時叢集上驗證一下。兩種擇一就好,不要同時用。

日常操作

k3d cluster stop dev # 停止(資料保留) k3d cluster start dev # 啟動 k3d cluster delete dev # 刪除(掛到主機的資料還在) 

踩到的坑

1. 不要再另外裝一套 local-path-provisioner

我當初照著網路上的教學,apply 了官方社群版的 local-path-storage.yaml。

結果新裝的那套一直 CrashLoopBackOff。

後來才搞懂:k3s 本來就內建一套,兩邊的 ClusterRole 與 ClusterRoleBinding 名稱一樣,而且是 cluster 層級的資源。

k3s 的 Addon controller 會一直把它們 reconcile 回內建的版本,新裝那套的 subject namespace 就被蓋掉,沒有權限。

正確做法是上面的步驟七:直接改內建那套的設定,不要再裝新的。

2. PV 資料預設會隨節點容器一起消失

這個我差點中招。

k3d 的節點是容器,容器被刪除重建,容器內的檔案系統就沒了。

一定要用 volumes 把主機資料夾掛進去,再讓 provisioner 寫到那裡。

3. 重啟 Docker 之後要手動把叢集叫起來

改了 daemon.json 重啟 Docker,叢集的容器不一定會自動恢復運作。

記得 k3d cluster start。

為什麼這樣選

k3d 而不是 kind/minikube

要的是多節點加接近真實的 k3s,k3d 在這兩點上最順手。

只要單節點的快速環境的話,minikube 或 kind 都比較簡單。

用設定檔而不是命令列參數

設定檔可以進 Git,之後要重建叢集、跟別人分享,都只要一個檔案。

固定版本

k3d 和 k3s 都固定版本,什麼時候換由自己決定,不是某天自動發生的。

延伸閱讀

這篇的環境是 Ubuntu LTS、k3d v5.8.3、k3s v1.31.5。你的版本不同的話,記得對一下欄位名稱。