容器化與編排 (Container & Kubernetes)
容器把應用程式和它需要的一切(程式碼、runtime、library、設定)打包成一個獨立單元,在任何環境都以完全相同的方式執行。
系統設計上的價值三件事:一致性(到處一樣跑)、隔離性(服務互不干擾)、效率(秒級啟動、高密度部署)。它是現代水平擴展架構的地基。
虛擬機器 vs 容器
VM 模擬整台電腦(含獨立 kernel);容器共用宿主機 OS 核心,只隔離應用與 library。
- Linux namespace → 每個容器有自己的行程空間、網路、檔案系統視圖
- Linux cgroups → 限制每個容器的 CPU / 記憶體
| 維度 | 虛擬機器(VM) | 容器 |
|---|---|---|
| 啟動時間 | 幾分鐘 | 幾秒(常幾百毫秒) |
| 記憶體 | 幾 GB(含完整 OS) | 幾十~幾百 MB |
| 隔離程度 | 完整 kernel 隔離 | 行程層面,共用 kernel |
| 適合場景 | 強安全隔離、跑不同 OS | 微服務、快速擴展、高密度 |
除非題目明確涉及強安全隔離(多租戶跑不同客戶程式碼),大多數時候你談的是容器而非 VM。
Docker:容器的基本單位
| 概念 | 說明 |
|---|---|
| Image(鏡像) | 唯讀藍圖,定義容器啟動後狀態(基礎層 + 程式碼 + 相依)。類比 class |
| Container | image 的執行實例,有自己的可寫檔案系統層。類比 object |
一個 image 可同時跑多個容器(水平擴展):
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production # 這層被快取,只改程式碼時不重裝
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
- Image 分層(layer):Dockerfile 每個指令產生一層,Docker 快取每一層。只改程式碼 → 直接拿
npm ci那層的快取,建置快、磁碟省(多 image 共用基礎層只存一份) - Image Registry:存放分發 image 的中央倉庫。Docker Hub(公開)/ AWS ECR / GCP Artifact Registry / Harbor。CI/CD 推 image 進 registry,部署時拉取
為什麼容器對系統設計重要
| 屬性 | 說明 |
|---|---|
| 水平擴展 | 不是升級一台機器,而是複製實例。秒級啟動 → 尖峰 5→50 個容器幾乎即時 |
| 服務隔離 | 每服務跑自己容器(不同語言互不干擾);故障隔離:一個容器崩潰不影響其他 |
| 資源效率 | 多服務密集共用同一台機器,每容器只用實際需要的 CPU/記憶體 |
容器編排:Kubernetes(K8s)
幾十個服務、幾百個容器、幾十台機器 → 需要編排器統一管理部署、擴展、網路、健康監控。
| 抽象 | 角色 |
|---|---|
| Pod | 最小部署單位,1+ 容器共用網路/儲存。短暫的,不能存持久狀態 |
| Deployment | 部署無狀態服務。「維持 3 個健康 Pod」;Pod 崩潰自動補;更新逐步替換 |
| Service | Pod IP 不固定 → Service 提供穩定網路端點,自動對應符合條件的 Pod |
| HorizontalPodAutoscaler | 依 CPU / 記憶體 / 自訂指標自動增減 Pod 數 |
kind: Deployment
spec:
replicas: 3 # 保持三個 Pod
template:
spec:
containers:
- image: my-registry/order-service:1.2
resources:
requests: { memory: "256Mi", cpu: "250m" } # 保證
limits: { memory: "512Mi", cpu: "500m" } # 上限
livenessProbe:
httpGet: { path: /health, port: 3000 }
滾動更新(Rolling Update)— 零停機
1. 起一個新版本 Pod,等它通過 health check
2. 停掉一個舊版本 Pod
3. 重複,直到全部換成新版本
→ 過程中始終有 Pod 在服務;有問題立刻回滾(舊 image 還在 registry)
無狀態設計的重要性
把 session 存在容器本地記憶體 → 第二個請求被 LB 打到容器 B,找不到 session,用戶被登出;容器 A 重啟,記憶體 session 全消失。
狀態必須外部化:
| 狀態類型 | 存放 |
|---|---|
| Session/用戶 | Redis |
| 持久化資料 | PostgreSQL / MySQL |
| 檔案、媒體 | S3 / 物件儲存 |
| 服務間設定 | 環境變數 / ConfigMap |
容器本身是短暫的(ephemeral),隨時可停可重建,不帶走任何東西。
容器的網路模型
- 同機容器:透過 Docker 虛擬網路直接溝通;同 Pod 內容器用
localhost互呼 - 跨機服務間:透過 K8s Service 的 DNS 名稱(
order-service.production.svc.cluster.local),不必知道 Pod IP - 對外暴露:Ingress Controller 依路徑/host 路由到 Service,也可在此做 TLS 終止與限速
面試話術
「把 API service 容器化部署在 K8s 上,設 HPA:CPU > 70% 自動增加 Pod。因為服務無狀態,LB 可把請求打到任何 Pod,擴縮都是幾秒的事。」比「加更多伺服器」具體得多。
常見情境:
- 電商促銷:HPA 幾秒內 Pod 10→100,活動後縮回,只付尖峰費用
- 影片串流:API 用通用機器、轉碼容器跑 GPU 機器,按需啟動完即停
- 多租戶 SaaS:用 K8s namespace + 資源配額隔離不同客戶
常見 Deep Dive
Deployment 保證指定數量 Pod 始終運行。Pod 崩潰 → controller loop 透過 liveness probe 幾秒內偵測並自動起新 Pod 替換。
Liveness probe:「容器還活著嗎?」失敗 → 重啟。Readiness probe:「準備好接流量了嗎?」失敗 → 從 Service 後端移除(暖機中或連不到 DB 時不該接流量)。
Image 不該包含環境設定或密碼 —— 同一 image 要能跑開發/測試/生產,差別只在設定。
設定 → 環境變數 / ConfigMap 注入;機密(密碼、API key)→ Secret 或外部 Vault / AWS Secrets Manager。機密絕不進 image,也不進 git。
容器:90% 工作負載(微服務、API、worker、排程),啟動快、密度高、CI/CD 順。
VM:① 強安全隔離(多租戶 kernel 級隔離,Lambda 底層用 Firecracker microVM、Fargate 是 VM 級隔離)② 跑不同 OS(服務要 Windows,cluster 是 Linux)③ Legacy 應用無法容器化。
除了滾動更新:① DB migration 要向後相容(不能同時加 not-null 欄位 + 滾動更新,舊版本容器會崩)② 版本化 API(破壞性變更保留 /v1/ 一段時間)③ Graceful shutdown(收到 SIGTERM 先拒新請求、等進行中請求完成再退出)。
有狀態服務需要額外設計(PersistentVolume、StatefulSet);強安全隔離可能需要 VM。知道容器不適合什麼才是成熟的架構判斷。
自我測驗重點
| 問題 | 重點 |
|---|---|
| VM vs 容器 | 共用 kernel(namespace/cgroups)vs 完整 OS |
| Image vs Container | class vs object;分層快取 |
| K8s 四抽象 | Pod / Deployment / Service / HPA |
| 容器崩潰自癒 | Deployment + liveness probe |
| liveness vs readiness | 活著重啟 vs 沒準備好移出 Service |
| 容器前提 | 無狀態,狀態外移 Redis/DB/S3 |
| 零停機細節 | migration 向後相容 + graceful shutdown |
Related Notes
- 11-Infrastructure/04-Serverless — Serverless 是比容器更極端的無狀態 + 自動擴展;對照三方部署模式
- 11-Infrastructure/02-Load-Balancer — K8s Service / Ingress 本質是 LB;健康檢查對應 probe
- 02-Distributed-Systems/03-Scalability — 容器是水平擴展與 stateless 設計的落地手段
- 10-Design-Patterns/02-Scaling-Writes — HPA 自動擴展處理流量尖峰
- 07-Caching-Storage/01-Caching — 無狀態服務把 session 外移到 Redis