返回首頁Cloud Native

Cloud Native 12 Factor 完整解析:打造可擴展雲原生應用的 12 項原則【2025】

23 min 分鐘閱讀
#12-factor#cloud-native#architecture#microservices#devops

Cloud Native 12 Factor 完整解析:打造可擴展雲原生應用的 12 項原則【2025】

你寫的程式在自己電腦上跑得好好的,一上線就出問題。設定檔散落各處,環境變數搞不清楚,每次部署都像在拆炸彈。這些問題,12 Factor App 在十多年前就給出了解答。

12 Factor 是一套經過實戰驗證的雲端應用開發原則。不管你用什麼程式語言、什麼框架,這 12 項原則都適用。讀完這篇文章,你會知道每項原則的具體意義,以及如何在你的專案中落實。

台灣軟體工程師在電腦前檢視程式碼架構圖,螢幕顯示微服務元件與依賴關係


什麼是 12 Factor App?

12 Factor 的起源

12 Factor App 由 Heroku 共同創辦人 Adam Wiggins 在 2011 年提出。當時 Heroku 是最早的 PaaS(Platform as a Service)平台之一,他們累積了大量在雲端環境運行 SaaS 應用的經驗。

這些經驗被整理成 12 項原則,幫助開發者避開常見的坑。雖然已經過了十多年,這些原則到今天仍然適用,甚至成為 Cloud Native 應用的基礎標準。

為什麼 12 Factor 重要?

12 Factor 解決的是這些問題:

  • 環境不一致:開發環境可以跑,生產環境就掛
  • 部署困難:每次上線都要手動改一堆設定
  • 擴展受限:應用程式無法水平擴展
  • 維護困難:新人接手專案要花很長時間理解

遵循 12 Factor 原則的應用程式:

  • 可以在不同環境間無縫移植
  • 適合部署在雲端平台
  • 可以輕鬆水平擴展
  • 減少開發和維運之間的差異

12 Factor 與 Cloud Native 的關係

12 Factor 比 Cloud Native 這個詞出現得更早,但它的理念完全符合 Cloud Native 架構的要求。事實上,如果你想導入 Kubernetes 或微服務架構,遵循 12 Factor 是最基本的前提。

CNCF 後來提出的許多最佳實踐,都可以追溯到 12 Factor 的原則。可以說,12 Factor 是 Cloud Native 的地基

想了解 Cloud Native 完整概念?請參考 Cloud Native 完整指南:雲原生是什麼?


12 項原則完整解析

1. Codebase(程式碼基底)

一份程式碼基底,多個部署環境

原則說明:

一個應用程式應該只有一份程式碼,存放在版本控制系統(如 Git)中。這份程式碼可以部署到多個環境(開發、測試、生產),但程式碼本身只有一份。

正確做法:

my-app/
├── src/
├── package.json
└── .git/

# 透過 CI/CD 部署到不同環境
dev.example.com  ← 同一份程式碼
staging.example.com  ← 同一份程式碼
prod.example.com  ← 同一份程式碼

違反的情況:

  • 開發環境和生產環境用不同的程式碼版本
  • 針對不同客戶 fork 出不同版本的程式碼
  • 把共用程式碼複製貼上到多個專案

為何重要:

程式碼分散會導致版本混亂。一個 bug 修了這邊,忘了修那邊。遵循這個原則,任何變更都能追蹤,任何環境的問題都能重現。

2. Dependencies(依賴管理)

明確宣告並隔離依賴

原則說明:

應用程式不應該依賴系統層級的套件。所有依賴都應該在依賴清單(如 package.json、requirements.txt、pom.xml)中明確宣告。

正確做法:

// package.json
{
  "dependencies": {
    "express": "^4.18.0",
    "pg": "^8.11.0",
    "redis": "^4.6.0"
  }
}

違反的情況:

  • 程式碼假設系統已經安裝了某個命令列工具
  • 使用未宣告的全域套件
  • README 寫著「請先手動安裝 ImageMagick」

為何重要:

明確宣告依賴,新人可以用一個指令就把開發環境架好。部署時也不會因為「這台機器少裝了什麼」而出錯。

3. Config(配置外部化)

設定存在環境變數中

原則說明:

設定(資料庫連線、API 金鑰、服務端點)不應該寫在程式碼裡。應該透過環境變數注入,讓同一份程式碼可以在不同環境使用不同設定。

正確做法:

// 從環境變數讀取設定
const dbHost = process.env.DATABASE_HOST;
const apiKey = process.env.API_KEY;

違反的情況:

// 寫死在程式碼裡(錯誤)
const dbHost = 'prod-db.example.com';
const apiKey = 'sk-1234567890';

為何重要:

  • 避免把機密資訊提交到 Git
  • 不同環境使用不同設定,不需要改程式碼
  • 安全性:機密資訊不會外洩到程式碼倉庫

