伺服器維運

k8s架構詳解:Master 與 Node 各元件的作用和互動

k8s架構分兩部分:控制平面負責儲存叢集狀態和做決策,工作節點負責把容器真正跑起來。所有元件都只和 kube-apiserver 通訊,由它讀寫 etcd;看懂這條通訊路徑,就能理解建立一個 Pod 時各元件的先後順序,也知道機房防火牆該放行哪些連接埠。

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

k8s架構可以用一句話概括:一組控制平面節點負責記錄狀態和做決策,一組工作節點負責執行。控制平面(早期叫 Master)有四個核心元件:kube-apiserver、etcd、kube-scheduler、kube-controller-manager;工作節點(Node)上有三個:kubelet、kube-proxy 和容器執行時。這些元件彼此不直接對話,全部透過 kube-apiserver 讀寫叢集狀態,再各自監聽(watch)自己關心的變化去動手。

k8s架構圖:控制平面與工作節點

kubectl、CI 系統、監控 ──HTTPS 6443──► kube-apiserver

控制平面(Control Plane,舊稱 Master)
├── kube-apiserver           唯一入口:認證 → 鑑權 → 准入 → 寫入 etcd,並向其他元件推送變化
├── etcd                     分散式鍵值資料庫,儲存全部叢集狀態(2379 客戶端,2380 成員間)
├── kube-scheduler           監聽還沒分配節點的 Pod,為它選一個節點
└── kube-controller-manager  執行幾十個控制器,讓實際狀態不斷向期望狀態靠攏

        ▲ 6443:節點上的元件主動連線 apiserver,監聽分給自己的任務
        │ 10250:apiserver 反向存取 kubelet,用於 logs、exec、port-forward

工作節點(Node)× N
├── kubelet                  節點代理:接收分到本節點的 Pod,呼叫容器執行時建立容器,上報狀態
├── 容器執行時               containerd 或 CRI-O,透過 CRI 介面被 kubelet 呼叫
│   └── CNI 網路外掛         建立 Pod 網路、分配 Pod IP(Calico、Flannel、Cilium 等)
├── kube-proxy               把 Service 翻譯成本機的 iptables、IPVS 或 nftables 規則
└── Pod                      pause 容器持有網路名稱空間,同一 Pod 裡的業務容器共享它

讀這張圖抓住三點:

  • apiserver 是唯一的樞紐。scheduler、controller-manager、kubelet、kube-proxy 都是 apiserver 的客戶端,只有 apiserver 直接讀寫 etcd。任何元件想知道“發生了什麼”,都是向 apiserver 發起 watch。
  • 通訊方向有兩條。節點元件主動連 apiserver 的 6443;apiserver 也會反向連 kubelet 的 10250,kubectl logs、kubectl exec 就走這條路。防火牆只放了 6443 時,Pod 能正常執行,但看日誌和進容器會失敗。
  • 外掛不在核心元件裡,但幾乎必裝。CoreDNS 提供叢集內的服務名解析,網路外掛負責 Pod 之間互通,Ingress 控制器負責對外的 HTTP 入口,metrics-server 提供 kubectl top 和自動擴縮用的指標。

“Master”和“控制平面”指的是同一組元件。新版本里已統一改稱 control-plane,節點角色標籤和汙點都寫作 node-role.kubernetes.io/control-plane,老教程裡的 master 標籤不再使用。

控制平面四個元件分別做什麼

元件 職責 是否儲存狀態 多例項時怎麼工作
kube-apiserver 叢集唯一的 API 入口。每個請求依次經過認證(證書、Token)、鑑權(通常是 RBAC)、准入控制(先改寫、再校驗),透過後寫入 etcd 不儲存,狀態都在 etcd 多個例項同時工作,前面放負載均衡或虛擬 IP
etcd 分散式鍵值資料庫,儲存 Deployment、Pod、Service、Secret 等全部物件 儲存,是叢集裡唯一必須備份的資料 Raft 多數派,寫入要過半數成員確認
kube-scheduler 監聽 spec.nodeName 為空的 Pod,先過濾掉資源不夠、有汙點、不滿足親和性的節點,再給剩下的節點打分,把得分最高的節點寫回 Pod 不儲存 主備模式,靠 Lease 物件選主,同一時刻只有一個在工作
kube-controller-manager 一個程序裡執行多個控制器:Deployment、ReplicaSet、節點生命週期、Job、EndpointSlice、ServiceAccount 等,每個控制器迴圈比較期望狀態與實際狀態,發現差異就透過 apiserver 修正 不儲存 主備模式,同樣靠 Lease 選主

