快轉到主要內容
  1. 教學文章/

Kubernetes 入門:Pod, Service, Deployment 一次搞懂

·10 分鐘· loading · loading · ·
Kubernetes K8s Devops Container Docker Pod Deployment
每日拍拍
作者
每日拍拍
科學家 X 科技宅宅
目錄
DevOps - 本文屬於一個選集。
§ 2: 本文

一、前言
#

嗨,我是拍拍君 🐳

如果你已經學會了 Docker,知道怎麼把應用程式打包成 container,那恭喜你——你已經準備好踏入下一個大坑了。

那個坑叫 Kubernetes(簡稱 K8s,因為 k 和 s 之間有 8 個字母,工程師就是這麼懶)。

想像一下:你有一個 web app 跑在 Docker container 裡,一切運作良好。但如果這個 container 掛了怎麼辦?如果流量暴增需要 10 個 container 怎麼辦?如果你要做零停機更新怎麼辦?

手動管理?那你大概會先崩潰。

Kubernetes 就是幫你自動化管理大量 container 的工具。 它負責:

  • 自動部署你的 container
  • 監控它們的健康狀態
  • 掛了自動重啟
  • 流量太大自動擴展
  • 滾動更新不斷線

今天我們就來搞懂 K8s 最核心的三個概念:PodServiceDeployment。學會這三個,你就掌握了 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 有幾個問題:

  1. Pod 死了不會自動重建
  2. 無法簡單地擴展到多個副本
  3. 更新版本很麻煩

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
  • latest tag 但 imagePullPolicyIfNotPresent
# 解法:確認 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 的冰山一角。接下來可以探索的方向:

  1. ConfigMap & Secret:管理設定檔和敏感資訊
  2. Ingress:比 Service 更強大的 HTTP 路由
  3. Persistent Volume:讓資料不隨 Pod 消失
  4. Helm:K8s 的套件管理工具
  5. Namespace:多租戶隔離
  6. HPA(Horizontal Pod Autoscaler):根據 CPU/記憶體自動擴縮

十二、總結
#

今天我們學了 K8s 三個核心角色:

概念 角色 類比
Pod 最小部署單位 一個工人
Deployment Pod 的管理員 工頭,確保人數和版本
Service 穩定的入口 公司總機,幫你轉接

記住這個關係:

你(YAML)→ Deployment(管 Pod 數量)→ Pod(跑 container)
                                          ↑
                                    Service(穩定入口)

K8s 的學習曲線確實比較陡,但一旦掌握了基本概念,你就會發現它的設計非常優雅。Pod、Deployment、Service 三者的分工清晰,各司其職。

容器編排,K8s 說了算 🚢

下次見,拍拍君。

DevOps - 本文屬於一個選集。
§ 2: 本文

相關文章

Docker Compose:多容器服務編排實戰
·5 分鐘· loading · loading
Docker Docker-Compose Devops Container 部署
Docker for Python:讓你的程式在任何地方都能跑
·6 分鐘· loading · loading
Python Docker Container Devops 部署
Rust Collections vs Python:Vec 和 HashMap 的生存指南
·7 分鐘· loading · loading
Rust Python Collections Vec Hashmap
Python Generators/Yield 完全攻略:惰性運算的藝術
·7 分鐘· loading · loading
Python Generator Yield Lazy Evaluation Iterator
Rust 生命週期(Lifetime)入門:編譯器教你管記憶體
·8 分鐘· loading · loading
Rust Lifetime Borrowing Memory-Safety Python
Python match/case:結構化模式匹配完全攻略
·6 分鐘· loading · loading
Python Match Pattern-Matching Python 3.10