iT邦幫忙

2026 iThome 鐵人賽

DAY 22
0
Kubernetes

不是背 YAML!30 天從零打造 Kubernetes 微服務:從本機實戰一路到 CKA系列 第 22 篇

Day 22|CNI 到底是什麼?砍掉整個 Cluster,再用 Calico 重建一次

  • 分享至 

  • xImage
  •  

這可能是到目前為止,整個 Kubernetes 系列最重要的一篇之一。

前面我們已經做過很多次:

FastAPI → Redis
FastAPI → PostgreSQL
Pod → Service → Pod

看起來就像 Pod 天生可以互相連線。

但其實 Kubernetes Control Plane 本身並不負責把完整的 Pod Network 實作出來。Kubernetes 定義了「網路應該長什麼樣子」,真正負責讓 Pod 取得 IP、建立網路介面,以及讓不同 Node 上的 Pod 可以互相傳送封包的,通常是額外安裝的 Network Plugin(網路外掛)。Kubernetes 官方也明確說明,一個可正常工作的 Cluster 需要 Network Plugin 來實作 Pod Network。

而 Kubernetes 與這些網路外掛之間使用的一套標準,就是今天的主角:

CNI
Container Network Interface

CNI 到底是什麼?

先不要把 CNI 想成一套軟體。

CNI 是一套介面規範(Interface / Specification)。

你可以把它想成 Kubernetes 和網路系統之間約定好的「插座規格」。

例如 Kubernetes 建立一個新的 Pod 時,需要有人處理:

建立 Pod
↓
建立 Network Namespace
↓
建立網路介面
↓
分配 Pod IP
↓
設定 Route
↓
讓 Pod 可以和其他 Pod 通訊

真正做這些事情的可能是:

Calico
Cilium
Flannel
kindnetd
...

這些才是實際的 Network Plugin。

所以:

CNI = 規格 / Interface
Calico = 實作 CNI 的其中一套網路方案

這個差別一定要先記住。


CRI、CNI、CSI 一次分清楚

https://ithelp.ithome.com.tw/upload/images/20260923/20168537Pkjp98kyQ2.jpg
source: https://www.linkedin.com/posts/kshitijhatwar_kubernetes-devops-platformengineering-share-7459666472722862081-_edy/

Kubernetes 很重要的一個設計理念,就是它不想把所有功能全部寫死在 Kubernetes 裡面,而是透過 Interface 讓其他系統接進來。

因此你很常看到三個縮寫:

CRI
Container Runtime Interface

CRI 解決的是:

kubelet 要怎麼和 Container Runtime 溝通?

例如:

kubelet
↓ CRI
containerd
↓
Container

接著是今天的:

CNI
Container Network Interface

它負責的是:

Container / Pod 的 Network 要怎麼建立?

例如:

Pod
↓
CNI Plugin
↓
取得 IP、建立介面、設定 Route
↓
Pod Network

第三個則是:

CSI
Container Storage Interface

CSI 解決的是:

Kubernetes 要怎麼和不同 Storage System 溝通?

例如 AWS EBS、Ceph、各種企業 Storage,都可以透過 CSI Driver 接進 Kubernetes。

所以最簡單的記法就是:

CRI = Runtime
CNI = Network
CSI = Storage

它們背後其實都是同一個思想:

Kubernetes 定義介面,實際功能可以交給外部元件實作。


那我們之前為什麼不用自己裝 CNI?

因為我們一直使用:

kind

kind 是 Kubernetes IN Docker,也就是把 Kubernetes Node 建立成 Container,方便我們在自己的電腦上建立 Kubernetes Lab。

kind 預設會幫我們安裝一套比較簡單的網路實作:

kindnetd

所以之前建立 Cluster:

kind create cluster

之後,Node 很快就可以進入:

Ready

Pod 也可以正常取得 IP。

不是因為 Kubernetes 不需要 CNI,而是:

kind 已經偷偷幫我們準備好了。

kind 官方也說明,預設會使用 kindnetd,但可以透過 disableDefaultCNI: true 關閉它,再自行安裝 Calico 等其他 CNI。


Calico 是啥?

是一套開源的容器網路介面(CNI, Container Network Interface)與網路安全方案,主要廣泛應用於 Kubernetes(K8s)、虛擬機及裸機環境。

它主要解決兩大核心問題:容器之間如何通訊(Networking),以及誰可以跟誰通訊(Network Security / Policy)。