幾個容易誤解的地方:

  • scheduler 只負責“選節點”,不啟動容器;它做的唯一寫操作是把節點名繫結到 Pod 上,真正起容器的是那台節點上的 kubelet。
  • 控制器不直接操作伺服器。ReplicaSet 控制器發現少了一個副本,只是在 apiserver 裡新建一個 Pod 物件,後面的事交給 scheduler 和 kubelet。
  • 控制平面節點也執行 kubelet 和容器執行時。kubeadm 把這四個元件做成靜態 Pod,清單放在 /etc/kubernetes/manifests/,由本機的 kubelet 直接拉起,不經過 scheduler。
  • cloud-controller-manager 只在雲上需要。它負責對接雲廠商的負載均衡、雲盤和節點資訊,自有機房的物理機叢集一般不部署。

檢視這些元件的狀態:

# kubeadm 叢集:控制平面元件的靜態 Pod 清單
ls /etc/kubernetes/manifests/
# etcd.yaml  kube-apiserver.yaml  kube-controller-manager.yaml  kube-scheduler.yaml

kubectl -n kube-system get pods -o wide        # 各元件執行在哪個節點
kubectl get --raw='/readyz?verbose'            # apiserver 逐項自檢,包含 etcd 連線
kubectl -n kube-system get lease kube-scheduler kube-controller-manager   # HOLDER 列是當前的主

kubectl get componentstatuses 從 1.19 起已標記為廢棄,結果不可靠,檢查控制平面用上面的 /readyz 和 Pod 狀態。

工作節點上的三個元件

元件 執行方式 主要工作 排查入口
kubelet systemd 服務,不是 Pod 把本節點註冊到叢集;監聽分配給本節點的 Pod;呼叫容器執行時建立容器、掛載儲存卷;執行存活、就緒、啟動探針;定期上報節點和 Pod 狀態 journalctl -u kubelet -f
kube-proxy DaemonSet,每個節點一個 Pod 監聽 Service 和 EndpointSlice,把“存取某個 ClusterIP”翻譯成本機的轉發規則 kubectl -n kube-system logs ds/kube-proxy
容器執行時 systemd 服務(containerd 或 CRI-O) 接收 kubelet 經 CRI 發來的指令:建立 Pod 沙箱、呼叫 CNI 外掛配置網路、拉映象、啟停容器 crictl pods、crictl ps -a

補充三點:

  • 節點心跳靠 Lease。kubelet 預設每 10 秒更新一次 kube-node-lease 名稱空間裡屬於自己的 Lease 物件,控制器據此判斷節點是否還活著。
  • kube-proxy 不在資料路徑上轉發流量。iptables 模式下它只負責寫規則,包由核心按規則直接轉發,所以 kube-proxy 程序短暫退出時已有的 Service 照樣能存取。
  • 每個 Pod 都有一個 pause 容器。它先建立並持有網路名稱空間,CNI 外掛給它分配 IP,業務容器隨後加入這個名稱空間,所以同一 Pod 裡的容器可以用 localhost 互相存取。

建立一個 Pod 時,各元件按什麼順序工作

以執行 kubectl apply 建立一個 2 副本的 Deployment 為例,從命令發出到流量進入新 Pod,經過七步:

  1. apiserver 受理:kubectl 把清單發給 apiserver 的 6443 連接埠,依次透過認證、鑑權、准入,Deployment 物件寫入 etcd。此時叢集裡還沒有任何 Pod。
  2. Deployment 控制器:controller-manager 透過 watch 看到新的 Deployment,建立一個 ReplicaSet。
  3. ReplicaSet 控制器:發現這個 ReplicaSet 期望 2 個 Pod、實際 0 個,建立 2 個 Pod 物件,它們的 nodeName 為空,狀態是 Pending。
  4. scheduler 選節點:看到 2 個未排程的 Pod,過濾、打分後選出節點,向 apiserver 寫入繫結關係。
  5. kubelet 起容器:目標節點上的 kubelet 看到有 Pod 分給自己,呼叫容器執行時:先建立 pause 沙箱並由 CNI 外掛分配 Pod IP,再拉取映象,最後建立並啟動業務容器。
  6. 就緒上報:kubelet 執行就緒探針,透過後把 Pod 標記為 Ready 並上報 apiserver。
  7. 接入流量:如果有 Service 的選擇器匹配這些 Pod,EndpointSlice 控制器會把就緒 Pod 的 IP 作為可用端點寫進 EndpointSlice,各節點的 kube-proxy 收到變化後更新轉發規則,存取 Service 的請求開始分到新 Pod 上。

每一步都會在叢集裡留下可以觀察的痕跡:

