一、前言 #
嗨,我是拍拍君 🐳
如果你已經學會了 Docker,知道怎麼把應用程式打包成 container,那恭喜你——你已經準備好踏入下一個大坑了。
那個坑叫 Kubernetes(簡稱 K8s,因為 k 和 s 之間有 8 個字母,工程師就是這麼懶)。
想像一下:你有一個 web app 跑在 Docker container 裡,一切運作良好。但如果這個 container 掛了怎麼辦?如果流量暴增需要 10 個 container 怎麼辦?如果你要做零停機更新怎麼辦?
手動管理?那你大概會先崩潰。
Kubernetes 就是幫你自動化管理大量 container 的工具。 它負責:
- 自動部署你的 container
- 監控它們的健康狀態
- 掛了自動重啟
- 流量太大自動擴展
- 滾動更新不斷線
今天我們就來搞懂 K8s 最核心的三個概念:Pod、Service、Deployment。學會這三個,你就掌握了 K8s 80% 的日常操作。
二、Kubernetes 全局觀 #
在深入之前,先看看 K8s 的架構全貌:
┌──────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Node 1 │ │ Node 2 │ │ Node 3 │ │
│ │ ┌──────┐ │ │ ┌──────┐ │ │ ┌──────┐ │ │
│ │ │ Pod │ │ │ │ Pod │ │ │ │ Pod │ │ │
│ │ │ Pod │ │ │ │ Pod │ │ │ │ Pod │ │ │
│ │ └──────┘ │ │ └──────┘ │ │ └──────┘ │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ Control Plane (Master) │ │
│ │ API Server | Scheduler | etcd | ... │ │
│ └──────────────────────────────────────┘ │
└──────────────────────────────────────────────┘
幾個關鍵概念:
- Cluster(叢集):整個 K8s 環境,包含 control plane 和多個 node
- Node(節點):一台實體或虛擬機器,上面跑著你的 container
- Control Plane:大腦,負責排程、監控、API 管理
- kubectl:你跟 K8s 溝通的 CLI 工具
K8s 的核心哲學是 宣告式管理(declarative management):你不是告訴它「啟動 3 個 container」,而是告訴它「我要 3 個 container 在運行中」,K8s 會自己想辦法達成這個狀態。
三、Pod:K8s 的最小部署單位 #
什麼是 Pod? #
Pod 是 K8s 裡最小的可部署單位。一個 Pod 裡面可以有一個或多個 container,但大多數情況下一個 Pod 就放一個 container。
你可以把 Pod 想成 container 的「包裝紙」——K8s 不直接管 container,而是管 Pod。
Pod
├── Container (你的 app)
├── 共享的網路空間 (同一個 IP)
└── 共享的儲存卷 (Volume)
為什麼不直接管 container? #
因為有時候你的應用需要多個 container 緊密合作。例如:
- 主要 container 跑你的 web app
- 一個 sidecar container 負責收集 log
- 它們共用同一個 IP 和 volume
Pod 讓這些 container 能方便地共享資源。
最簡單的 Pod YAML #
# my-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
用 kubectl 來操作:
# 建立 Pod
kubectl apply -f my-pod.yaml
# 查看 Pod 狀態
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# my-nginx 1/1 Running 0 10s
# 查看 Pod 詳細資訊
kubectl describe pod my-nginx
# 查看 Pod 的 log
kubectl logs my-nginx
# 進入 Pod 內部(像 docker exec)
kubectl exec -it my-nginx -- /bin/bash
# 刪除 Pod
kubectl delete pod my-nginx
Pod 的生命週期 #
Pod 有幾個重要的狀態:
| 狀態 | 說明 |
|---|---|
| Pending | 等待排程到某個 Node |
| Running | 至少一個 container 在運行 |
| Succeeded | 所有 container 正常結束 |
| Failed | 至少一個 container 以錯誤結束 |
| CrashLoopBackOff | container 不斷崩潰重啟 |
💡 CrashLoopBackOff 是你最常在凌晨三點看到的狀態。通常代表你的 app 啟動就崩了——檢查 log 是第一步。
重要觀念:Pod 是暫時的 #
Pod 不是用來直接管理的。 如果一個 Pod 死了,K8s 不會自動幫你重建(除非有更上層的控制器管理它)。就像你不會手動一台一台 VM 開機一樣——你需要的是 Deployment。
四、Deployment:Pod 的管理員 #
為什麼需要 Deployment? #
直接建立 Pod 有幾個問題:
- Pod 死了不會自動重建
- 無法簡單地擴展到多個副本
- 更新版本很麻煩
Deployment 就是解決這些問題的。它是一個「控制器」,負責管理一組 Pod,確保它們的數量和狀態符合你的期望。
Deployment YAML #
# my-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-web-app
labels:
app: web
spec:
replicas: 3 # 我要 3 個 Pod
selector:
matchLabels:
app: web # 管理帶有 app=web 標籤的 Pod
template: # Pod 的模板
metadata:
labels:
app: web
spec:
containers:
- name: web
image: my-web-app:1.0
ports:
- containerPort: 8080
resources:
requests:
memory: "128Mi"
cpu: "250m"
limits:
memory: "256Mi"
cpu: "500m"
來拆解幾個重點:
replicas: 3:我要 3 個 Pod 同時運行。少了 K8s 會自動補上,多了會砍掉。selector:告訴 Deployment 要管理哪些 Pod(用 label 配對)template:Pod 的藍圖,每個 replica 都照這個模板建立resources:設定每個 container 的 CPU 和記憶體限制
基本操作 #
# 部署
kubectl apply -f my-deployment.yaml
# 查看 Deployment
kubectl get deployments
# NAME READY UP-TO-DATE AVAILABLE AGE
# my-web-app 3/3 3 3 30s
# 查看管理的 Pod
kubectl get pods -l app=web
# NAME READY STATUS RESTARTS AGE
# my-web-app-7d9f8b6c4-abc12 1/1 Running 0 30s
# my-web-app-7d9f8b6c4-def34 1/1 Running 0 30s
# my-web-app-7d9f8b6c4-ghi56 1/1 Running 0 30s
# 擴展到 5 個副本
kubectl scale deployment my-web-app --replicas=5
# 查看部署狀態
kubectl rollout status deployment my-web-app
滾動更新(Rolling Update) #
Deployment 最強大的功能之一就是滾動更新——更新版本時不會全部一次砍掉,而是逐步替換:
# 方法一:直接更新 image
kubectl set image deployment/my-web-app web=my-web-app:2.0
# 方法二:修改 YAML 後 apply
kubectl apply -f my-deployment.yaml
更新過程大概是這樣:
開始狀態:3 個 v1.0 Pod
↓
建立 1 個 v2.0 Pod,就緒後砍 1 個 v1.0
↓
建立 1 個 v2.0 Pod,就緒後砍 1 個 v1.0
↓
建立 1 個 v2.0 Pod,就緒後砍最後 1 個 v1.0
↓
結束狀態:3 個 v2.0 Pod ✅
你可以透過 strategy 控制更新行為:
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 更新時最多多出 1 個 Pod
maxUnavailable: 0 # 更新時不允許有 Pod 不可用
回滾(Rollback) #
部署了有 bug 的新版本?別慌:
# 查看部署歷史
kubectl rollout history deployment my-web-app
# 回滾到上一版
kubectl rollout undo deployment my-web-app
# 回滾到指定版本
kubectl rollout undo deployment my-web-app --to-revision=2
這就是 K8s 的安全網——部署出問題可以秒速回退。
五、Service:讓 Pod 被找到 #
問題:Pod IP 會變 #
Pod 是暫時的——它隨時可能被重建、被排到不同的 Node。每次重建後 IP 都會變。
如果你的前端要連後端 Pod,用 IP 直連?那每次 Pod 重啟你都要改設定。
Service 就是解決這個問題的。它提供一個穩定的入口(固定 IP + DNS 名稱),自動把流量導向後面的 Pod。
┌─────────┐
│ Service │ ← 固定的 ClusterIP / DNS
│ (proxy) │
└────┬─────┘
┌────────┼────────┐
▼ ▼ ▼
┌──────┐ ┌──────┐ ┌──────┐
│ Pod1 │ │ Pod2 │ │ Pod3 │
└──────┘ └──────┘ └──────┘
Service 的類型 #
K8s 提供四種 Service 類型:
| 類型 | 用途 | 說明 |
|---|---|---|
| ClusterIP | 叢集內部通訊 | 預設類型,只有叢集內能連 |
| NodePort | 開發測試 | 在每個 Node 開一個 port 對外 |
| LoadBalancer | 正式環境對外 | 用雲端的 load balancer |
| ExternalName | DNS 別名 | 指向外部服務 |
ClusterIP(預設) #
最常用的類型,給叢集內的其他 Pod 連接用:
# my-service.yaml
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
selector:
app: web # 找到所有 app=web 的 Pod
ports:
- protocol: TCP
port: 80 # Service 對外的 port
targetPort: 8080 # Pod 實際監聽的 port
type: ClusterIP # 可以省略,這是預設
建立後,叢集內的其他 Pod 可以用 web-service:80 或完整 DNS web-service.default.svc.cluster.local:80 來連。
# 建立 Service
kubectl apply -f my-service.yaml
# 查看 Service
kubectl get services
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# web-service ClusterIP 10.96.123.45 <none> 80/TCP 10s
# 測試連線(從叢集內)
kubectl run test --image=busybox --rm -it -- wget -qO- web-service:80
NodePort(開發用) #
想從叢集外面直接連進來:
apiVersion: v1
kind: Service
metadata:
name: web-nodeport
spec:
selector:
app: web
ports:
- protocol: TCP
port: 80
targetPort: 8080
nodePort: 30080 # 指定 Node 上開的 port(30000-32767)
type: NodePort
這樣你就可以用 <任何 Node 的 IP>:30080 連到你的服務。
LoadBalancer(正式環境) #
在雲端(AWS、GCP、Azure)上用:
apiVersion: v1
kind: Service
metadata:
name: web-lb
spec:
selector:
app: web
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: LoadBalancer
K8s 會自動幫你建一個雲端 load balancer,分配一個外部 IP。
六、三劍客協作:完整範例 #
來看一個完整的例子——部署一個 Python Flask web app:
1. 準備 Docker image #
# app.py
from flask import Flask, jsonify
import os
import socket
app = Flask(__name__)
@app.route("/")
def hello():
return jsonify({
"message": "Hello from K8s! 🚀",
"hostname": socket.gethostname(),
"version": os.getenv("APP_VERSION", "1.0")
})
@app.route("/health")
def health():
return jsonify({"status": "ok"})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
# Dockerfile
FROM python:3.12-slim
WORKDIR /app
RUN pip install flask
COPY app.py .
ENV APP_VERSION=1.0
CMD ["python", "app.py"]
# 建立 image
docker build -t my-flask-app:1.0 .
2. 建立 Deployment #
# flask-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: flask-app
labels:
app: flask
spec:
replicas: 3
selector:
matchLabels:
app: flask
template:
metadata:
labels:
app: flask
spec:
containers:
- name: flask
image: my-flask-app:1.0
ports:
- containerPort: 5000
env:
- name: APP_VERSION
value: "1.0"
readinessProbe: # 就緒探針
httpGet:
path: /health
port: 5000
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe: # 存活探針
httpGet:
path: /health
port: 5000
initialDelaySeconds: 10
periodSeconds: 15
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "250m"
注意這裡加了兩個 probe(探針):
- readinessProbe:Pod 準備好接收流量了嗎?沒準備好就不會收到 request
- livenessProbe:Pod 還活著嗎?死了就重啟
3. 建立 Service #
# flask-service.yaml
apiVersion: v1
kind: Service
metadata:
name: flask-service
spec:
selector:
app: flask
ports:
- protocol: TCP
port: 80
targetPort: 5000
type: LoadBalancer
4. 一鍵部署 #
# 全部部署
kubectl apply -f flask-deployment.yaml
kubectl apply -f flask-service.yaml
# 或是把所有 YAML 放在同一個資料夾
kubectl apply -f k8s/
# 檢查狀態
kubectl get all -l app=flask
看到類似這樣的輸出就成功了:
NAME READY STATUS RESTARTS AGE
pod/flask-app-7d9f8b6c4-abc12 1/1 Running 0 60s
pod/flask-app-7d9f8b6c4-def34 1/1 Running 0 60s
pod/flask-app-7d9f8b6c4-ghi56 1/1 Running 0 60s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
service/flask-service LoadBalancer 10.96.78.90 34.56.78.90 80:31234/TCP
NAME READY UP-TO-DATE AVAILABLE AGE
deployment/flask-app 3/3 3 3 60s
七、kubectl 常用指令速查 #
把最常用的指令整理一下:
# === 查看資源 ===
kubectl get pods # 列出所有 Pod
kubectl get pods -o wide # 顯示更多資訊(Node、IP)
kubectl get pods -w # watch 模式,即時更新
kubectl get all # 列出所有資源
# === 詳細資訊 ===
kubectl describe pod <name> # Pod 詳細資訊
kubectl describe deployment <name> # Deployment 詳細資訊
kubectl describe service <name> # Service 詳細資訊
# === Log ===
kubectl logs <pod-name> # 查看 log
kubectl logs <pod-name> -f # 串流 log(像 tail -f)
kubectl logs <pod-name> --previous # 上一次執行的 log(crash 偵錯好用)
# === 互動 ===
kubectl exec -it <pod> -- /bin/bash # 進入 Pod
kubectl port-forward <pod> 8080:80 # port forwarding 到本機
# === 刪除 ===
kubectl delete -f my-deployment.yaml # 刪除 YAML 定義的資源
kubectl delete pod <name> # 刪除特定 Pod
kubectl delete all -l app=flask # 刪除所有帶有標籤的資源
# === Namespace(命名空間)===
kubectl get pods -n kube-system # 查看特定 namespace
kubectl get namespaces # 列出所有 namespace
八、本機玩 K8s:用 Minikube 或 kind #
你不需要三台伺服器才能玩 K8s。在本機就能跑:
方法一:Minikube #
# 安裝(macOS)
brew install minikube
# 啟動叢集
minikube start
# 確認狀態
kubectl cluster-info
# 用完關掉
minikube stop
# 砍掉重來
minikube delete
方法二:kind(Kubernetes in Docker) #
# 安裝
brew install kind
# 建立叢集
kind create cluster --name my-cluster
# 用 kind 載入本地 image(不用推到 registry)
kind load docker-image my-flask-app:1.0 --name my-cluster
# 刪除叢集
kind delete cluster --name my-cluster
💡 推薦用 kind 做本地開發,比 Minikube 輕量。但如果你需要完整的 dashboard 和 add-on,Minikube 更方便。
九、常見地雷 #
1. ImagePullBackOff #
STATUS: ImagePullBackOff
Container image 拉不下來。可能原因:
- image 名稱打錯
- 用了 private registry 但沒設定 imagePullSecret
- 用
latesttag 但imagePullPolicy是IfNotPresent
# 解法:確認 imagePullPolicy
containers:
- name: app
image: my-app:1.0
imagePullPolicy: Always # 強制每次拉取
2. CrashLoopBackOff #
Pod 不斷重啟。第一步永遠是看 log:
kubectl logs <pod-name> --previous
常見原因:
- 應用程式啟動就 crash
- 環境變數沒設定
- port 衝突
3. Pending 狀態 #
Pod 一直停在 Pending,通常是資源不夠:
kubectl describe pod <name>
# 看 Events 區塊,通常會說 Insufficient cpu/memory
解法:減少 resource request,或增加 Node。
4. Service 連不到 Pod #
# 確認 selector 跟 Pod label 有對上
kubectl get pods --show-labels
kubectl describe service <name>
# 確認 endpoint 有 Pod
kubectl get endpoints <service-name>
十、Docker Compose vs Kubernetes #
學過 Docker Compose 的你可能會問:「那我什麼時候該用 K8s?」
| 面向 | Docker Compose | Kubernetes |
|---|---|---|
| 適合場景 | 本地開發、小型專案 | 正式環境、大規模部署 |
| 設定複雜度 | 低 | 高 |
| 自動擴展 | ❌ | ✅ |
| 自動修復 | ❌ | ✅ |
| 滾動更新 | 有限 | 完整支援 |
| 服務發現 | 簡單 DNS | 完整的 Service 機制 |
| 負載均衡 | 無 | 內建 |
| 學習曲線 | 平緩 | 陡峭 |
簡單規則:
- 🏠 個人專案 / 開發環境 → Docker Compose
- 🏢 團隊協作 / 正式上線 → Kubernetes
- 🤔 不確定 → 先用 Docker Compose,等需要時再遷移
十一、下一步 #
今天只是 K8s 的冰山一角。接下來可以探索的方向:
- ConfigMap & Secret:管理設定檔和敏感資訊
- Ingress:比 Service 更強大的 HTTP 路由
- Persistent Volume:讓資料不隨 Pod 消失
- Helm:K8s 的套件管理工具
- Namespace:多租戶隔離
- HPA(Horizontal Pod Autoscaler):根據 CPU/記憶體自動擴縮
十二、總結 #
今天我們學了 K8s 三個核心角色:
| 概念 | 角色 | 類比 |
|---|---|---|
| Pod | 最小部署單位 | 一個工人 |
| Deployment | Pod 的管理員 | 工頭,確保人數和版本 |
| Service | 穩定的入口 | 公司總機,幫你轉接 |
記住這個關係:
你(YAML)→ Deployment(管 Pod 數量)→ Pod(跑 container)
↑
Service(穩定入口)
K8s 的學習曲線確實比較陡,但一旦掌握了基本概念,你就會發現它的設計非常優雅。Pod、Deployment、Service 三者的分工清晰,各司其職。
容器編排,K8s 說了算 🚢
下次見,拍拍君。