https://ithelp.ithome.com.tw/upload/images/20260923/20168537AUQqjrs2wD.jpg

1. 核心工作原理與網路模式

傳統的容器網路方案(如早期 Flannel)常使用封裝技術(Overlay Network,如 VXLAN),將 Pod 的流量打包在另一個封包裡,這會產生額外的 CPU 損耗與延遲。Calico 的最大特色在於採用了標準的 三層(Layer 3)路由架構:

  • 純三層路由(Pure L3 Routing):

    Calico 預設將每個 Kubernetes Node 視為一台路由器(Router)。當 Node 上的 Pod 建立時,Calico 會將 Pod 的 IP 作為一筆路由規則直接寫入 Node 的 Linux 核心路由表。

  • BGP 協定(Border Gateway Protocol):

    每個 Node 上運行一個輕量級的 BGP 路由守護程式(基於 BIRD)。各 Node 透過 BGP 互相宣告各自負責的 Pod IP 網段,形成扁平且無封裝的路由網路。流量如同走在實體網路上,吞吐量高、延遲極低。

  • 彈性模式(VXLAN / IP-in-IP):

    若底層雲端環境或實體網路不支援 BGP(例如某些公有雲限制),Calico 也支援 Overlay 模式(VXLAN 或 IP-in-IP),能在限制條件下運作。

  • eBPF 支援:

    除了傳統的 Linux iptables 轉發,Calico 提供基於 eBPF 的資料平面(Data Plane),能繞過 iptables 的效能瓶頸,提供線速(Wire-speed)的轉發效能與原生 DSR(Direct Server Return)。

2. 主要核心功能

  • 細粒度的 NetworkPolicy(網路策略):

    原生 K8s 提供的 NetworkPolicy 規則較基礎,而 Calico 提供更強大的擴充功能:

    • 支援 GlobalNetworkPolicy(叢集層級策略,不限單一 namespace)。
    • 支援 Layer 7 / 應用層規則(如 HTTP 方法、路徑控制)。
    • 支援特定 Port 範圍、ICMP 控制,以及透過 DNS/網域名稱限制對外連線(Egress egress rules)。
  • 極佳的網路效能:

    因採用純 L3 轉發或 eBPF,沒有雙重封裝的 overhead,適合大流量、低延遲的微服務架構。

  • IP 地址管理(IPAM):

    動態分配與回收 Pod IP,支援以節點為單位分配連續網段(CIDR block),避免大規模叢集產生大量細碎路由條目。

  • 跨環境一致性:

    可同時管理混合雲、多叢集、甚至直接納管未跑在容器中的 Linux VM/裸機,使整個環境套用同一套防火牆策略。

3. 架構核心元件

元件名稱 職責說明
Felix 運行在每個 Node 上的 Daemon,負責將安全策略轉換為核心規則(iptables、IP sets 或 eBPF maps)。
BIRD (BGP Client) 負責 Node 之間的 BGP 路由廣播與同步。
confd 監聽資料庫(如 etcd 或 K8s API)的配置變更,並動態更新 BGP 設定檔。
Calico CNI Plugin 負責在 Pod 建立或銷毀時,設定網路命名空間(Network Namespace)與介面。

4. 常見選型比較:Calico vs. Flannel vs. Cilium

  • vs. Flannel:Flannel 設定極為簡單,但功能僅限於基礎的 Overlay 連通,不支援任何 NetworkPolicy。若專案有網路隔離、安全性與防火牆需求,通常會選擇 Calico。
  • vs. Cilium:Cilium 是原生全面基於 eBPF 的後起之秀,在 L7 可觀測性(Hubble)與複雜 Service Mesh 整合非常強勢;而 Calico 發展成熟度高、文檔豐富、在傳統 L3 BGP 架構與各雲平台支援度上依然是業界標準方案之一。

為什麼今天要換成 Calico?

因為下一步我們要開始實作非常重要的:

NetworkPolicy

NetworkPolicy 是 Kubernetes 用來限制 Pod 網路流量的 Resource。

例如我們可以定義:

Redis
只接受
FastAPI Pod
連線

其他 Pod 即使知道 Redis 的 Service 名稱,也不能直接連進去。

問題是 NetworkPolicy 本身只是:

「我希望網路規則長這樣」

它是一份宣告。

真正負責把規則變成實際封包過濾行為的,是 Network Plugin。

所以:

NetworkPolicy YAML
        ↓
