伺服器維運

k8s部署教程:用 kubeadm 從零搭建 Kubernetes 叢集

在自有伺服器上做 k8s部署,最直接的方式是官方工具 kubeadm:每台伺服器裝好 containerd 和 kubeadm、kubelet、kubectl,在一台上執行 kubeadm init 生成控制平面,其餘節點用 kubeadm join 加入,再裝一個網路外掛,節點變成 Ready 就完成了。動手前先定好版本和 Pod、Service 網段,網段定下之後很難再改。

作者 頂聯產品團隊發布於 6 分鐘閱讀

k8s部署(Kubernetes 部署)在自有伺服器上,用官方工具 kubeadm 最直接,完整流程分六步:所有節點做系統準備(關閉 swap、載入核心模組、開啟轉發)→ 安裝 containerd 並改用 systemd cgroup → 安裝 kubeadm、kubelet、kubectl → 在控制平面節點執行 kubeadm init → 安裝 Calico 或 Flannel 網路外掛 → 在 worker 上執行 kubeadm join 並驗證。下面以 3 台 Ubuntu 22.04 或 24.04 伺服器、Kubernetes 1.37 為例,每一步都給出可以直接執行的命令,Rocky、AlmaLinux 的差異在相應位置說明。

部署前先定三件事:節點、版本和網段

節點規劃

主機名 角色 內網 IP 最低配置
k8s-cp1 控制平面 10.10.20.11 2 核 CPU、2 GB 記憶體,建議 4 GB 以上
k8s-node1 worker 10.10.20.12 2 GB 記憶體起,按業務定
k8s-node2 worker 10.10.20.13 同上

每個節點的主機名、MAC 位址和 product_uuid 必須唯一。用同一個範本克隆的虛擬機器、用同一個映象批次裝的物理機,都要逐台執行 sudo cat /sys/class/dmi/id/product_uuid 核對。節點之間要互通的連接埠:控制平面 TCP 6443、2379–2380、10250、10257、10259,worker TCP 10250、10256 和 NodePort 範圍 30000–32767,另加網路外掛用的連接埠(見後文)。測試環境裡節點在同一內網段時,可以先放通節點之間的全部流量。

版本:k8s 最新版本怎麼選

Kubernetes 每年釋出三個左右的小版本,每個小版本釋出後約有 14 個月的補丁支援,期間大約每月出一個補丁版本。按官方釋出頁(kubernetes.io/releases)的資料,仍在支援期內的小版本如下:

小版本 釋出日期 支援結束
1.37 2026-08-26 2027-10-28
1.36 2026-04-22 2027-06-28
1.35 2025-12-17 2027-02-28

1.34 的支援已於 2026 年 10 月 27 日結束;1.38 按釋出計劃在 2026 年 12 月 16 日釋出。補丁號每個月都在變,安裝前用 curl -sL https://dl.k8s.io/release/stable.txt 查當前最新的穩定版,或者直接看官方釋出頁。本文範例用 1.37,選版本按三條規則:

  • 新叢集選最新的、已經出過一兩個補丁的小版本;剛釋出的 .0 版先在測試叢集裡跑。
  • kubeadm、kubelet、kubectl 裝同一個版本。kubelet 可以比 API Server 舊,最多舊三個小版本,但不能比它新;kubectl 與 API Server 相差不超過一個小版本。
  • 升級只能逐個小版本進行,1.35 要到 1.37,必須先升 1.36。所以別裝即將停止支援的版本,否則剛上線就要排升級。

網段:Pod 和 Service 不能與機房已有網段重疊

網段 用途 常見預設值 本例取值
節點網路 伺服器網路卡位址 機房分配 10.10.20.0/24
Pod 網段 每個 Pod 一個 IP Calico 清單 192.168.0.0/16,Flannel 10.244.0.0/16 172.20.0.0/16
Service 網段 ClusterIP 虛擬位址 kubeadm 預設 10.96.0.0/12 172.21.0.0/16

