【從一台 Server 到分散式架構】第 21 篇:廚房變成標準化套餐——容器化(Docker)的動機

某個週五下午,小明把新版本部署到測試環境,畫面一片白。

他 SSH 進去測試機查,發現 Node.js 版本是 16,而他本機是 18。某個套件的 API 在兩個版本之間有破壞性變更。

改好之後,他把版本對齊,重新部署。這次測試環境通了,但上到正式環境又掛了——正式環境的 Redis 版本跟開發環境不一樣,連接方式有差異。

小傑看著小明來回改了三次,說:

「你在修的不是程式,是環境差異。這種事每次部署都要來一遍,因為你部署的只是程式碼,不是整個執行環境。」

小明說:「那怎麼辦?」

小傑:「把環境也打包進去。」


生活化的比喻:廚師帶著整個廚房出差

傳統部署方式,像是廚師只帶著食譜去陌生的廚房做菜。

他假設那個廚房有他需要的鍋具、調味料、火候。但每個廚房都不一樣——有的爐子火太大、有的沒有特定的香料、有的鍋子形狀不對。

廚師每次都要花時間「適應新廚房」,才能開始做菜。

容器化的做法是:廚師帶著整個廚房出差——連鍋具、調味料、食材一起打包成一個標準化的「套餐」。

不管你把這個套餐搬到哪裡——自己電腦、測試機、正式環境、別的雲端——打開來都是一樣的廚房,廚師可以直接開始做菜。

這就是 Docker 在解決的問題。


核心概念:Image 和 Container

Docker 有兩個最基本的概念:

Image(映像檔)——食譜 + 備好的食材

Image 是一個靜態的、唯讀的打包檔,裡面包含:

  • 作業系統層(通常是輕量的 Linux)
  • 執行環境(Node.js 18、Python 3.11……)
  • 應用程式程式碼
  • 套件依賴(package.json 裡的所有東西)
  • 設定檔

Image 是「可以重現的快照」。你在任何地方用同一個 Image,得到的環境都一模一樣。

Container(容器)——正在執行的廚房

Container 是從 Image 啟動起來的執行中實例

一個 Image 可以同時啟動多個 Container,每個 Container 彼此隔離——就像同一份食譜,同時在三個廚房各做一份。

類比整理:

概念比喻
Dockerfile食譜(描述怎麼建造環境)
Image照著食譜備好的標準化廚房(靜態)
Container正在運作的廚房(動態)

Dockerfile:告訴 Docker 怎麼打包

你需要寫一個 Dockerfile 來描述 Image 應該長什麼樣:

# 從官方 Node.js 18 基底開始
FROM node:18-alpine
 
# 設定工作目錄
WORKDIR /app
 
# 先複製 package.json,安裝依賴(善用快取)
COPY package*.json ./
RUN npm ci --only=production
 
# 再複製應用程式程式碼
COPY . .
 
# 開放 3000 port
EXPOSE 3000
 
# 啟動指令
CMD ["node", "server.js"]

執行 docker build -t course-api:v1.0 .,Docker 就會照著這份 Dockerfile 建出一個 Image。


容器化帶來的不只是「到處能跑」

環境一致性只是 Docker 的第一個好處,它還帶來幾個連鎖效應:

1) 部署流程標準化

沒有容器化之前,部署是這樣:

SSH 進機器 → 拉 code → 跑 npm install → 重啟 PM2 → 祈禱

容器化之後,部署變成:

推新的 Image → 啟動新 Container → 舊 Container 下線

每個環境的部署步驟一樣,不再依賴機器上的「歷史狀態」。

2) 快速橫向擴充

要多跑幾個 API 服務的副本,只需要多起幾個 Container:

docker run -d course-api:v1.0
docker run -d course-api:v1.0
docker run -d course-api:v1.0

三台副本,三秒內啟動完畢。

3) 隔離性——一台機器跑多個服務不互相干擾

以前一台機器上同時跑訂單服務和支付服務,如果它們依賴不同版本的 Python,就會衝突。

