k8s架構詳解:Master 與 Node 各元件的作用和互動
k8s架構分兩部分:控制平面負責儲存叢集狀態和做決策,工作節點負責把容器真正跑起來。所有元件都只和 kube-apiserver 通訊,由它讀寫 etcd;看懂這條通訊路徑,就能理解建立一個 Pod 時各元件的先後順序,也知道機房防火牆該放行哪些連接埠。
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,經過七步:
- apiserver 受理:kubectl 把清單發給 apiserver 的 6443 連接埠,依次透過認證、鑑權、准入,Deployment 物件寫入 etcd。此時叢集裡還沒有任何 Pod。
- Deployment 控制器:controller-manager 透過 watch 看到新的 Deployment,建立一個 ReplicaSet。
- ReplicaSet 控制器:發現這個 ReplicaSet 期望 2 個 Pod、實際 0 個,建立 2 個 Pod 物件,它們的
nodeName為空,狀態是 Pending。 - scheduler 選節點:看到 2 個未排程的 Pod,過濾、打分後選出節點,向 apiserver 寫入繫結關係。
- kubelet 起容器:目標節點上的 kubelet 看到有 Pod 分給自己,呼叫容器執行時:先建立 pause 沙箱並由 CNI 外掛分配 Pod IP,再拉取映象,最後建立並啟動業務容器。
- 就緒上報:kubelet 執行就緒探針,透過後把 Pod 標記為 Ready 並上報 apiserver。
- 接入流量:如果有 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。