機房環境裡,預設值很容易撞車:10.96.0.0/12 覆蓋 10.96.0.0 到 10.111.255.255,機房業務網如果用的是 10.100.x.x,就正好落在裡面;192.168.0.0/16 則經常和辦公網、BMC 管理網重疊。一旦重疊,Pod 存取這些內網機器時可能被當成 Service 位址處理,或者路由走錯,表現為叢集內存取某幾台機器不通、叢集外卻正常,非常難查。本例選 172.20.0.0/16 和 172.21.0.0/16,同時避開了 Docker 預設的 172.17.0.0/16。

容量也要算一下:kubeadm 預設給每個節點切一個 /24 的 Pod 子網(254 個可用位址,而 kubelet 預設每個節點最多執行 110 個 Pod),/16 的 Pod 網段最多容納 256 個節點。Flannel 按這個劃分給節點分配位址;Calico 預設不用它,而是按 /26(64 個位址)的塊按需分給各節點。

所有節點:系統準備、containerd 與 kubeadm

以下命令在三台伺服器上都要執行。

系統引數

# 1. 主機名和解析,每台把 hostname 改成自己的名字
sudo hostnamectl set-hostname k8s-cp1
cat <<EOF | sudo tee -a /etc/hosts
10.10.20.11 k8s-cp1 k8s-api.example.internal
10.10.20.12 k8s-node1
10.10.20.13 k8s-node2
EOF

# 2. 關閉 swap,並註釋 /etc/fstab 裡的 swap 行,防止重啟後恢復
sudo swapoff -a
sudo sed -ri 's/^([^#].*\sswap\s.*)$/#\1/' /etc/fstab

# 3. 開機載入核心模組
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter

# 4. 核心引數:開啟 IPv4 轉發,讓網橋上的流量經過 iptables
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.ipv4.ip_forward                 = 1
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
sudo sysctl --system

# 5. 時間同步,證書校驗依賴準確的時間
sudo apt-get update && sudo apt-get install -y chrony
sudo systemctl enable --now chrony

kubelet 預設檢測到 swap 就拒絕啟動。新版本可以配置成容忍 swap,但入門部署直接關掉最省事。Rocky、AlmaLinux 還要把 SELinux 設為 permissive(sudo setenforce 0,並修改 /etc/selinux/config),並在 firewalld 裡放行上面列出的連接埠。

安裝 containerd

sudo apt-get install -y containerd
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml >/dev/null
containerd --version

# containerd 1.x 的預設配置裡有 SystemdCgroup = false,改成 true
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
# 國內網路:pause 映象改從阿里雲的映象倉庫拉取
sudo sed -i 's#registry.k8s.io/pause#registry.aliyuncs.com/google_containers/pause#' /etc/containerd/config.toml
grep -nE 'SystemdCgroup|pause' /etc/containerd/config.toml

sudo systemctl restart containerd
sudo systemctl enable containerd

grep 應該輸出 SystemdCgroup = true 和改過位址的 pause 映象。如果 containerd --version 顯示 2.x,預設配置裡沒有 SystemdCgroup 這一行,grep 也就找不到它,這時在 [plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runc.options] 這一段下面手動加一行 SystemdCgroup = true。kubeadm 預設讓 kubelet 使用 systemd 管理 cgroup,containerd 必須保持一致,否則控制平面的容器會反覆重啟。

安裝 kubeadm、kubelet、kubectl

Kubernetes 的軟體源按小版本分開,K8S_MINOR 寫到小版本即可:

K8S_MINOR=v1.37
REPO=https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/deb
# 國內網路改用阿里雲映象(路徑裡沒有冒號):
# REPO=https://mirrors.aliyun.com/kubernetes-new/core/stable/${K8S_MINOR}/deb