容器化後,每個服務都住在自己的容器裡,各自有自己的執行環境,彼此不干擾。

4) 開發環境一致

新人加入團隊,以前要花半天設定開發環境;現在:

docker-compose up

一行指令,把整個開發環境(API + DB + Redis)全部啟動起來,跟其他人的環境一模一樣。


容器不是虛擬機

很多人第一次聽到容器,會以為它就是虛擬機(VM)的縮小版。兩者有本質差異:

虛擬機(VM)容器(Container)
隔離層級硬體層(每個 VM 有自己的 OS kernel)作業系統層(共用 host kernel)
啟動時間分鐘級秒級(甚至毫秒)
佔用空間GB 級MB 級
隔離強度較弱(共用 kernel,有逃逸風險)

容器比 VM 輕量很多,啟動更快,適合作為服務部署單位。需要強隔離的場景(例如給不同客戶跑不同程式碼),還是需要 VM。


小明的落地做法

第一步:給每個服務寫 Dockerfile
  每個服務(訂單、支付、通知…)各自有一個 Dockerfile

第二步:本地開發用 docker-compose
  一個 docker-compose.yml 定義整個開發環境:
  API + PostgreSQL + Redis + mock 外部服務

第三步:CI/CD 推 Image
  每次 push 到主分支,CI 自動建 Image 推到 Image Registry
  (Docker Hub / AWS ECR / Google GCR)

第四步:正式環境拉 Image 跑
  正式機器從 Registry 拉指定版本的 Image,啟動 Container

小結與預告

這篇小明學到的重點是:

  1. 容器化解決環境不一致:把程式碼和執行環境一起打包成 Image,任何地方跑都一樣。
  2. Image vs Container:Image 是靜態快照(食譜),Container 是執行中的實例(廚房)。
  3. 容器不只是「能跑」:還帶來部署標準化、快速擴充、服務隔離、開發環境一致。
  4. 容器比 VM 輕量:秒級啟動、MB 級大小,適合服務部署。

容器解決了「在哪裡跑都一樣」的問題。但當你有幾十個、幾百個容器分散在幾十台機器上——誰來決定每個容器要跑在哪台機器?容器掛掉了誰來重啟它?流量多了誰來自動加容器?

下一篇,我們來談負責「管理一群容器」的系統——Kubernetes

相關推薦

2026-04-07
【從一台 Server 到分散式架構】第 31 篇:從面試題反推架構——用本系列思維拆解系統設計考題
「設計一個 URL 短網址服務」「設計一個訊息系統」——系統設計面試問的不是背答案,而是思考方式。這篇整理了一套可以重複使用的面試框架,並用兩道例題示範如何從需求出發,一步步推導出架構、說清楚取捨。
2026-04-06
【從一台 Server 到分散式架構】第 30 篇:限流、排隊與降級實戰——開賣與直播場景
平常流量是 1000 QPS,但「開賣」的那一刻,瞬間湧入 50 萬個請求——系統要怎麼活下來?這篇用課程平台的限量課程開賣和直播開播場景,把第 13 篇學到的限流、降級、熔斷,落地到一個真實的高流量設計,走過每個防護層是怎麼工作的。
2026-04-05
【從一台 Server 到分散式架構】第 29 篇:用同樣的思維看 ChatGPT——AI 聊天系統架構
ChatGPT 看起來像一個聊天視窗,背後卻有幾個特殊的設計挑戰:回應是串流的、推理非常耗資源、每輪對話要記住上下文、系統要支援幾千萬用戶同時使用。這篇用熟悉的架構思維,拆解 AI 聊天系統的關鍵設計。
2026-04-04
【從一台 Server 到分散式架構】第 28 篇:用同樣的思維看 Twitter——社群動態與時序設計
Twitter 的核心功能看起來很簡單:發推文、看動態、按讚留言。但「動態牆」背後藏著一個棘手的設計問題:你追蹤 200 個人,每個人都可能隨時發文——你打開 App 時,那條時序動態要怎麼快速組出來?這篇來看社群動態系統的兩種策略:推(Fan-out on Write)與拉(Fan-out on Read)。