
DevOps 學習筆記

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

## K8s 的整體架構

K8s cluster 由兩個角色組成:

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

典型的 production cluster:

```
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 裡, 用來學習和測試

```bash
kind create cluster --name devops-lab
```

這個指令做了什麼:

```
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

```bash
kubectl config current-context
# kind-devops-lab
```

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

### 載入 Image 到 kind

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

```bash
kind load docker-image go-api:0.0.2 --name devops-lab
```

```
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 裡:

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

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

## Pod — K8s 的最小運作單位

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

```
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

```yaml
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
```

```bash
kubectl apply -f k8s/pod.yaml
kubectl describe pod go-api
```

### Pod 的誕生過程

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

```
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
```

對應到元件的協作:

```
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)

```bash
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 裡的資源分類

```bash
kubectl get namespaces
# default        ← your Pod lives here
# kube-system    ← K8s internal components
```

## Deployment — 管理 Pod 的 Controller

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

### 三層架構

```
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

```yaml
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 裡面了

### 命名規律

```bash
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: 自動修復

```bash
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 完全不同 — 刪一個, 馬上補一個:

```
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:

```
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:

```bash
kubectl apply -f k8s/deployment.yaml
```

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

## 宣告式管理 (Declarative)

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

```
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 管理:

```bash
# 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

- [Kubernetes Documentation — Overview](https://kubernetes.io/docs/concepts/overview/) — K8s 官方概念總覽
- [Kubernetes Documentation — Pods](https://kubernetes.io/docs/concepts/workloads/pods/) — Pod 的設計理念與生命週期
- [Kubernetes Documentation — Deployments](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) — Deployment、ReplicaSet、rolling update 的完整說明
- [kind — Quick Start](https://kind.sigs.k8s.io/docs/user/quick-start/) — kind 的安裝與使用說明