sudo apt-get install -y apt-transport-https ca-certificates curl gpg
sudo mkdir -p -m 755 /etc/apt/keyrings
curl -fsSL ${REPO}/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] ${REPO}/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
sudo systemctl enable --now kubelet

apt-mark hold 防止日常 apt upgrade 把三個元件意外升級,叢集升級要按 kubeadm 的流程來。裝好後 kubelet 每隔幾秒重啟一次是正常的,它在等 kubeadm init 生成配置。Rocky、AlmaLinux 按官方文件寫 /etc/yum.repos.d/kubernetes.repo,位址把末尾的 deb 換成 rpm,再用 dnf 安裝。

初始化控制平面並安裝網路外掛

在 k8s-cp1 上執行 kubeadm init

# 先單獨拉映象,網路問題在這一步暴露,不會卡在 init 中途
sudo kubeadm config images pull \
  --kubernetes-version $(kubeadm version -o short) \
  --image-repository registry.aliyuncs.com/google_containers

sudo kubeadm init \
  --kubernetes-version $(kubeadm version -o short) \
  --apiserver-advertise-address 10.10.20.11 \
  --control-plane-endpoint k8s-api.example.internal:6443 \
  --pod-network-cidr 172.20.0.0/16 \
  --service-cidr 172.21.0.0/16 \
  --image-repository registry.aliyuncs.com/google_containers

# 讓當前使用者能用 kubectl
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
kubectl get nodes

引數說明:

  • --image-repository 只在拉不到 registry.k8s.io 時才需要;執行 kubeadm config images list --kubernetes-version $(kubeadm version -o short) 可以看到這個版本要用的 pause 標籤,和 containerd 配置裡的不一致時,把配置改成同一個標籤再重啟 containerd。
  • --control-plane-endpoint 寫一個域名而不是 IP。現在它解析到 k8s-cp1,以後擴成 3 台控制平面時改指向負載均衡的虛擬 IP 即可;不加這個引數建好的單控制平面叢集,官方不支援再改造成高可用,只能重建。
  • init 成功後會列印一條 kubeadm join ... 命令,先複製儲存。此時 kubectl get nodes 顯示 NotReady,裝完網路外掛才會變成 Ready。

網路外掛:Calico 還是 Flannel

對比項 Calico Flannel
NetworkPolicy 網路策略 支援 不支援
預設轉發方式 清單預設 IPIP 封裝,可改 VXLAN 或不封裝的 BGP 路由 VXLAN
與機房交換器 BGP 互通 可以,Pod 網段直接宣告給接入交換器 不支援
節點間需放行 BGP 用 TCP 179,IPIP 是 IP 協議號 4,VXLAN 用 UDP 4789 VXLAN 用 UDP 8472
複雜度 元件和概念較多 簡單,幾乎不用配置
適合 生產叢集、多租戶、需要存取控制 測試和小叢集

兩者只裝一個。裝 Calico(3.33 官方測試過 Kubernetes 1.35 到 1.37):

CALICO_VERSION=v3.33.0
curl -LO https://raw.githubusercontent.com/projectcalico/calico/${CALICO_VERSION}/manifests/calico.yaml
# 編輯 calico.yaml:找到 CALICO_IPV4POOL_CIDR,去掉兩行的註釋,改成 init 時的 Pod 網段
kubectl apply -f calico.yaml
kubectl -n kube-system get pods -l k8s-app=calico-node -w

改好後的那兩行是這樣的,縮排要和上下文對齊:

            - name: CALICO_IPV4POOL_CIDR
              value: "172.20.0.0/16"

按 Calico 官方文件,用 kubeadm 且 init 時指定了 --pod-network-cidr 的叢集,Calico 能自動識別 Pod 網段,這一步不改也行;顯式寫上更直觀,但必須在 apply 之前改,清單裡的註釋寫明瞭安裝後再改不會生效。Calico 的映象在 quay.io 上,拉取失敗時先匯入機房的私有映象倉庫,再修改清單裡的 image 欄位。官方對新叢集更推薦用 Tigera Operator 安裝,清單方式步驟少,適合入門和小叢集。

