Workshop สำหรับเรียนรู้หลักการ The Twelve-Factor App ผ่านการออกแบบและปรับปรุง REST API ให้เหมาะกับการ Deploy บน Cloud และ Container

#เป้าหมายของ Workshop

หลังจบ Workshop นี้ ผู้เรียนจะสามารถ:

  • อธิบายหลักการ Twelve-Factor App ทั้ง 12 ข้อได้
  • แยก Configuration และ Secret ออกจาก Source Code
  • จัดการ Dependencies อย่างชัดเจน
  • ออกแบบ Application ให้เป็น Stateless
  • เชื่อมต่อ Database ในฐานะ Backing Service
  • แยกขั้นตอน Build, Release และ Run
  • ส่ง Logs ผ่าน stdout / stderr
  • Containerize Application ด้วย Docker
  • เชื่อมโยง Twelve-Factor กับ Kubernetes และ CI/CD ได้

#1. Twelve-Factor App คืออะไร?

The Twelve-Factor App คือ methodology สำหรับออกแบบ Web Application และ Software-as-a-Service (SaaS) ให้สามารถ Deploy, Scale และดูแลรักษาได้ง่าย โดยเฉพาะระบบที่ทำงานบน Cloud

หลักการประกอบด้วย 12 Factors:

# Factor แนวคิด


1 Codebase หนึ่ง Codebase อยู่ใน Version Control 2 Dependencies ประกาศ Dependencies อย่างชัดเจน 3 Config เก็บ Configuration นอก Source Code 4 Backing Services มอง DB/Cache/Queue เป็น Resource 5 Build, Release, Run แยก lifecycle ออกจากกัน 6 Processes Process ต้อง Stateless 7 Port Binding Application ให้บริการผ่าน Port 8 Concurrency Scale ด้วย Process/Instance 9 Disposability Start เร็วและ Shutdown ได้อย่างเหมาะสม 10 Dev/Prod Parity Environment ควรใกล้เคียงกัน 11 Logs มอง Log เป็น Event Stream 12 Admin Processes งาน Admin เป็น One-off Process


#Workshop: Twelve-Factor REST API

ใน Workshop นี้จะสร้าง REST API ตัวอย่างด้วย Node.js + Express + PostgreSQL + Docker Compose

โครงสร้างระบบ:

Client
  |
  v
REST API :3000
  |
  v
PostgreSQL :5432

#2. เตรียม Project

mkdir twelve-factor-workshop
cd twelve-factor-workshop

npm init -y
npm install express pg

สร้างโครงสร้าง:

twelve-factor-workshop/
├── src/
│   └── app.js
├── package.json
├── Dockerfile
├── compose.yaml
└── .gitignore

เพิ่ม .gitignore

node_modules/
.env

#3. Factor I --- Codebase

หลักการ:

One codebase tracked in revision control, many deploys.

นำ Project เข้า Git:

git init
git add .
git commit -m "Initial Twelve-Factor application"

Codebase เดียวสามารถนำไปใช้ได้หลาย Environment:

Git Repository
     |
     +---- Development
     +---- Staging
     +---- Production

ไม่ควรสร้าง Source Code แยกชุด เช่น app-dev, app-test, app-production


#4. Factor II --- Dependencies

Application ต้องประกาศ Dependency อย่างชัดเจน

สำหรับ Node.js ใช้:

package.json
package-lock.json

ตรวจสอบ:

npm list --depth=0

ตัวอย่าง:

{
  "dependencies": {
    "express": "...",
    "pg": "..."
  }
}

เมื่อ Clone Project ไปยังเครื่องใหม่:

npm ci

Application จึงไม่ควรพึ่ง Library ที่ติดตั้งแบบ global โดยไม่ประกาศไว้ใน Project


#5. Factor III --- Config

ไม่ควร Hard-code Configuration:

const password = "123456";

ให้ใช้ Environment Variables:

const dbConfig = {
  host: process.env.DB_HOST,
  port: process.env.DB_PORT || 5432,
  database: process.env.DB_NAME,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD
};

แนวคิดสำคัญคือ:

Source Code != Configuration

Environment ต่างกันจึงใช้ Code เดียวกันได้:

Development  -> DB_HOST=localhost
Staging      -> DB_HOST=staging-db
Production   -> DB_HOST=production-db

.env เหมาะสำหรับ local development แต่ Secret จริงใน Production ควรจัดการผ่าน secret-management mechanism ของ platform


#6. Factor IV --- Backing Services

Database ควรถูกมองเป็น Resource ที่เชื่อมเข้ามาผ่าน Configuration

สร้าง src/app.js

const express = require("express");
const { Pool } = require("pg");

const app = express();
app.use(express.json());