階段 由誰完成 在哪裡看
寫入 Deployment apiserver、etcd kubectl get deploy 出現新物件
建立 ReplicaSet 和 Pod controller-manager Deployment 的事件 ScalingReplicaSet,ReplicaSet 的事件 SuccessfulCreate
選節點 scheduler Pod 事件 Scheduled;資源不夠時是 FailedScheduling
配網路、拉映象、起容器 kubelet、容器執行時、CNI Pod 狀態 ContainerCreating;事件 Pulling、Pulled、Created、Started
就緒並接入流量 kubelet、EndpointSlice 控制器、kube-proxy READY 列變為 1/1;建了 Service 時,kubectl get endpointslices 裡出現 Pod IP
kubectl create deployment web --image=nginx --replicas=2
kubectl get pods -l app=web -o wide -w                 # Pending → ContainerCreating → Running
kubectl get events --sort-by=.metadata.creationTimestamp | tail -n 20
kubectl describe pod -l app=web | sed -n '/Events:/,$p'

這套機制的好處是排障有章可循:Pod 卡在哪個階段,就查負責那個階段的元件。一直 Pending 且沒有 Scheduled 事件,看 scheduler 和節點資源;長時間 ContainerCreating,看映象拉取和網路外掛;Running 但 READY 是 0/1,看就緒探針和應用本身。

元件故障時會出現什麼現象

因為每個元件只對狀態變化做出反應,某個元件停了,流程就停在它負責的那一步。按現象反推故障元件:

故障元件 現象 已在執行的業務
kube-apiserver 全部不可用 kubectl 報連線被拒絕或超時;擴縮容、發版、自愈全部停止 Pod 繼續執行,已有的 Service 規則繼續生效
etcd 失去多數成員 apiserver 無法寫入,讀請求也可能失敗,叢集相當於被凍結 同上
kube-scheduler 新 Pod 一直 Pending,事件裡沒有 Scheduled 不受影響
kube-controller-manager 改副本數不生效,刪掉的 Pod 不會補,節點當機後上面的 Pod 不會被驅逐重建 不受影響
某節點的 kubelet 該節點過一段寬限期後變為 NotReady;預設約 5 分鐘後,由控制器管理的 Pod 在其他節點重建 該節點上的容器還在跑,但不再受管理
kube-proxy 新建或變更的 Service、端點不再同步到該節點的規則裡 已有規則繼續工作
CNI 外掛 新 Pod 卡在 ContainerCreating,事件裡是網路配置相關的報錯 已分配 IP 的 Pod 通常不受影響
CoreDNS 按服務名存取失敗,按 IP 存取正常 依賴域名解析的呼叫失敗

這張表也說明了高可用該保護什麼:apiserver 和 etcd 一停,整個叢集就失去管理能力。kubeadm 的高可用方案有兩種拓撲:堆疊 etcd(etcd 和其他控制平面元件跑在同一批節點上,至少 3 台)和外接 etcd(etcd 單獨部署,至少 3 台 etcd 加 3 台控制平面,共 6 台)。中小規模用堆疊更省機器,代價是一台節點故障同時損失一個 etcd 成員和一個 apiserver 例項。

元件與連接埠對照表:機房防火牆與網路規劃

下表按 Kubernetes 官方文件列出的連接埠整理,並補充了常見網路外掛的連接埠:

連接埠 協議 元件 監聽位置 誰來存取 放行建議
6443 TCP kube-apiserver 控制平面 所有節點、維運人員、負載均衡 只對節點網段和維運網段開放,不對公網開放
2379 TCP etcd 客戶端介面 控制平面或獨立 etcd 節點 kube-apiserver 只在控制平面之間放行
2380 TCP etcd 成員間通訊 同上 其他 etcd 成員 只在 etcd 成員之間放行
10250 TCP kubelet API 所有節點 apiserver、metrics-server 只允許控制平面和叢集內部存取
10259 TCP kube-scheduler 控制平面 本機 kubeadm 預設只監聽 127.0.0.1,無需對外放行
10257 TCP kube-controller-manager 控制平面 本機 同上
10256 TCP kube-proxy 健康檢查 所有節點 外部負載均衡 用外部負載均衡探測節點時放行
30000–32767 TCP、UDP NodePort 型別的 Service 所有節點 客戶端 只開放實際用到的連接埠
179 TCP Calico BGP 所有節點 其他節點或接入交換器 使用 Calico BGP 模式時
IP 協議號 4 IPIP Calico IPIP 封裝 所有節點 其他節點 這是協議號不是連接埠,ACL 要按協議放行
4789 UDP Calico VXLAN 所有節點 其他節點 使用 Calico VXLAN 模式時
8472 UDP Flannel、Cilium 的 VXLAN 所有節點 其他節點 使用對應外掛時
4240 TCP Cilium 健康檢查 所有節點 其他節點 使用 Cilium 時