裝 Flannel 則下載 https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml,把 net-conf.json 裡的 "Network": "10.244.0.0/16" 改成 172.20.0.0/16 再 apply,映象在 ghcr.io 上。

加入 worker 節點並驗證

在 k8s-node1、k8s-node2 上執行 init 列印的那條命令:

sudo kubeadm join k8s-api.example.internal:6443 \
  --token abcdef.0123456789abcdef \
  --discovery-token-ca-cert-hash sha256:<init 輸出裡的雜湊值>

token 預設 24 小時過期,過期或忘了儲存,在控制平面上執行 kubeadm token create --print-join-command 重新生成。然後在 k8s-cp1 上驗證:

kubectl get nodes -o wide          # 三個節點都應為 Ready
kubectl get pods -A -o wide        # coredns、calico-node、kube-proxy 都應為 Running
kubectl create deployment web --image=nginx --replicas=2
kubectl expose deployment web --port=80 --type=NodePort
kubectl get pods -l app=web -o wide
kubectl get svc web                # PORT(S) 一欄形如 80:31234/TCP,冒號後是 NodePort
curl http://10.10.20.12:<NodePort>

看三個地方:兩個 web Pod 分佈在不同節點,IP 落在 172.20.0.0/16 裡;Service 的 CLUSTER-IP 落在 172.21.0.0/16 裡;從任意節點 IP 加 NodePort 都能開啟 nginx 頁面。三項都對,說明網段規劃和網路外掛都生效了。nginx 映象來自 Docker Hub,國內拉不下來時換成私有倉庫裡的映象。

只有一台機器、想讓普通 Pod 也排程到控制平面上,執行 kubectl taint nodes k8s-cp1 node-role.kubernetes.io/control-plane:NoSchedule-。

部署失敗時按報錯排查

現象 常見原因 處理
preflight 報 /proc/sys/net/ipv4/ip_forward contents are not set to 1 核心引數沒生效 檢查 /etc/sysctl.d/k8s.conf,執行 sysctl --system
preflight 或 kubelet 日誌提示 swap swap 沒關,或 fstab 沒改、重啟後又開了 swapoff -a 並註釋 fstab 裡的 swap 行
init 停在 waiting for the kubelet to boot up the control plane,最後超時 映象拉不下來;containerd 與 kubelet 的 cgroup 驅動不一致 看 kubelet 日誌和容器狀態,見下方命令
節點一直 NotReady,coredns 一直 Pending 網路外掛沒裝,或外掛的 Pod 起不來 kubectl -n kube-system get pods,對異常 Pod 執行 describe
calico-node 顯示 0/1,日誌提示 BGP 未建立 節點間 TCP 179 或 IPIP 被防火牆攔截;多網路卡伺服器識別錯了網路卡 放行對應流量;kubectl -n kube-system set env daemonset/calico-node IP_AUTODETECTION_METHOD=cidr=10.10.20.0/24
join 報 token 無效 token 過期 重新生成 join 命令
Pod IP 不在規劃的網段裡 CALICO_IPV4POOL_CIDR 或 Flannel 的 Network 與 init 時的 --pod-network-cidr 不一致 Calico 已建立的 IP 池不會自動改變,要新建正確的池再刪除舊池,測試叢集直接重灌更快
sudo journalctl -u kubelet -f
sudo crictl --runtime-endpoint unix:///run/containerd/containerd.sock ps -a
sudo crictl --runtime-endpoint unix:///run/containerd/containerd.sock logs <容器ID>

# 推倒重來:清掉 kubeadm 生成的配置和網路外掛配置
sudo kubeadm reset -f
sudo rm -rf /etc/cni/net.d $HOME/.kube/config

kubeadm reset 不會清理 iptables 規則和 IPVS 表,重灌前按它的提示手動清理,或者直接重啟節點。