const pool = new Pool({
  host: process.env.DB_HOST,
  port: Number(process.env.DB_PORT || 5432),
  database: process.env.DB_NAME,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD
});

app.get("/", (req, res) => {
  res.json({
    message: "Twelve-Factor App Workshop"
  });
});

app.get("/health", async (req, res) => {
  try {
    await pool.query("SELECT 1");
    res.json({ status: "UP" });
  } catch (error) {
    console.error(error);
    res.status(503).json({ status: "DOWN" });
  }
});

const port = Number(process.env.PORT || 3000);

const server = app.listen(port, "0.0.0.0", () => {
  console.log(`Application listening on port ${port}`);
});

Application ไม่จำเป็นต้องรู้ว่า PostgreSQL ทำงานบนเครื่องเดียวกัน, Container อื่น หรือ Managed Database


#7. Factor V --- Build, Release, Run

Lifecycle ควรถูกแยกเป็น:

Source
  |
  v
BUILD
  |
  v
Artifact / Docker Image
  |
  + Configuration
  |
  v
RELEASE
  |
  v
RUN

สร้าง Dockerfile

FROM node:24-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --omit=dev

COPY src ./src

ENV NODE_ENV=production

EXPOSE 3000

CMD ["node", "src/app.js"]

Build:

docker build -t twelve-factor-api:1.0 .

Run:

docker run --rm \
  -p 3000:3000 \
  -e PORT=3000 \
  twelve-factor-api:1.0

ใน CI/CD ควร Build artifact ครั้งเดียว แล้ว promote artifact เดิมไปยัง environment ต่าง ๆ พร้อม configuration ที่เหมาะสม


#8. Factor VI --- Processes

Application Process ควรเป็น Stateless

ไม่ควรเก็บข้อมูลสำคัญไว้ใน:

RAM ของ Instance
Local filesystem
Local session

ควรเก็บ State ไว้ใน Backing Services:

API Instance 1 ---+
API Instance 2 ---+---> PostgreSQL / Redis / Object Storage
API Instance 3 ---+

ผลคือ Instance ใดหยุดทำงาน ระบบสามารถสร้าง Instance ใหม่ได้โดยไม่สูญเสีย Application State ที่จำเป็น


#9. Factor VII --- Port Binding

Application ให้บริการผ่าน Port ที่กำหนดจาก Configuration:

const port = Number(process.env.PORT || 3000);

จากนั้น expose ผ่าน:

http://localhost:3000

ทดสอบ:

curl http://localhost:3000/

Expected response:

{
  "message": "Twelve-Factor App Workshop"
}

#10. Factor VIII --- Concurrency

แทนที่จะผูกระบบกับ Process เดียว ควรออกแบบให้สามารถเพิ่ม Instance ได้:

              +--> API #1
Load Balancer +--> API #2
              +--> API #3

บน Kubernetes ตัวอย่างเช่น:

spec:
  replicas: 3

เมื่อใช้ Horizontal Pod Autoscaler จำนวน Pod สามารถเพิ่ม/ลดตาม workload ได้


#11. Factor IX --- Disposability

Application ต้องเริ่มได้เร็วและรองรับ Graceful Shutdown

เพิ่มใน src/app.js:

async function shutdown(signal) {
  console.log(`${signal} received`);

  server.close(async () => {
    try {
      await pool.end();
      console.log("Application stopped");
      process.exit(0);
    } catch (error) {
      console.error(error);
      process.exit(1);
    }
  });
}

process.on("SIGTERM", () => shutdown("SIGTERM"));
process.on("SIGINT", () => shutdown("SIGINT"));

สิ่งนี้สำคัญกับ:

  • Rolling Deployment
  • Auto Scaling
  • Container restart
  • Kubernetes Pod termination

#12. Factor X --- Dev/Prod Parity

Development และ Production ควรใกล้เคียงกัน

แทนที่จะใช้:

Development -> SQLite
Production  -> PostgreSQL

Workshop นี้ใช้ PostgreSQL ผ่าน Container เพื่อให้ Development ใกล้ Production มากขึ้น

สร้าง compose.yaml

services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      PORT: 3000
      DB_HOST: db
      DB_PORT: 5432
      DB_NAME: workshop
      DB_USER: workshop
      DB_PASSWORD: workshop_password
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:18-alpine
    environment:
      POSTGRES_DB: workshop
      POSTGRES_USER: workshop
      POSTGRES_PASSWORD: workshop_password
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U workshop -d workshop"]
      interval: 5s
      timeout: 5s
      retries: 10
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

ค่า credential ในตัวอย่างมีไว้สำหรับ Workshop เท่านั้น ไม่ควรใช้ค่าแบบนี้ใน Production

Run:

docker compose up --build

ทดสอบ:

curl http://localhost:3000/health

#13. Factor XI --- Logs