描述允許哪些流量
        ↓
Calico
        ↓
真的執行網路限制

如果你的 Network Plugin 根本不支援 NetworkPolicy,就算:

kubectl apply -f network-policy.yaml

API Server 一樣可以接受這份 Resource,但網路流量可能完全不受影響。Kubernetes 官方文件也特別強調,NetworkPolicy 必須搭配支援 Policy Enforcement 的 Networking Solution 才會生效。

因此今天我們要把 kind 原本的簡單網路換成:

Calico

Calico 同時提供 Kubernetes Networking 與 NetworkPolicy 能力。


第一步:先把現在的環境保存好

今天我們甚至會直接把整個 Kubernetes Cluster 刪掉。

先確認,我們真正重要的東西應該都已經存在專案裡,例如:

app/
Dockerfile
k8s/
kind-config.yaml

接著就開始把 Cluster 刪掉:

kind delete cluster \
  --name cka-lab

確認:

kubectl get nodes

https://ithelp.ithome.com.tw/upload/images/20260923/20168537CoPjp88TiS.png

這時可能會因為原本的 Cluster 已經不存在而出現連線錯誤。

這是正常的。

但注意:

Cluster 消失了

FastAPI Code 還在
Dockerfile 還在
Kubernetes YAML 還在
kind Config 還在

所以我們隨時可以重建。

這正是 Infrastructure as Code 很重要的觀念:

Infrastructure is disposable (一次性的)
Configuration is reproducible

基礎設施可以被刪除,但建立它的方法必須能被保存並重複執行。


第二步:讓 kind 不要幫我們安裝 CNI

修改:

kind-config.yaml

內容:

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4

nodes:
  - role: control-plane
  - role: worker
  - role: worker

networking:
  disableDefaultCNI: true
  podSubnet: 192.168.0.0/16

這裡最重要的是:

disableDefaultCNI: true

意思就是:

kind,不要安裝你的預設 kindnetd,我要自己處理 CNI。

另外:

podSubnet: 192.168.0.0/16

Pod Subnet 是我們預留給 Pod 使用的一段 IP 範圍。

也就是未來可能看到:

192.168.10.5
192.168.20.8
192.168.30.12

這些 IP 都可以從這個範圍分配出去。


第三步:建立一個「沒有 CNI」的 Kubernetes

執行:

kind create cluster \
  --name cka-lab \
  --config kind-config.yaml

https://ithelp.ithome.com.tw/upload/images/20260923/20168537KqTFroru0k.png

完成之後馬上看:

kubectl get nodes

https://ithelp.ithome.com.tw/upload/images/20260923/20168537DKI1ZuwslQ.png

你很可能會看到:

NAME                    STATUS
cka-lab-control-plane   NotReady
cka-lab-worker          NotReady
cka-lab-worker2         NotReady

以前看到 NotReady 可能會覺得出錯了,但這次:

這正是我們故意製造出來的狀態。


為什麼 Node 是 NotReady?

因為目前:

API Server
Scheduler
Controller Manager
kubelet
containerd

這些 Kubernetes 核心元件已經存在。

但是:

CNI 尚未安裝

也就是:

Control Plane:有
Container Runtime:有
Pod Network:沒有

kubelet 沒辦法準備完整的 Pod Networking,因此 Node 還不能正常承接一般 Workload。

如果想看得更清楚,可以執行:

kubectl describe node cka-lab-worker

在 Conditions 或 Events 附近,通常可以看到和:

container runtime network not ready
NetworkPlugin
CNI

https://ithelp.ithome.com.tw/upload/images/20260923/20168537AIOGmxz5Rb.png
相關的訊息。

這是第一次真正看到:

CNI 並不是 Kubernetes 可有可無的裝飾品,而是 Pod Network 能不能正常運作的重要基礎。


第四步:安裝 Calico

現在我們已經故意建立了一個「有 Kubernetes、但沒有 CNI」的 Cluster,所以 Node 會維持在 NotReady。接下來就是要把 Calico 裝進來,讓 Pod Network 真正建立起來。

Calico 官方針對 kind 提供兩種安裝方式:Operator 與 Manifest。這篇我們採用 Operator,因為它比較符合 Kubernetes 的管理方式,也能順便理解 CRD 與 Operator 這兩個重要概念。

