一句話結論:用 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 都固定版本,什麼時候換由自己決定,不是某天自動發生的。
延伸閱讀
- k3d:Using config files:
Simple設定檔的用法與範例。這一頁沒有列出runtime.serversMemory/agentsMemory,完整欄位請看它連到的 JSON Schema。 - K3s:Volumes and Storage:預設的儲存路徑是
/var/lib/rancher/k3s/storage,以及--default-local-storage-path旗標。 - Local Path Provisioner 的 README:
nodePathMap與DEFAULT_PATH_FOR_NON_LISTED_NODES的說明(步驟七 patch 的就是這份設定)。 - K3s:
k3s server參數:--disable可關閉的內建元件(coredns、servicelb、traefik、local-storage、metrics-server、runtimes)。 - Docker:Install Docker Engine on Ubuntu:官方安裝步驟,上面的指令就是依這份。
- Docker:JSON File logging driver:
max-size(預設不限制)、max-file(要搭配max-size才有效)的說明。 - Docker:Linux post-installation steps:不用
sudo執行 Docker,以及docker群組的安全性提醒。
這篇的環境是 Ubuntu LTS、k3d v5.8.3、k3s v1.31.5。你的版本不同的話,記得對一下欄位名稱。