從測試叢集到生產還差什麼

照上面的步驟建出來的是單控制平面叢集,跑測試足夠,上生產還要補四件事:

  1. 控制平面高可用:3 台控制平面節點,前面用 keepalived 加 HAProxy 提供一個虛擬 IP,k8s-api.example.internal 解析到這個虛擬 IP;第一台 kubeadm init 時加 --upload-certs,其餘控制平面節點 join 時加 --control-plane 和 init 輸出裡的 --certificate-key。上傳的證書兩小時後自動刪除,過期了用 sudo kubeadm init phase upload-certs --upload-certs 重新生成金鑰。
  2. etcd 定期備份:叢集的全部狀態都在 etcd 裡。主機上裝好 etcdctl 後,用 kubeadm 生成的證書做快照,並把檔案拷到叢集之外:
sudo etcdctl --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  snapshot save /backup/etcd-$(date +%F).db
  1. 證書到期:kubeadm 簽發的元件證書有效期一年,每次 kubeadm upgrade 會自動續期。長期不升級的叢集用 sudo kubeadm certs check-expiration 定期檢查,到期前執行 kubeadm certs renew all 並重啟控制平面元件。
  2. 對外暴露服務:物理機上沒有云廠商的負載均衡,LoadBalancer 型別的 Service 會一直處於 Pending。常見做法是部署 MetalLB,從機房分配的一段位址裡給 Service 分配 IP,L2 模式用 ARP 宣告,BGP 模式與交換器建立鄰居。要注意:交換器連接埠開了 IP 與 MAC 繫結或源位址校驗時,L2 模式下在節點間漂移的虛擬 IP 會被丟棄,要麼把這些位址的繫結一併放開,要麼改用 BGP 模式。

在管理系統裡怎麼落地

k8s 節點最好裝同一個系統版本、同樣的分割槽和核心引數。用 Toplink DCIM 的 IPMI 遠端管理可以讓一批節點統一從 PXE 引導、排進任務佇列批次重灌系統,幾台節點的底子從一開始就一致。網段這件事最怕“只記在部署文件裡”:把 Pod 網段、Service 網段和 MetalLB 用的位址段錄入 IP 位址管理,型別標為內網 IP 或公網 IP 並設為鎖定,它們就不會在開通新伺服器時被自動分配出去。節點接在哪台交換器的哪個連接埠、連接埠在哪個 VLAN,在交換器管理裡能直接看到,排查 MetalLB 位址不通時先從這裡確認節點的接入連接埠。

常見問題

k8s 最少需要幾台伺服器?

一台也能跑:kubeadm init 之後去掉控制平面節點的汙點,普通 Pod 就能排程到這台機器上,適合學習。生產環境的控制平面建議 3 台做高可用,worker 按業務量另算;只有 1 台控制平面時,它一壞叢集就無法管理。

還能用 Docker 作為 k8s 的容器執行時嗎?

Kubernetes 1.24 起移除了內建的 dockershim,不能直接對接 Docker Engine,堅持用 Docker 要額外安裝 cri-dockerd。新叢集直接用 containerd 更簡單;用 Docker 構建的映象遵循 OCI 標準,在 containerd 上照樣能執行。

k8s 叢集可以跨機房部署嗎?

不建議把一個叢集的控制平面拆到多個機房。etcd 對節點間延遲很敏感,跨機房鏈路一抖動就可能選主失敗、API 變慢。多機房更常見的做法是每個機房一套叢集,在上層做流量排程和統一發布。

想看看在你的機房裡怎麼用?

從一次產品示範開始,梳理你的業務鏈路。

查看價格
服務熱線 400-112-2951

讓我們聊聊你的 IDC 業務

掃碼新增企業微信,預約產品示範或申請試用。

Toplink 企業微信二維碼

長按儲存或使用企業微信 / 微信掃描

也可致電
400-112-2951
Telegram
@TopLink88