老教程裡常見的 kubelet 唯讀連接埠 10255,kubeadm 預設已經關閉,不要再開啟。10250 尤其不能暴露到公網:拿到它的存取許可權就能在節點上的容器裡執行命令。

做網路規劃時,除了連接埠還要注意三件事:

  • apiserver 用虛擬 IP 對外。3 台控制平面前面用 keepalived 加 HAProxy 提供一個虛擬 IP,節點和 kubectl 都連這個位址;這個位址要事先在機房的位址台帳裡預留,避免被分配給其他伺服器。
  • etcd 怕延遲和慢盤。etcd 預設心跳間隔 100 毫秒、選舉超時 1000 毫秒,網路抖動或磁碟寫入慢都可能觸發重新選主,表現為 apiserver 間歇性變慢。控制平面放在同一機房的低延遲網路裡,etcd 資料目錄放 SSD。
  • 封裝會吃掉 MTU。VXLAN 封裝多出 50 位元組,物理網路 MTU 1500 時 Pod 網路卡要設為 1450;IPIP 多出 20 位元組,對應 1480。網路外掛和物理網路的 MTU 不匹配時,常見症狀是小請求正常、大檔案或大回應卡住。交換器統一開了 9000 位元組巨幀的,可以相應調大。

節點上開著 firewalld 時,控制平面可以這樣按來源放行(範例中控制平面為 10.10.20.11–13,節點網段 10.10.20.0/24,Pod 網段 172.20.0.0/16):

firewall-cmd --permanent --new-ipset=k8s-cp --type=hash:ip
for ip in 10.10.20.11 10.10.20.12 10.10.20.13; do
  firewall-cmd --permanent --ipset=k8s-cp --add-entry=$ip
done
# etcd 與 kubelet 只允許控制平面存取
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source ipset="k8s-cp" port port="2379-2380" protocol="tcp" accept'
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source ipset="k8s-cp" port port="10250" protocol="tcp" accept'
# apiserver 允許整個節點網段存取
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.10.20.0/24" port port="6443" protocol="tcp" accept'
# Pod 網段放進信任區,否則跨節點的 Pod 流量可能被攔截
firewall-cmd --permanent --zone=trusted --add-source=172.20.0.0/16
firewall-cmd --reload

工作節點按同樣思路放行 10250(來源為控制平面)、所用網路外掛的連接埠和需要對外的 NodePort。

在機房裡怎麼落地

架構圖上的高可用,要落到機櫃和供電上才算數。3 台控制平面節點如果裝在同一個機櫃、接同一路 PDU,一次斷電就會同時帶走 3 個 etcd 成員,多數派設計形同虛設。上架前在 機櫃與資產 的 U 位檢視裡確認 3 台控制平面分屬不同機櫃和供電迴路,擴容時新節點按同樣原則擺放。

節點出現 NotReady、SSH 又連不上時,問題往往出在作業系統層:核心崩潰、根分割槽寫滿或變成唯讀、網路卡驅動異常。這時可以透過 IPMI 遠端管理 開啟節點的 VNC 控制檯看螢幕輸出,需要時遠端重啟或進入救援模式,確認是單節點故障後再決定修復還是把節點移出叢集重灌。

常見問題

控制平面節點上能跑業務 Pod 嗎?

能。kubeadm 預設給控制平面節點加上 node-role.kubernetes.io/control-plane:NoSchedule 汙點,普通 Pod 不會排程上去,去掉汙點即可。小叢集常這樣節省機器,但業務把節點資源吃滿時會拖慢 apiserver 和 etcd,生產環境要給控制平面元件預留資源,或者讓控制平面只做控制平面。

etcd 為什麼要部署奇數個?

etcd 用 Raft 協議,寫入要超過半數成員確認。3 個成員能容忍壞 1 個,4 個成員同樣只能容忍壞 1 個,多出的一個不增加容錯,反而多一份同步開銷,所以一般部署 3 個或 5 個。

kube-proxy 可以不裝嗎?

可以由網路外掛接管。例如 Cilium 能用 eBPF 實現 Service 轉發,開啟替代模式後叢集裡不再需要 kube-proxy;用 kubeadm 部署時,init 時加 --skip-phases=addon/kube-proxy 跳過它。具體步驟以所用外掛的文件為準。

k3s 的架構和標準 k8s 一樣嗎?

元件和職責一樣,打包方式不同:k3s 把 apiserver、scheduler、controller-manager、kubelet 等打進一個二進位制,以 server 和 agent 兩種程序執行;單 server 時預設用 SQLite 存狀態,高可用模式常用內建 etcd,也可以接外部資料庫。apiserver 連接埠同樣是 6443。

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

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

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

讓我們聊聊你的 IDC 業務

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

Toplink 企業微信二維碼

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

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