先講 CRD(Custom Resource Definition)。Kubernetes 原本只認識像 Pod、Deployment、Service 這些內建 Resource,但 Calico 有自己的 Installation、IPPool、BGPConfiguration 等 Resource,因此必須先透過 CRD 告訴 Kubernetes:「這些也是合法的 Resource 類型」。所以 CRD 可以理解成是在擴充 Kubernetes API,但它本身還不是 Calico 真正執行網路功能的程式。

另一個名詞是 Operator。Operator 可以先理解成「跑在 Kubernetes 裡面的自動管理程式」。我們不需要自己建立所有 Calico 元件,而是先建立 Tigera Operator,再建立一個 Installation Resource 告訴它:「我要一套 Calico」。Operator 看到之後,就會自動建立並維護 calico-node、calico-kube-controllers、Typha 等相關元件。

整體關係可以記成:

CRD
↓
讓 Kubernetes 認識 Calico Resource

Tigera Operator
↓
負責讀取這些 Resource

Installation Resource
↓
告訴 Operator「請幫我建立 Calico」

Operator
↓
自動建立 Calico Components

目前官方 Operator 安裝流程分成三步。

第一步先安裝 Calico CRDs:

kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.32.2/manifests/v1_crd_projectcalico_org.yaml

執行後會看到很多:

customresourcedefinition.apiextensions.k8s.io/... created

這是正常的,代表 Kubernetes 正在加入 Calico 所需要的 Custom Resource 定義。

接著安裝 Tigera Operator:

kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.32.2/manifests/tigera-operator.yaml

這一步會建立 tigera-operator Namespace、ServiceAccount、RBAC 與 Operator Deployment。剛建立時如果看到:

0/1 ContainerCreating

這要等一段時間,才變成:

1/1 Running

Operator Running 只代表「負責安裝 Calico 的管理程式已經啟動」,並不代表 Calico 已經全部 Ready。

第三步才是真正建立 Calico Installation:

kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.32.2/manifests/custom-resources.yaml

這個檔案會建立 Installation/default 等 Custom Resources。可以把它理解成我們正式向 Operator 下達需求:

我要一套 Calico,請按照這份設定建立。

這裡要特別注意 Pod CIDR。我們前面的 kind 設定是:

networking:
  disableDefaultCNI: true
  podSubnet: 192.168.0.0/16

而官方的 custom-resources.yaml 目前也是使用:

192.168.0.0/16

所以兩邊剛好一致。如果未來你把 kind 的 podSubnet 改成其他範圍,就要同步修改 Calico 的 IP Pool,不然兩邊的 Pod Network 設定會對不起來。


第五步:等待 Calico 把 Pod Network 建起來

第三個指令執行完之後,不代表 Calico 會瞬間完成。這點很重要,因為第一次安裝通常需要下載不少 Image,例如 calico/node、calico/cni、calico-kube-controllers、Typha 等,所以可能需要等幾分鐘。

可以先執行:

kubectl get pods -A -w

-w 就是持續監看狀態。macOS 預設通常沒有 Linux 常見的 watch 指令,所以不需要另外安裝,直接使用 kubectl 自己的 -w 就可以。

這時很可能看到:

Pending
ContainerCreating
Init:0/3
Init:1/3

這些狀態不一定代表錯誤。

尤其 calico-node 很常出現:

Init:1/3

這表示這個 Pod 有三個 Init Container。Init Container 是正式程式啟動以前,先執行初始化工作的 Container。Calico 目前可能會看到像:

flexvol-driver
ebpf-bootstrap
install-cni

所以:

Init:1/3

意思是第一個初始化工作已經完成,後面兩個還在執行或等待,不是「Calico 壞掉三分之一」。

實際流程大概是:

calico-node Pod 建立
↓
flexvol-driver
↓
ebpf-bootstrap
↓
install-cni
↓
calico-node 正式啟動
↓
Running

而且其中某一步可能只是正在下載 Image。像我們實際看到 flexvol-driver 已經成功完成,但 ebpf-bootstrap 還在 Pull:

quay.io/calico/node:v3.32.2

這種情況就只是還在下載,不需要馬上刪 Cluster 重來。

如果想確認到底卡在哪裡,可以先找 calico-node:

kubectl get pods \
  -n calico-system \
  -l k8s-app=calico-node

再執行:

kubectl describe pod \
  -n calico-system \
  calico-node-你的Pod名稱

重點看最下面的:

Events:

如果看到:

Pulling image ...

通常只是還在下載。

如果後來看到:

Successfully pulled image ...

