Kubernetes 核心架構: Cluster、Pod、Deployment 與宣告式管理

DevOps 學習筆記

用 kind 在本機建立 K8s cluster, 從零開始理解 Cluster、Node、Pod、Deployment 的關係, 以及 K8s 宣告式管理的核心思維

K8s 的整體架構

K8s cluster 由兩個角色組成:

  • Control Plane: 大腦, 負責決策 (排程、監控、存狀態)
  • Worker Node: 手腳, 負責真的跑 container

典型的 production cluster:

1
2
3
4
5
6
7
8
Cluster
├── Control Plane Node 1  
├── Control Plane Node 2  ├── usually 3 nodes for HA (high availability)
├── Control Plane Node 3  
├── Worker Node 1  
├── Worker Node 2  ├── scales with workload, could be tens to hundreds
├── Worker Node 3  
└── ...             

Control Plane 開 3 台是因為 etcd (存 cluster 所有狀態的資料庫) 需要奇數台做 consensus — 一台掛了, 剩下兩台還能投票決定 leader, cluster 繼續運作

Control Plane 的元件

元件 角色
API Server 所有操作的入口, kubectl 就是跟它溝通
etcd 分散式 key-value 資料庫, 存整個 cluster 的狀態
Scheduler 決定 Pod 跑在哪個 Node
Controller Manager 跑各種 controller (Deployment、ReplicaSet 等)

Worker Node 的元件

元件 角色
kubelet 每個 Node 上的 agent, 負責確保 Pod 真的在跑
kube-proxy 維護網路規則 (iptables/ipvs), 讓 Service 能路由到 Pod
container runtime 真正跑 container 的東西 (containerd)

用 kind 建立本機 Cluster

kind (Kubernetes IN Docker) 把整個 K8s cluster 跑在一個 Docker container 裡, 用來學習和測試

1
kind create cluster --name devops-lab

這個指令做了什麼:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
kind create cluster
 ├── pull kindest/node Docker image
 ├── start a Docker container (this is your Node)
 ├── run the full K8s stack inside the container:
 │   ├── etcd
 │   ├── API Server
 │   ├── Scheduler
 │   ├── Controller Manager
 │   ├── kubelet
 │   ├── kube-proxy
 │   └── CoreDNS
 └── configure kubectl context

kind 的特殊之處: 一個 Docker container 同時扮演 Control Plane + Worker, 所以 kubectl get nodes 只會看到一個 Node

context 是什麼

kubectl 靠 context 知道要連到哪個 cluster kind create 完成後會自動把 kind-devops-lab 設為 active context

1
2
kubectl config current-context
# kind-devops-lab

如果同時有多個 cluster (例如 kind + AWS EKS), 就需要用 --context 指定

載入 Image 到 kind

kind 裡的 container runtime (containerd) 跟本機的 Docker daemon 是隔離的 本機 docker build 出來的 image, kind 看不到

1
kind load docker-image go-api:0.0.2 --name devops-lab
1
2
3
4
Local Docker daemon                kind Node's containerd
┌──────────────────┐              ┌──────────────────┐
│  go-api:0.0.2    │ ──transfer─→ │  go-api:0.0.2    │
└──────────────────┘              └──────────────────┘

kind load 只是搬 image, 不會啟動任何東西 要到 kubectl apply 的時候, kubelet 才會用這個 image 建 container

可以用 crictl 驗證 image 已經在 Node 裡:

1
docker exec devops-lab-control-plane crictl images | grep go-api

crictl 是跟 containerd 互動的 CLI, 就像 docker 指令是跟 Docker daemon 互動的 CLI

Pod — K8s 的最小運作單位

Pod 是 K8s 在 container 上面加的一層包裝

1
2
Docker world:     Container
K8s world:        Pod → wraps 1 or more Containers

同一個 Pod 裡的 container 共享 network (同一個 IP, 用 localhost 互通) 和 storage 大多數情況就是一個 Pod 一個 container

為什麼不直接管 Container

K8s 管理的最小單位是 Pod, 不是 container:

  • 排程: Scheduler 把整個 Pod 放到某個 Node
  • IP: 分配給 Pod, 同一個 Pod 裡的 container 共用
  • 生死: Pod 死了, 裡面所有 container 一起死

Pod YAML

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
apiVersion: v1
kind: Pod
metadata:
  name: go-api
  labels:
    app: go-api         # label, used by Service to find this Pod
spec:
  containers:
    - name: go-api
      image: go-api:0.0.2
      ports:
        - containerPort: 8080
1
2
kubectl apply -f k8s/pod.yaml
kubectl describe pod go-api

Pod 的誕生過程

kubectl describe pod 的 Events 區塊記錄了完整流程:

1
2
3
4
5
Events:
  Scheduled  → default-scheduler  → Successfully assigned default/go-api to devops-lab-control-plane
  Pulled     → kubelet             → Container image "go-api:0.0.2" already present on machine
  Created    → kubelet             → Container created
  Started    → kubelet             → Container started

對應到元件的協作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
kubectl apply
 
API Server: receives request, stores in etcd
 
Scheduler: decides which Node  devops-lab-control-plane      Scheduled
 
kubelet (on that Node):
 ├── pull image  already present (loaded via kind load)       Pulled
 ├── create container                                          Created
 └── start container                                           Started