判斷標準:

問自己:這份程式碼可以立即開源嗎?如果開源會洩漏機密,那就是設定寫錯地方了。

4. Backing Services(後端服務)

把後端服務當作附加資源

原則說明:

資料庫、快取、訊息佇列、Email 服務等,都應該當作可抽換的「附加資源」。應用程式不應該區分「本地服務」和「第三方服務」。

正確做法:

# 透過環境變數指定服務位置
DATABASE_URL=postgres://user:pass@localhost:5432/mydb
REDIS_URL=redis://localhost:6379
SMTP_URL=smtp://sendgrid.net:587

切換資料庫?只要改環境變數,不需要改程式碼。

違反的情況:

  • 程式碼假設資料庫一定在 localhost
  • 硬編碼服務的 IP 位址
  • 本地開發用 SQLite,生產用 PostgreSQL(但程式碼有差異)

為何重要:

雲端環境的服務經常變動。資料庫可能遷移、快取服務可能換供應商。遵循這個原則,這些變動不需要改程式碼,只要改設定。

Kubernetes 如何實踐這項原則?請參考 Cloud Native 技術棧入門,了解 K8s 的 Service 和 ConfigMap 機制。

5. Build, Release, Run(建置、發布、執行)

嚴格分離建置和執行階段

原則說明:

程式碼到執行之間,應該經過三個階段:

  1. Build(建置):把程式碼編譯成可執行的 artifact
  2. Release(發布):把 build 產物和設定結合,產生可部署的版本
  3. Run(執行):在執行環境中啟動應用程式

正確做法:

程式碼 → [Build] → Docker Image
Docker Image + Config → [Release] → v1.2.3
v1.2.3 → [Run] → 執行中的容器

每個 Release 都應該有唯一的版本號,方便追蹤和回滾。

違反的情況:

  • 直接在生產環境修改程式碼
  • Build 時寫入設定值
  • 沒有版本號,不知道現在跑的是哪個版本

為何重要:

嚴格分離這三個階段,可以確保:

  • 同一個 build 可以部署到不同環境
  • 出問題時可以快速回滾到上一個版本
  • 每個版本都可重現

6. Processes(無狀態流程)

把應用程式當作無狀態的流程執行

原則說明:

應用程式的每個實例(process)都應該是無狀態的。任何需要持久化的資料都應該存在外部服務(資料庫、快取)。

正確做法:

// 使用 Redis 存 session
const session = require('express-session');
const RedisStore = require('connect-redis')(session);

app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: process.env.SESSION_SECRET
}));

違反的情況:

// 把資料存在記憶體裡(錯誤)
const userSessions = {};

app.post('/login', (req, res) => {
  userSessions[req.body.userId] = { loggedIn: true };
});

為何重要:

無狀態的應用程式才能水平擴展。如果狀態存在記憶體裡,增加一台伺服器就會造成資料不一致。

這也是為什麼 Kubernetes 可以隨時重啟 Pod 而不影響服務——因為 Pod 本身不存重要資料。

7. Port Binding(端口綁定)

透過 Port 綁定對外提供服務

原則說明:

應用程式應該自己綁定一個 Port 來對外提供服務,而不是依賴外部的 Web Server(如 Apache、Nginx)來處理請求。

正確做法:

const express = require('express');
const app = express();

const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(`Server listening on port ${port}`);
});

違反的情況:

  • 程式碼假設前面一定有 Apache 或 Nginx
  • 沒有定義如何對外暴露服務

為何重要:

容器環境中,每個容器都是獨立的單位。它應該自己知道如何對外提供服務,而不是依賴外部設定。這讓應用程式可以被當作其他應用的「backing service」使用。

8. Concurrency(併發)

透過 Process 模型水平擴展

原則說明:

應用程式應該透過增加 process 數量來處理更多負載,而不是把單一 process 變得更大(加更多 CPU、RAM)。

擴展模型:

┌─────────────────────────────────────┐
│  Web (3 instances)                  │
│  ├─ web.1  ├─ web.2  ├─ web.3      │
├─────────────────────────────────────┤
│  Worker (2 instances)               │
│  ├─ worker.1  ├─ worker.2          │
├─────────────────────────────────────┤
│  Clock (1 instance)                 │
│  └─ clock.1                        │
└─────────────────────────────────────┘

違反的情況:

  • 用單一程序處理所有事情
  • 用多線程(thread)來擴展,但無法跨機器
  • 假設只會有一個實例在執行

為何重要:

水平擴展比垂直擴展更靈活、更經濟。Kubernetes 的 HPA(Horizontal Pod Autoscaler)就是基於這個概念——流量大時自動增加 Pod 數量。

電腦螢幕顯示 Kubernetes 自動擴展儀表板,顯示多個 Pod 副本運行狀態