代表下載成功,流程會繼續。

真正比較需要處理的是:

ErrImagePull
ImagePullBackOff
CrashLoopBackOff
Failed

這些才比較像真正的錯誤。


因為我們是使用 Operator 安裝,也可以用:

kubectl get tigerastatus

來看 Calico 整體狀態。如果看到:

AVAILABLE     False
PROGRESSING   True

通常表示還在安裝中。

最後理想狀態會變成:

AVAILABLE     True
PROGRESSING   False
DEGRADED      False

這時你可能也會看到 CoreDNS 或其他 Pod 一直停在:

Pending

先不用急著一個一個除錯,因為現在真正的關鍵是 calico-node。

它們之間的關係其實是:

calico-node 初始化完成
↓
CNI 安裝完成
↓
Pod Network 可以運作
↓
Node NotReady → Ready
↓
CoreDNS 等其他 Pod 才開始正常啟動

所以目前只要先確保每一台 Node 上的 calico-node 都成功即可。

等一段時間後再執行:

kubectl get nodes

應該會慢慢看到:
https://ithelp.ithome.com.tw/upload/images/20260924/20168537PGnIrXfUV2.png

這一刻就是今天最重要的實驗結果。

原本:

Kubernetes
↓
沒有 CNI
↓
Node NotReady

現在變成:

Kubernetes
↓
Calico
↓
Pod Network 建立
↓
Node Ready

Kubernetes 為什麼可以直接換掉 Network 實作?

原本 kind 使用的是自己的預設 Network Plugin:

kind
↓
kindnetd

今天則變成:

kind
↓
Calico

但我們原本學過的 Pod、Deployment、Service、ConfigMap 完全不需要因此重新學一套。

原因就在於 Kubernetes 中間定義了 CNI 這層 Interface。

Kubernetes 負責規定:

Pod Networking 應該提供什麼能力。

Calico、Cilium、Flannel 等 Network Plugin 則負責:

具體怎麼把這些能力實作出來。

所以:

Kubernetes
↓
CNI Interface
↓
Calico / Cilium / Flannel ...

這就是 Interface 最大的價值:上層不需要知道底層到底怎麼完成工作。


第六步:把 Application 建回來

因為前面整個 kind Cluster 已經刪掉,所以原本存在 Cluster 裡的:

Namespace
Deployment
Service
PVC
Secret
ConfigMap

當然全部都不存在了。

但我們的:

app/
Dockerfile
k8s/
kind-config.yaml

仍然保留,因此可以重新建立。

Cluster 可以消失,但只要設定與程式碼還在,就可以重新把環境建回來。

不過這裡有一個很容易忽略的地方:

Cluster 重建之後,原本手動設定在 Node 上的 Label、Taint 等設定,也會跟著消失。

因為我們不是把原本的 Node 重新啟動,而是:

舊 kind Cluster
↓
刪除舊 Node

重新建立 kind Cluster
↓
產生全新的 Node

例如前面的 FastAPI Deployment 有設定:

nodeSelector:
  disktype: ssd

https://ithelp.ithome.com.tw/upload/images/20260924/201685377pESvn2vHn.png

這代表 FastAPI Pod 並不是隨便一台 Node 都可以執行,而是要求:

Node 必須具有:

disktype=ssd

如果以前曾經執行:

kubectl label node \
  cka-lab-worker \
  disktype=ssd

這個 Label 是直接設定在舊 Node 上面的。

當整個 kind Cluster 被刪除之後,這個 Label 也會一起消失。

因此重新建立 Cluster 之後,可以先確認:

kubectl get nodes --show-labels

或者只看 disktype:

kubectl get nodes -L disktype

如果新的 Worker Node 沒有:

disktype=ssd

就重新加回去:

kubectl label node \
  cka-lab-worker \
  disktype=ssd

如果希望兩台 Worker 都可以承接 FastAPI Pod,也可以兩台都加:

kubectl label node \
  cka-lab-worker \
  disktype=ssd

kubectl label node \
  cka-lab-worker2 \
  disktype=ssd

再確認一次:

kubectl get nodes -L disktype

就會看到:

https://ithelp.ithome.com.tw/upload/images/20260924/20168537tvDAViBgyO.png
這樣 FastAPI Deployment 裡的:

nodeSelector:
  disktype: ssd

才真的有符合條件的 Node 可以選擇。

這也提醒我們:

kubectl apply -f k8s/