Pod 是短命的 (Ephemeral)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
kubectl get pod go-api -o wide
# IP: 10.244.0.5

kubectl delete pod go-api
kubectl get pods
# No resources found — gone forever

kubectl apply -f k8s/pod.yaml
kubectl get pod go-api -o wide
# IP: 10.244.0.6 — IP changed

兩個關鍵觀察:

  • 刪掉 Pod, 沒有任何東西會幫你重建
  • 重新 apply, Pod 拿到不同的 IP

這就是為什麼不會在 production 直接用 bare Pod — 需要 Deployment 來管理

K8s Namespace ≠ Linux Namespace

名字相同但完全不同:

  • Linux Namespace (Phase 1 的內容): kernel 層級的隔離機制 (PID, Network, Mount…)
  • K8s Namespace: 邏輯分群, 像資料夾一樣把 cluster 裡的資源分類
1
2
3
kubectl get namespaces
# default        ← your Pod lives here
# kube-system    ← K8s internal components

Deployment — 管理 Pod 的 Controller

Deployment 解決 bare Pod 的兩個問題: 刪了就沒了, 以及沒辦法做零停機更新

三層架構

1
2
3
4
5
Deployment (you define: I want 3 go-api instances)
 └── ReplicaSet (auto-created, maintains the count)
      ├── Pod 1
      ├── Pod 2
      └── Pod 3

你只管 Deployment, ReplicaSet 和 Pod 都是自動管的

Deployment YAML

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
apiVersion: apps/v1
kind: Deployment
metadata:
  name: go-api
spec:
  replicas: 3                    # always maintain 3 Pods
  selector:
    matchLabels:
      app: go-api                # identifies which Pods belong to this Deployment
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1                # at most 1 extra Pod during update
      maxUnavailable: 0          # no Pod downtime allowed (zero-downtime)
  template:                      # Pod template
    metadata:
      labels:
        app: go-api
    spec:
      containers:
        - name: go-api
          image: go-api:0.0.2
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: 50m           # minimum 0.05 CPU cores
              memory: 32Mi       # minimum 32MB memory
            limits:
              cpu: 100m          # maximum 0.1 CPU cores
              memory: 64Mi       # maximum 64MB memory

template 底下的內容就是之前 pod.yaml 的 spec, 被包在 Deployment 裡面了

命名規律

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
kubectl get deployment
# go-api

kubectl get rs
# go-api-668dcc5dd                   ← Deployment name + hash

kubectl get pods
# go-api-668dcc5dd-72s9z             ← ReplicaSet name + random suffix
# go-api-668dcc5dd-fbz2q
# go-api-668dcc5dd-zbk6l

從名字就能看出 Deployment → ReplicaSet → Pod 的從屬關係

Reconciliation Loop: 自動修復

1
2
3
4
5
kubectl delete pod go-api-668dcc5dd-72s9z
kubectl get pods
# go-api-668dcc5dd-5gcng   ← brand new, AGE is seconds
# go-api-668dcc5dd-fbz2q   ← unchanged
# go-api-668dcc5dd-zbk6l   ← unchanged

跟 bare Pod 完全不同 — 刪一個, 馬上補一個:

1
2
3
4
5
6
7
one Pod deleted → actual count = 2
ReplicaSet detects: desired=3, actual=2, short by 1
auto-creates a new Pod to compensate
actual count back to 3

這個循環持續不斷在跑, 不管 Pod 怎麼死的 — 手動刪、crash、Node 掛掉 — 都會補回來

Rolling Update

更新 image 版本時, Deployment 會同時管兩個 ReplicaSet:

1
2
3
4
5
6
7
Deployment
 ├── ReplicaSet v1 (old version, scaling down)
 │    └── Pod (old)
 └── ReplicaSet v2 (new version, scaling up)
      ├── Pod (new)
      ├── Pod (new)
      └── Pod (new)

maxSurge: 1, maxUnavailable: 0 代表: 先建好新 Pod 確認 Ready, 再殺舊 Pod 達成零停機

正常流程不會 apply bare Pod

實際工作中直接寫 Deployment:

1
kubectl apply -f k8s/deployment.yaml

Deployment 自動建 ReplicaSet 和 Pod 不需要另外寫 pod.yaml

宣告式管理 (Declarative)

K8s 的核心思維 — 你寫 YAML 描述「我要的狀態」, K8s 負責達成:

1
2
3
4
5
6
7
write YAML (desired state)
kubectl apply
K8s continuously ensures actual state = desired state
need changes → edit YAML → apply again

這跟 Terraform、Docker Compose 是同一套思維, 都屬於 Infrastructure as Code (IaC):

工具 管什麼
Terraform 雲端資源 (EC2, RDS, VPC…)
Docker Compose 本機多個 container
K8s YAML cluster 裡的應用部署

YAML 檔進 git, 變更可追蹤, 所有基礎設施都用程式碼定義和管理

kind Cluster 的管理

kind cluster 就是一個 Docker container, 可以直接用 Docker 管理:

1
2
3
4
5
6
7
8
# pause (state preserved, resumes on next start)
docker stop devops-lab-control-plane

# resume
docker start devops-lab-control-plane

# destroy completely (everything gone, needs full rebuild)
kind delete cluster --name devops-lab

kind 不走 Docker Compose, 是 kind 自己用 Docker API 建的 container

References

0%