9. Disposability(可處置性)

快速啟動、優雅關閉

原則說明:

應用程式應該可以在幾秒內啟動,也可以優雅地關閉。這讓系統可以快速擴展、快速部署、快速恢復。

正確做法:

// 處理關閉信號
process.on('SIGTERM', async () => {
  console.log('Received SIGTERM, shutting down gracefully');

  // 停止接受新請求
  server.close();

  // 完成進行中的請求
  await finishPendingRequests();

  // 關閉資料庫連線
  await db.close();

  process.exit(0);
});

違反的情況:

  • 啟動需要好幾分鐘
  • 關閉時直接殺掉 process,不處理進行中的請求
  • 沒有處理 SIGTERM 信號

為何重要:

Kubernetes 在重新部署或縮減 Pod 時,會發送 SIGTERM 給容器。如果容器沒有優雅關閉,進行中的請求會失敗,使用者會看到錯誤。

12 Factor 聽起來簡單,落地卻不容易。預約架構諮詢,讓有經驗的人幫你避開實作的坑。

10. Dev/Prod Parity(開發/生產一致性)

保持開發、測試、生產環境一致

原則說明:

開發環境和生產環境之間的差異應該越小越好。這包括:

  • 時間差距:開發完到部署的時間越短越好
  • 人員差距:開發者也應該參與部署
  • 工具差距:開發和生產使用相同的技術棧

正確做法:

# docker-compose.yml 用於本地開發
services:
  db:
    image: postgres:15  # 和生產環境一樣
  redis:
    image: redis:7      # 和生產環境一樣
  app:
    build: .
    environment:
      - DATABASE_URL=postgres://...

違反的情況:

  • 開發用 SQLite,生產用 PostgreSQL
  • 開發用 Windows,生產用 Linux
  • 開發環境落後生產環境好幾個版本

為何重要:

環境差異是「在我電腦上可以跑」問題的根源。Docker 和容器技術的出現,讓維持環境一致變得容易許多。

11. Logs(日誌即事件流)

把日誌當作事件流處理

原則說明:

應用程式不應該管理自己的日誌檔案。它應該把日誌寫到標準輸出(stdout),讓執行環境來決定如何收集和儲存。

正確做法:

// 寫到 stdout
console.log(JSON.stringify({
  timestamp: new Date().toISOString(),
  level: 'info',
  message: 'User logged in',
  userId: user.id
}));

違反的情況:

// 寫到檔案(錯誤)
const fs = require('fs');
fs.appendFileSync('/var/log/app.log', 'User logged in\n');

為何重要:

在容器環境中,容器是暫時的。把日誌寫到容器內的檔案,容器一刪除日誌就不見了。

寫到 stdout 的日誌可以被 Kubernetes 收集,送到 Elasticsearch、CloudWatch 等集中式日誌系統。這讓你可以跨所有實例搜尋和分析日誌。

12. Admin Processes(管理流程)

把管理任務當作一次性程序執行

原則說明:

資料庫遷移、資料清理、維護腳本等管理任務,應該在和應用程式相同的環境中執行,使用相同的程式碼和設定。

正確做法:

# 在 K8s 中用 Job 執行資料庫遷移
kubectl run db-migrate \
  --image=myapp:v1.2.3 \
  --env="DATABASE_URL=$DATABASE_URL" \
  --command -- npm run migrate

違反的情況:

  • 直接 SSH 到生產伺服器執行腳本
  • 用不同版本的程式碼執行管理任務
  • 管理腳本沒有進版本控制

為何重要:

管理任務和應用程式面對同樣的環境變數、同樣的資料庫、同樣的設定。在不同環境執行,可能會因為版本不一致造成問題。

台灣工程師團隊在白板前討論 12 Factor 原則,白板上列出 12 項原則清單


15 Factor 擴充版

隨著 Cloud Native 發展,社群發現原始的 12 Factor 需要補充一些現代實踐。以下是常被提及的 3 項額外原則:

13. API First

原則說明:

設計時優先考慮 API 介面。在寫實作程式碼之前,先定義 API 契約(OpenAPI、GraphQL Schema)。

為什麼需要這項原則:

  • 微服務架構中,服務間透過 API 溝通
  • 前後端分離的開發模式需要清楚的 API 規格
  • API 契約可以用來自動產生文件和 SDK

14. Telemetry

原則說明:

應用程式應該內建可觀測性(Observability)能力:

  • Metrics:效能指標(請求數、延遲、錯誤率)
  • Tracing:分散式追蹤(一個請求經過哪些服務)
  • Logging:結構化日誌

為什麼需要這項原則:

在微服務架構中,傳統的除錯方式不再適用。你需要能夠跨服務追蹤一個請求的完整路徑。

OpenTelemetry 是目前 CNCF 推動的標準,可以同時處理 metrics、tracing 和 logging。