只能重新建立 YAML 裡描述的 Kubernetes Resource。

像這種以前透過:

kubectl label node ...

手動設定在 Node 上的狀態,Cluster 重建之後就必須另外恢復。


接著還要處理 Application Image。

我們自己的:

cka-api:v4

Image 可能只存在 Mac 的 Docker,因此新的 kind Cluster 也要重新載入。

不過這裡有一個很重要的地方:

kind load docker-image 載入的 Image 版本,必須和 Deployment YAML 裡設定的 image: 完全一致。

我們的 API Deployment 定義在:

k8s/01-api.yaml

因此可以先確認目前使用的是哪個 Image:

grep -n "image:" k8s/01-api.yaml

這篇目前統一使用:

image: cka-api:v4

所以後面載入 kind Cluster 的也必須是:

cka-api:v4

可以先確認 Mac Docker 裡真的有這個 Image:

docker images | grep cka-api

確認存在之後,再執行:

kind load docker-image \
  cka-api:v4 \
  --name cka-lab

https://ithelp.ithome.com.tw/upload/images/20260924/201685379d1lqrAyxH.png

這個指令就是把 Mac Docker 裡的 Image 放進 kind Node 使用的 containerd。

因為 kind 的每個 Node 本身其實都是 Container,而 Kubernetes 在這些 Node 裡使用 containerd 作為 Container Runtime。

所以即使:

docker images

在 Mac 上看得到:

cka-api:v4

也不代表新的 kind Node 裡面已經有這個 Image。

可以把整個關係理解成:

Mac Docker
↓
kind load docker-image
↓
kind Node 裡的 containerd
↓
Kubernetes Pod 才能使用

而且不只是 Image 名稱要一樣,Tag 也必須一致。

例如:

Deployment 要:

cka-api:v3

但是 kind load 的是:

cka-api:v4

對 Kubernetes 來說,這就是兩個不同的 Image。

即使 Mac Docker 裡:

cka-api:v3
cka-api:v4

兩個都存在,只要新的 kind Cluster 裡沒有 Deployment 指定的那個版本,Pod 一樣無法正常啟動。

例如 Node 本地找不到:

cka-api:v3

kubelet 就可能嘗試往外部 Registry 找:

docker.io/library/cka-api:v3

但我們的 cka-api 是自己在本機 Build 的 Image,Docker Hub 上並沒有這個 Repository,因此就會出現:

ErrImagePull
↓
ImagePullBackOff

所以這裡一定要確認:

k8s/01-api.yaml

image: cka-api:v4

        ↓ 完全一致

kind load docker-image

cka-api:v4

確認 Image 版本一致之後,再重新部署:

kubectl apply -f k8s/

https://ithelp.ithome.com.tw/upload/images/20260924/20168537V4odjGnNPY.png

如果想更容易理解,可以按照依賴關係看:

Namespace
↓
ConfigMap / Secret
↓
PVC
↓
Redis / PostgreSQL
↓
FastAPI
↓
Service

不代表 Kubernetes 永遠都一定要嚴格照這個順序建立。

例如我們直接:

kubectl apply -f k8s/

Kubernetes 本身也會處理很多 Resource 之間的建立與等待。

只是在學習階段按照依賴關係理解,之後遇到問題時會比較容易知道是哪一層出錯。

另外,現在還要多記兩層:

Application YAML
↓
可能有 Node Scheduling 條件
↓
Node Label / Taint / Affinity

以及:

Deployment YAML
↓
指定 Image
↓
kind Node 必須真的擁有對應版本

所以 Cluster 重建之後,不只是 Application Resource 要恢復。

原本依賴的:

Node Label
Node Taint
Application Image

也都要重新確認。


第七步:確認 Application 真的回來

先確認 Pod:

kubectl get pods \
  -n cka-lab

FastAPI、Redis、PostgreSQL 正常情況下都應該逐漸變成:

Running

https://ithelp.ithome.com.tw/upload/images/20260924/20168537WIL4RFtyyJ.png

不過這裡不要只記得:

等到 Pod 變成 Running。

更重要的是要學會看:

Pod 到底卡在哪一個階段。

如果某個 Pod 長時間沒有變成 Running,可以執行:

kubectl describe pod \
  <POD_NAME> \
  -n cka-lab

然後看最下面的:

Events:

因為不同狀態通常代表完全不同的問題。


例如這次我們一開始遇到:

Pending

查看 Events 後看到:

0/3 nodes are available:
1 node(s) had untolerated taint(s),
2 node(s) didn't match Pod's node affinity/selector.

這代表 Pod 還卡在:

Scheduling

階段。

原因是 FastAPI Deployment 有:

nodeSelector:
  disktype: ssd

但是 Cluster 重建之後,新的 Worker Node 已經沒有原本的:

disktype=ssd

Label。

所以:

FastAPI Pod
↓
要求 disktype=ssd
↓
Scheduler 檢查所有 Node
↓
沒有符合條件的 Worker
↓
Pending

重新補上:

kubectl label node \
  cka-lab-worker \
  disktype=ssd

之後 Events 就會出現:

Successfully assigned ...

這代表:

Scheduling ✅

已經成功。


但是修好 Scheduling 之後,不代表後面一定全部正常。

這次 Pod 成功被排到 Worker 之後,又出現:

ErrImagePull
ImagePullBackOff

Events 顯示:

Failed to pull image "cka-api:v3"

這時問題已經不是 NodeSelector 了。

因為:

Successfully assigned

已經證明 Scheduler 成功找到 Node。

現在是下一個階段:

Pod 已經排到 Node
↓
kubelet 準備建立 Container
↓
尋找 Deployment 指定的 Image
↓
找不到
↓
ErrImagePull
↓
ImagePullBackOff

這時就應該確認:

grep -n "image:" k8s/01-api.yaml

以及:

docker images | grep cka-api

再確認我們載入 kind 的版本:

kind load docker-image \
  cka-api:v4 \
  --name cka-lab

三邊必須一致:

k8s/01-api.yaml
↓
image: cka-api:v4

Mac Docker
↓
cka-api:v4

kind Node
↓
cka-api:v4

如果其中一個還是:

cka-api:v3

就有可能再次出現:

ImagePullBackOff

因此這次實際上遇到了兩個連續、但完全不同的問題:

第一關:Scheduling

Node 沒有 disktype=ssd
↓
Pending
↓
補上 Node Label
↓
Successfully assigned ✅

接著:

第二關:Container Image

Deployment 指定的 Image
和 kind 裡實際存在的 Image 不一致
↓
ErrImagePull
↓
ImagePullBackOff
↓
統一 Image 版本
↓
Container 正常建立 ✅

所以 Kubernetes 除錯不能只看到第一個錯誤修好,就認為所有事情結束了。

Pod 每往下一個階段前進,都可能再暴露出下一個問題。

可以簡單記成:

Pending
↓
先檢查 Scheduling

Successfully assigned
↓
代表 Scheduler 已成功

ErrImagePull / ImagePullBackOff
↓
檢查 Image

CrashLoopBackOff
↓
Container 有啟動
但 Application 一直 Crash

Running
↓
Pod 正常執行

而最常用來判斷真正原因的就是:

kubectl describe pod \
  <POD_NAME> \
  -n cka-lab

搭配最下面的:

Events:

確認 FastAPI、Redis、PostgreSQL 都正常 Running 之後,再確認 Service:

kubectl get svc \
  -n cka-lab

接著建立 Port Forward:

kubectl port-forward \
  service/api \
  8080:80 \
  -n cka-lab

另外開一個 Terminal:

curl localhost:8080

https://ithelp.ithome.com.tw/upload/images/20260924/20168537jtYqVakDxN.png

如果 API 可以正常回應,就代表整個環境已經成功恢復。

這裡也要注意:

如果 FastAPI Pod 還是:

Pending

或者:

ImagePullBackOff

這時候直接執行:

kubectl port-forward \
  service/api \
  8080:80 \
  -n cka-lab

可能會看到:

error: unable to forward port because pod is not running.
Current status=Pending

原因不是 Service 壞掉。

而是:

Port Forward
↓
找到 Service
↓
Service 要轉送到 FastAPI Pod
↓
FastAPI Pod 還沒有 Running
↓
無法 Forward

所以正確順序應該是:

Pod Running
↓
Service 有可用 Backend
↓
Port Forward
↓
curl 測試

今天完整走過的流程其實是:

Delete Cluster
↓
Recreate Cluster
↓
No CNI
↓
Node NotReady
↓
Install Calico
↓
Calico Images / Init Containers
↓
Pod Network Ready
↓
Node Ready
↓
Restore Node Labels
↓
Verify Deployment Image Version
↓
Reload Matching Application Image
↓
Redeploy Application
↓
Check Pod Scheduling
↓
Check Container Image
↓
Pods Running
↓
Service Restored