Application ไม่ควรผูกตัวเองกับไฟล์ Log เฉพาะ เช่น:

/var/log/myapp.log

ให้ส่ง Log ออก stdout / stderr:

console.log("Application started");
console.error("Database connection failed");

ดู Log ของ Container:

docker compose logs -f api

ใน Production สามารถส่งต่อ Event Stream ไปยังระบบ เช่น:

Application
    |
stdout / stderr
    |
Container Runtime
    |
    +--> Loki
    +--> Elasticsearch
    +--> Cloud Logging

#14. Factor XII --- Admin Processes

งาน Administration ควรทำเป็น One-off Process โดยใช้ code/configuration ชุดเดียวกับ Application

ตัวอย่างเช่น:

docker compose exec api node scripts/migrate.js

หรือใน Framework อื่น:

php artisan migrate
python manage.py migrate

บน Kubernetes สามารถใช้ Job สำหรับ Database Migration หรือ administrative task ที่ต้องทำครั้งเดียว


#15. ทดสอบระบบ

เริ่มระบบ:

docker compose up --build -d

ดู Container:

docker compose ps

ทดสอบ API:

curl http://localhost:3000/
curl http://localhost:3000/health

ดู Logs:

docker compose logs -f api

หยุดระบบ:

docker compose down

หากต้องการลบ Volume ด้วย:

docker compose down -v

#16. Twelve-Factor กับ Kubernetes

สามารถ Mapping หลักการกับ Kubernetes ได้โดยประมาณ:

Twelve-Factor Kubernetes / Cloud-Native


Codebase Git Repository Dependencies Container Image Config ConfigMap / Secret Backing Services Service / Managed DB Build, Release, Run CI/CD + Registry + Deployment Processes Pods Port Binding Container Port / Service Concurrency Replica / HPA Disposability Pod lifecycle / Graceful shutdown Dev/Prod Parity Containers / IaC Logs stdout / stderr Admin Processes Job

Architecture:

Developer
    |
    v
Git Repository
    |
    v
CI Pipeline
    |
    v
Container Image
    |
    v
Container Registry
    |
    v
Kubernetes
    |
    +------> Pod #1 ----+
    +------> Pod #2 ----+----> PostgreSQL
    +------> Pod #3 ----+
              |
              v
        stdout/stderr
              |
              v
       Logging Platform

#17. แบบฝึกหัด

ให้ผู้เรียนปรับปรุง Workshop ต่อไปนี้:

  1. เพิ่ม GET /users
  2. สร้างตาราง users ใน PostgreSQL
  3. เพิ่ม POST /users
  4. ห้าม Hard-code Database Configuration ใน Source Code
  5. เพิ่ม Health Check
  6. เพิ่ม Graceful Shutdown
  7. เพิ่ม Docker Compose
  8. ทดลอง Scale API หลาย Instance
  9. ตรวจสอบ Logs จาก Container
  10. อธิบายว่าแต่ละส่วนสอดคล้องกับ Factor ใด

#18. Checklist สำหรับตรวจ Application

ใช้ Checklist นี้กับ Project จริง:

[ ] Source Code อยู่ใน Version Control
[ ] Dependencies ถูกประกาศอย่างชัดเจน
[ ] ไม่มี Secret ฝังอยู่ใน Source Code
[ ] Config เปลี่ยนตาม Environment ได้
[ ] Database/Cache/Queue ถูกมองเป็น Backing Service
[ ] Build, Release และ Run แยกจากกัน
[ ] Application Process เป็น Stateless
[ ] Service เปิดใช้งานผ่าน Port
[ ] สามารถเพิ่ม Instance เพื่อ Scale ได้
[ ] Application รองรับ Graceful Shutdown
[ ] Development และ Production ใกล้เคียงกัน
[ ] Logs ส่งออกเป็น Event Stream
[ ] Admin Task สามารถ Run เป็น One-off Process

#19. สรุป

หัวใจของ Twelve-Factor App ไม่ใช่การบังคับให้ใช้ Docker หรือ Kubernetes แต่คือการออกแบบ Application ให้ portable, configurable, stateless, observable และ deployable

เมื่อนำหลักการมาใช้ร่วมกับเทคโนโลยีสมัยใหม่จะเห็นภาพดังนี้:

Twelve-Factor
      |
      +--> Git
      +--> Environment Configuration
      +--> Docker
      +--> CI/CD
      +--> Kubernetes
      +--> Managed Services
      +--> Centralized Logging
      |
      v
Cloud-Native Application

การเข้าใจ Twelve-Factor จึงเป็นพื้นฐานที่ดีสำหรับการศึกษาต่อในหัวข้อ DevOps, Microservices, Docker, Kubernetes, GitOps, CI/CD, SRE และ Platform Engineering


#เอกสารอ้างอิง