15. Security

原則說明:

安全性應該內建在應用程式中,而不是事後加上的防護層。這包括:

  • 依賴項的安全掃描
  • 機密管理(不把密碼存在環境變數的明文)
  • 最小權限原則
  • 容器映像的安全掃描

為什麼需要這項原則:

傳統的邊界安全(防火牆、WAF)在 Cloud Native 環境中不夠用。零信任架構要求每個服務都要驗證和授權。

想深入了解 Cloud Native 安全?請參考 Cloud Native Security 完整指南


12 Factor 實踐範例

以下用一個 Node.js 應用程式示範如何遵循 12 Factor:

專案結構:

my-app/
├── src/
│   ├── index.js
│   ├── config.js
│   └── routes/
├── package.json
├── Dockerfile
├── docker-compose.yml
└── .env.example

config.js(第 3 原則:Config):

module.exports = {
  port: process.env.PORT || 3000,
  database: {
    url: process.env.DATABASE_URL,
  },
  redis: {
    url: process.env.REDIS_URL,
  },
  logLevel: process.env.LOG_LEVEL || 'info',
};

Dockerfile(第 2、5 原則:Dependencies、Build):

FROM node:20-alpine

WORKDIR /app

# 明確宣告依賴
COPY package*.json ./
RUN npm ci --only=production

COPY src/ ./src/

# 透過環境變數注入設定
ENV NODE_ENV=production

EXPOSE 3000
CMD ["node", "src/index.js"]

index.js(第 7、9、11 原則:Port Binding、Disposability、Logs):

const express = require('express');
const config = require('./config');

const app = express();

// 第 7 原則:Port Binding
const server = app.listen(config.port, () => {
  // 第 11 原則:Logs 到 stdout
  console.log(JSON.stringify({
    level: 'info',
    message: `Server started on port ${config.port}`,
    timestamp: new Date().toISOString()
  }));
});

// 第 9 原則:Disposability - 優雅關閉
process.on('SIGTERM', () => {
  console.log(JSON.stringify({
    level: 'info',
    message: 'SIGTERM received, shutting down gracefully'
  }));

  server.close(() => {
    console.log(JSON.stringify({
      level: 'info',
      message: 'Server closed'
    }));
    process.exit(0);
  });
});

Spring Boot 如何實踐 12 Factor?請參考 Cloud Native Java 開發指南


12 Factor 快速參考表

#原則一句話檢查項目
1Codebase一份程式碼,多個部署程式碼在 Git?只有一份?
2Dependencies明確宣告依賴package.json 完整?
3Config設定在環境變數沒有 hardcode 的密碼?
4Backing Services服務可抽換換資料庫要改程式碼嗎?
5Build, Release, Run階段分離有 CI/CD?有版本號?
6Processes無狀態Session 存外部?
7Port Binding自己綁 Port不依賴 Apache/Nginx?
8Concurrency水平擴展可以跑多個實例?
9Disposability快啟動、優雅關閉處理 SIGTERM?
10Dev/Prod Parity環境一致開發用 Docker?
11Logs寫 stdout沒有寫檔案?
12Admin Processes一次性任務遷移腳本在版控?

FAQ

Q1: 12 Factor 是強制規定還是建議?

12 Factor 是建議性的最佳實踐,不是強制規定。但如果你想在 Kubernetes 等 Cloud Native 環境順利運行應用程式,遵循這些原則會讓事情簡單很多。

Q2: 一定要全部遵循才算合格嗎?

不一定。根據你的應用程式需求,某些原則可能不適用。但核心的幾項(Config、Stateless Processes、Logs)幾乎是 Cloud Native 的必要條件。

Q3: 12 Factor 和微服務有什麼關係?

12 Factor 最初是為單體式 SaaS 應用設計的,但它的原則同樣適用於微服務。事實上,微服務架構更需要遵循這些原則,因為服務數量更多、環境更複雜。

Q4: 我的遺留系統能套用 12 Factor 嗎?

可以逐步導入。通常從設定外部化(第 3 原則)和日誌標準化(第 11 原則)開始最容易,然後再處理無狀態和依賴管理。

Q5: 15 Factor 是官方標準嗎?

不是。15 Factor 是社群根據 Cloud Native 發展補充的建議,沒有官方認證。但 API First、Telemetry、Security 這三項確實是現代雲端應用的重要考量。


下一步

12 Factor 是 Cloud Native 架構的基礎。掌握這些原則後,你可以進一步探索:

架構設計需要第二意見?12 Factor 看起來簡單,但要在既有系統中落實並不容易。預約架構諮詢,讓有經驗的專家幫你評估現況,規劃改善路徑。


參考資料

需要專業的雲端建議?

無論您正在評估雲平台、優化現有架構,或尋找節費方案,我們都能提供協助

預約免費諮詢

相關文章