這次也讓我們實際看到:

Cluster 重建

並不只是重新執行:

kubectl apply -f k8s/

就一定全部恢復。

因為 Application 有時候還會依賴:

Node Label
Node Taint
Image
CNI
Storage

這些 Cluster 層級或 Node 層級的環境設定。

例如:

kubectl apply -f k8s/

可以重新建立:

Deployment
Service
ConfigMap
Secret
PVC

但它不會自動幫我們恢復以前手動執行的:

kubectl label node ...

也不會自動把 Mac Docker 裡的本地 Image 搬進新的 kind Node。

因此真正可重建的環境應該考慮:

Kubernetes YAML
+
Node 設定
+
Container Image
+
Network
+
Storage

而不是只有 Application YAML。


Day 22 小結

今天真正要記住的並不是 Calico 的三個安裝指令,而是 Kubernetes 很核心的一個架構思想:

Kubernetes
≠
所有功能全部內建

它大量透過標準 Interface 和外部元件合作:

CRI → Runtime
CNI → Network
CSI → Storage

今天我們真正操作的是:

Kubernetes
↓
CNI Interface
↓
Calico
↓
Pod Network

也因此可以理解,為什麼我們可以把:

kindnetd

換成:

Calico

但原本的:

Pod
Deployment
Service
ConfigMap

完全不需要重新學一套。

因為 Kubernetes 上層只需要知道:

CNI 應該提供什麼 Networking 能力

至於底層到底由:

Calico
Cilium
Flannel

哪一套實作,是 Network Plugin 自己負責。


更重要的是,我們真的親眼看到:

沒有 CNI
↓
Node NotReady

安裝 Calico
↓
calico-node 初始化
↓
Pod Network Ready
↓
Node Ready

但:

Node Ready

只代表 Node 本身已經具備正常工作的基本條件。

它不代表:

所有 Pod 一定可以 Running

因為 Pod 後面還要經過:

Scheduler
↓
選擇 Node

kubelet
↓
準備 Image

Container Runtime
↓
建立 Container

Application
↓
真正啟動

其中 Scheduler 還會判斷:

NodeSelector
NodeAffinity
Taint / Toleration
CPU / Memory
PVC

例如這次:

FastAPI
↓
nodeSelector: disktype=ssd
↓
Cluster 重建後 Label 消失
↓
找不到符合條件的 Worker
↓
Pending

最後重新補上:

kubectl label node \
  cka-lab-worker \
  disktype=ssd

Scheduler 才重新找到符合條件的 Node。

但是 Scheduler 成功之後,我們又遇到:

ImagePullBackOff

原因是:

Deployment Image
↓
cka-api:v3

實際重新載入 kind 的 Image
↓
cka-api:v4

版本不一致。

修正成:

Deployment
↓
cka-api:v4

kind load
↓
cka-api:v4

之後 Container 才能正常建立。

所以今天其實學到了兩個非常重要的除錯觀念。

第一個:

Pod Pending
≠
再等一下就好

而是:

Pod Pending
↓
kubectl describe pod
↓
Events
↓
找到 Scheduling 真正原因

第二個:

修好 Pending
≠
所有問題都結束

因為 Pod 往下一個階段前進之後,還可能遇到:

ErrImagePull
ImagePullBackOff
CrashLoopBackOff

所以更完整的 Kubernetes 除錯思路應該是:

看 Pod Status
↓
kubectl describe pod
↓
看 Events
↓
判斷現在卡在哪一層
↓
只處理那一層的問題
↓
重新觀察下一個狀態

這會是之後 Kubernetes 除錯最常使用的思考方式之一。


接下來有了支援 NetworkPolicy 的 Calico,就可以真正開始限制 Pod 之間的流量,例如:

FastAPI → Redis    ✅

其他 Pod → Redis  ❌

這時 NetworkPolicy 才會從一份 YAML,真正變成 Kubernetes 裡可以實際驗證的網路存取規則。


上一篇
Day 21|StatefulSet、StorageClass 與 PV:為什麼 Database 不只是另一個 Deployment?
下一篇
Day 23|NetworkPolicy 實戰:Pod 能互連,不代表所有 Pod 都應該互連
系列文
不是背 YAML!30 天從零打造 Kubernetes 微服務:從本機實戰一路到 CKA 共 29 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言