เป้าหมาย: เรียนรู้แนวคิดการจำลองสภาพแวดล้อมระดับระบบปฏิบัติการ (OS-level virtualization) พร้อมลงมือสร้างแอปพลิเคชันที่ประกอบด้วย Flask API และ Redis ให้ทำงานแยกกันคนละ Container แต่สื่อสารผ่าน Docker Network และเก็บข้อมูลแบบถาวรด้วย Named Volume

Container ไม่ใช่เพียงวิธีเปิดโปรแกรมบนเครื่องอื่น แต่เป็นกลไกสำคัญของ Software Delivery ยุค DevOps และ Cloud Native ที่ช่วยให้การสร้างแอป (Build) การส่งมอบ (Ship) และการรัน (Run) มีสภาพแวดล้อมใกล้เคียงกันตั้งแต่เครื่องนักพัฒนาจนถึงเซิร์ฟเวอร์

บทความนี้ออกแบบเป็น Web Blog + Hands-on Workshop สำหรับผู้เรียน Software Engineering ที่รู้จักการใช้ Terminal เบื้องต้น โดยไม่จำเป็นต้องมีประสบการณ์ Docker มาก่อน

สารบัญ

  1. แนวคิด Container Virtualization และ VM
  2. สถาปัตยกรรมและองค์ประกอบที่เกี่ยวข้อง
  3. เครื่องมือและการเตรียมสภาพแวดล้อม
  4. Lab 1: รัน Container แรกด้วย Nginx
  5. Lab 2: สร้าง Container Image สำหรับ Flask API
  6. Lab 3: เชื่อม Flask และ Redis ด้วย Docker Compose
  7. Lab 4: ตรวจสอบ Isolation, Network, Volume และ Resources
  8. Lab 5: Health Check และจำลอง Service Failure
  9. Best Practices, Troubleshooting และงานฝึกเพิ่มเติม

info-workshop-container-virtualization-docker-docker-compose

#1. Container Virtualization คืออะไร?

Container Virtualization เป็นวิธีแยก Process และสภาพแวดล้อมการทำงานในระดับระบบปฏิบัติการ โดย Container ที่รันอยู่บน Linux Kernel เดียวกันจะใช้ Kernel ร่วมกัน แต่มีมุมมองของ Process, Network และ File System ที่ถูกแยกตามการกำหนดค่า

กลไกสำคัญ ได้แก่

  • Linux Namespaces: แยกสิ่งที่ Process มองเห็น เช่น PID, Network, Mount, IPC, UTS และ User Namespace
  • Control Groups (cgroups): ควบคุมและติดตามทรัพยากร เช่น CPU, Memory และจำนวน Process
  • Layered Image และ Writable Layer: นำ Image มาใช้สร้าง Container โดยแยกชั้นข้อมูลที่เขียนระหว่างรันออกจากชั้น Image
  • Container Runtime: จัดเตรียมและจัดการ Process ภายใน Container ตามสเปกที่เกี่ยวข้อง เช่น OCI

#Container กับ Virtual Machine ต่างกันอย่างไร?

ประเด็น Virtual Machine (VM) Linux Container
ระดับการจำลอง Hardware Virtualization OS-level Virtualization
Kernel แต่ละ VM มี Guest OS/Kernel Container ใช้ Kernel ของ Linux Host ร่วมกัน
ขนาดโดยทั่วไป มักใหญ่กว่า เพราะรวม Guest OS มักเล็กกว่าเมื่อเทียบ Workload ใกล้เคียงกัน
การเริ่มทำงาน โดยทั่วไปใช้เวลามากกว่า โดยทั่วไปเริ่มได้รวดเร็วกว่า
การแยกสภาพแวดล้อม มีขอบเขต Guest Kernel แยกกัน พึ่งพา Kernel เดียวและมาตรการ Isolation เพิ่มเติม
การใช้งาน OS หลายประเภท, Isolation เข้มข้น Web API, Microservices, CI/CD, Batch Jobs

ข้อควรระวัง: Container ไม่ใช่ VM ขนาดเล็ก และไม่ใช่ Security Boundary ที่สมบูรณ์แบบ การแชร์ Kernel ทำให้ต้องระวังสิทธิ์ที่มอบให้ Container เป็นพิเศษ

#ภาพเปรียบเทียบสถาปัตยกรรม

flowchart TB
    subgraph VM[Virtual Machines]
        direction TB
        H1[Host Hardware] --> HV[Hypervisor]
        HV --> V1[VM 1: Guest OS + App]
        HV --> V2[VM 2: Guest OS + App]
    end
    subgraph CT[Linux Containers]
        direction TB
        H2[Host Hardware] --> K[Linux Kernel]
        K --> R[Container Runtime]
        R --> C1[Container A: App + Dependencies]
        R --> C2[Container B: App + Dependencies]
    end

หมายเหตุสำหรับ Windows และ macOS: เมื่อใช้ Docker Desktop เพื่อรัน Linux Containers จะมี Linux VM หรือสภาพแวดล้อม Linux Kernel รองรับอยู่เบื้องหลัง Container ใช้ Kernel ของ Linux VM นั้น ไม่ได้ใช้ Windows หรือ macOS Kernel โดยตรง สำหรับ Windows ที่ใช้ WSL 2 ให้มอง WSL 2 เป็นตัวจัดหา Linux Kernel ให้ Workload เหล่านี้


#2. สถาปัตยกรรมการทำงานของ Docker

องค์ประกอบหลักประกอบด้วย Docker CLI (รับคำสั่ง), Docker Engine / Daemon (จัดการ Image, Network และ Container), containerd และ OCI Runtime ในสถาปัตยกรรมการรัน และ Registry (เก็บ Image)

flowchart LR
    Dev[Developer / Terminal] --> CLI[Docker CLI]
    CLI --> Engine[Docker Engine]
    Engine --> Image[Container Image]
    Engine --> RT[Container Runtime]
    RT --> C[Running Container]
    Reg[(Image Registry)] <--> Engine

คำศัพท์ที่พบบ่อย:

คำศัพท์ ความหมาย
Dockerfile คำสั่งอธิบายวิธี Build Image
Image Artifact ที่นำกลับมาใช้สร้าง Container ได้
Container อินสแตนซ์ที่สร้างจาก Image และมี Process ทำงาน
Registry แหล่งเก็บและแจกจ่าย Image
Docker Network ช่องทางสื่อสารระหว่าง Container หรือกับภายนอก
Volume พื้นที่เก็บข้อมูลที่คงอยู่ได้แม้ลบ Container
Docker Compose เครื่องมือกำหนดและจัดการหลาย Service ผ่านไฟล์ YAML

#สถาปัตยกรรมที่จะได้จาก Workshop นี้

flowchart LR
    User[Browser / curl] -->|localhost:8000| WEB[Flask API Container]
    WEB -->|redis:6379 ใน app_net| REDIS[(Redis Container)]
    REDIS -->|/data| VOL[(Named Volume: redis_data)]
    subgraph DockerHost[Docker Host / Linux VM]
        WEB
        REDIS
        VOL
    end

หลักการออกแบบ: เปิดเผยพอร์ต 8000 ของ Web API ให้เครื่องผู้เรียนผ่าน 127.0.0.1 เท่านั้น ส่วน Redis ไม่มีการ Publish Port ไปยัง Host แต่ให้ Web API ติดต่อด้วย DNS ชื่อ Service redis ภายใน Docker Network


#3. เตรียมเครื่องมือสำหรับ Workshop

สิ่งที่ต้องมี

  • เครื่องคอมพิวเตอร์ Windows 10/11, macOS หรือ Linux ที่รองรับการใช้งาน Docker
  • Docker Desktop หรือ Docker Engine ที่ติดตั้ง Docker Compose Plugin
  • Terminal: PowerShell, Windows Terminal, Bash หรือ zsh
  • โปรแกรมแก้ไขโค้ด เช่น Visual Studio Code
  • อินเทอร์เน็ตในการดึง Base Images และ Python Packages สำหรับ Build ครั้งแรก
  • พอร์ต 8000 และ 8088 ที่ยังไม่ถูกใช้งาน

#3.1 ตรวจสอบการติดตั้ง

docker --version
docker compose version
docker info
docker run --rm hello-world

ผลลัพธ์ที่คาดหวัง: คำสั่งทำงานได้ และ hello-world แสดงข้อความยืนยันว่า Docker Client ติดต่อ Docker Daemon ได้สำเร็จ

สำหรับ Windows + WSL 2 ให้เปิด Docker Desktop และเลือก Linux Container Backend ตามการตั้งค่าที่รองรับ จากนั้นเรียกคำสั่งใน PowerShell หรือ WSL 2 ที่เชื่อมต่อกับ Docker Desktop ได้

ใน Windows PowerShell หากคำสั่ง curl ไม่เรียกโปรแกรม cURL CLI ให้ใช้ curl.exe แทนคำสั่ง curl ในบทความนี้

#3.2 ตรวจสอบรายละเอียด Engine

docker version
docker context ls
docker system df

ค่า Docker Context ช่วยบอกว่า CLI กำลังติดต่อ Docker Engine ตัวใด ส่วน docker system df แสดงสรุปพื้นที่ของ Image, Container และ Volume


#4. Lab 1 — รัน Container แรกด้วย Nginx

วัตถุประสงค์: เข้าใจวงจร pull → run → inspect → stop → remove และการ Publish Port

#Step 1: ดาวน์โหลด Image

docker pull nginx:stable-alpine
docker images

#Step 2: สร้างและรัน Nginx Container

docker run -d \
  --name seai-nginx \
  -p 127.0.0.1:8088:80 \
  --memory=128m \
  --cpus=0.5 \
  nginx:stable-alpine

คำสั่งด้านบนใช้เครื่องหมาย \ สำหรับขึ้นบรรทัดใหม่ใน Bash/zsh หากใช้ Windows PowerShell ให้เขียนเป็นบรรทัดเดียวตามตัวอย่างต่อไปนี้:

docker run -d --name seai-nginx -p 127.0.0.1:8088:80 --memory=128m --cpus=0.5 nginx:stable-alpine

พารามิเตอร์สำคัญ:

  • -d รันแบบ Detached ใน Background
  • --name ตั้งชื่อ Container
  • -p 127.0.0.1:8088:80 ผูก Host Port 8088 เข้ากับ Container Port 80 โดยรับจาก Loopback ของ Host
  • --memory=128m จำกัดหน่วยความจำของ Container
  • --cpus=0.5 จำกัดการใช้ CPU เทียบเท่าประมาณครึ่งหนึ่งของหนึ่ง Logical CPU

#Step 3: ทดสอบและตรวจสอบ

เปิด http://localhost:8088 ในเว็บเบราว์เซอร์ หรือใช้คำสั่ง:

curl -i http://localhost:8088/
docker ps
docker logs seai-nginx
docker inspect seai-nginx
docker stats --no-stream seai-nginx

สิ่งที่ควรสังเกต: Browser แสดงหน้าเริ่มต้น Nginx, docker ps แสดงสถานะ Container และ docker stats แสดงการใช้ทรัพยากร

#Step 4: ลบ Container ทดสอบ

docker stop seai-nginx
docker rm seai-nginx

การลบ Container ไม่ได้ลบ Image ที่ใช้สร้าง หากต้องการเริ่มใหม่สามารถเรียก docker run จาก Image เดิมได้


#5. Lab 2 — สร้าง Docker Image สำหรับ Flask API

วัตถุประสงค์: เข้าใจ Dockerfile, Dependency Isolation, Image Build และการรันด้วย Non-root User

#Step 1: สร้างโครงสร้างโปรเจกต์

สร้างโฟลเดอร์ชื่อ container-virtualization-workshop และไฟล์ตามนี้:

container-virtualization-workshop/
├── app.py
├── requirements.txt
├── Dockerfile
├── .dockerignore
└── compose.yaml

สามารถนำไฟล์ทั้งชุดจาก ZIP Source Code ที่แนบกับบทความไปใช้แทนการคัดลอกโค้ดทีละไฟล์ได้

#Step 2: สร้าง app.py

API นี้มี 2 Endpoint ได้แก่ / สำหรับแสดงจำนวนครั้งที่เรียก API ซึ่งนับผ่าน Redis และ /health สำหรับตรวจสอบ Redis Connectivity

"""Sample Flask application for the Container Virtualization workshop."""
import os
import socket

from flask import Flask, jsonify
from redis import Redis
from redis.exceptions import RedisError

app = Flask(__name__)
cache = Redis(
    host=os.getenv("REDIS_HOST", "redis"),
    port=int(os.getenv("REDIS_PORT", "6379")),
    db=0,
    decode_responses=True,
    socket_connect_timeout=2,
    socket_timeout=2,
)


@app.get("/")
def index():
    try:
        visits = cache.incr("visits")
        return jsonify(
            message="Hello from Container Virtualization Workshop",
            visits=visits,
            hostname=socket.gethostname(),
        )
    except RedisError:
        return jsonify(error="Redis service unavailable"), 503


@app.get("/health")
def health():
    try:
        cache.ping()
        return jsonify(status="ok", redis="up"), 200
    except RedisError:
        return jsonify(status="unhealthy", redis="down"), 503

เหตุผลที่ใช้ Redis: หากเก็บตัวนับไว้ในตัวแปร Python ของ Flask จำนวนครั้งจะไม่ถูกแชร์ระหว่างหลาย Worker หรือหลาย Container แต่ INCR ของ Redis เป็นการเพิ่มค่าแบบ Atomic จึงเป็นตัวอย่างที่ดีสำหรับการสื่อสารระหว่าง Service

#Step 3: สร้าง requirements.txt

Flask==3.1.1
redis==6.2.0
gunicorn==23.0.0

เวอร์ชันที่กำหนดเป็นชุดสำหรับ Workshop เพื่อความทำซ้ำได้ในระดับหนึ่ง ไม่ใช่การยืนยันว่าเป็นเวอร์ชันล่าสุด การ Deploy จริงควรทบทวนช่องโหว่, อัปเดต Dependency และใช้ Lockfile/Hashes หรือกระบวนการควบคุม Supply Chain ตามมาตรฐานทีม

#Step 4: สร้าง Dockerfile

FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

RUN groupadd --gid 10001 appgroup \
    && useradd --uid 10001 --gid appgroup --create-home appuser

COPY --chown=appuser:appgroup app.py ./
USER appuser

EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "2", "app:app"]

อธิบาย Dockerfile:

  1. FROM python:3.12-slim กำหนด Base Image สำหรับรัน Python บน Linux
  2. WORKDIR /app กำหนด Working Directory
  3. COPY requirements.txt ตามด้วย pip install ทำให้ใช้ Docker Build Cache ได้ดีเมื่อ Source Code เปลี่ยนแต่ Dependencies ไม่เปลี่ยน
  4. USER appuser ลดความเสี่ยงจากการรันแอปด้วยสิทธิ์ Root
  5. EXPOSE 8000 เป็น Metadata เพื่อสื่อถึง Port ภายใน ไม่ได้ Publish Port ออก Host เอง
  6. CMD ... gunicorn รัน Web Server โดย Bind ไปยัง 0.0.0.0:8000 ภายใน Container เพื่อรองรับการเชื่อมต่อจาก Docker Network

#Step 5: สร้าง .dockerignore

.git
.venv
__pycache__
*.pyc
.env
*.env
*.md
compose.yaml

ไฟล์นี้ช่วยตัด .git, Virtual Environment และไฟล์ Secrets ออกจาก Docker Build Context อย่าเก็บ Password หรือ API Key ลง Docker Image

#Step 6: ทดลอง Build Image

docker build -t seai/container-workshop:1.0 .
docker images

หมายเหตุ: ในขั้นนี้ Image ของ Flask สร้างสำเร็จแล้ว แต่ API ต้องใช้ Redis จึงยังไม่เหมาะที่จะเปิด API แบบเดี่ยว ๆ โดยไม่มี Redis; เราจะรันทั้งคู่พร้อมกันใน Lab 3


#6. Lab 3 — เชื่อม Flask API กับ Redis ด้วย Docker Compose

วัตถุประสงค์: เรียนรู้การประกาศหลาย Service, Docker DNS, Named Volume และ Health Check

#Step 1: สร้าง compose.yaml

services:
  web:
    build: .
    image: seai/container-workshop:1.0
    ports:
      - "127.0.0.1:8000:8000"
    environment:
      REDIS_HOST: redis
      REDIS_PORT: "6379"
    depends_on:
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3)"]
      interval: 10s
      timeout: 4s
      retries: 3
      start_period: 15s
    mem_limit: 256m
    cpus: 1.0
    security_opt:
      - no-new-privileges:true
    networks:
      - app_net

  redis:
    image: redis:7-alpine
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 10
      start_period: 5s
    mem_limit: 128m
    networks:
      - app_net

volumes:
  redis_data:

networks:
  app_net:
    driver: bridge

อธิบายส่วนสำคัญ:

Configuration ความหมาย
services.web กำหนด Container สำหรับ Flask API
services.redis กำหนด Redis Server
build: . Build Web Image จาก Dockerfile ในโฟลเดอร์ปัจจุบัน
ports: "127.0.0.1:8000:8000" เปิด Web API เฉพาะ Loopback ของ Host ใน Workshop
environment.REDIS_HOST: redis ใช้ Service Name เป็น DNS ภายใน Docker Network
depends_on + service_healthy ให้ Redis ผ่าน Health Check ก่อนเริ่ม Web
healthcheck ตรวจสอบความพร้อมในการให้บริการ
volumes: redis_data:/data เก็บข้อมูล Redis แยกจาก Writable Layer ของ Container
mem_limit / cpus กำหนด Resource Limits
networks.app_net ให้ทั้งสอง Service ใช้ Network เดียวกัน

ประเด็นสำคัญ: depends_on ช่วยเรื่องลำดับการเริ่มระบบ แต่ไม่ใช่กลไก Recovery ครบวงจร หาก Redis ล่มหลังจาก Web เริ่มทำงาน แอปต้องจัดการ Error/Retry เอง ซึ่งตัวอย่างนี้ตอบ HTTP 503 เมื่อ Redis ติดต่อไม่ได้

#Step 2: ตรวจสอบ Compose Configuration

docker compose config --quiet

หากไม่มี Error ให้รัน:

docker compose up --build -d
docker compose ps

#Step 3: ทดสอบ API

curl http://localhost:8000/
curl http://localhost:8000/
curl http://localhost:8000/health

ตัวอย่าง Response จาก / หลังการเรียกครั้งแรก:

{
  "hostname": "<container-id>",
  "message": "Hello from Container Virtualization Workshop",
  "visits": 1
}

เมื่อเรียกซ้ำ ค่า visits ควรเพิ่มเป็น 2, 3 ตามลำดับ ในสภาพแวดล้อมที่ยังไม่เคยสร้างข้อมูลมาก่อน ส่วน hostname จะเปลี่ยนไปตาม Container ที่รันจริง

Endpoint /health ควรได้ Response ลักษณะนี้:

{
  "redis": "up",
  "status": "ok"
}

#Step 4: ตรวจสอบ Logs และค่าที่ Redis

docker compose logs --tail=50 web
docker compose logs --tail=50 redis
docker compose exec redis redis-cli GET visits

คำสั่ง redis-cli GET visits จะอ่านค่าจาก Redis จริง ไม่ใช่ตัวแปรใน Python Process


#7. Lab 4 — ทดลอง Isolation, Network, Volume และ Resource Limits

#7.1 ตรวจสอบ Container Isolation

docker compose exec web id
docker compose exec web hostname
docker compose exec web python -c "import os; print('PID:', os.getpid())"

ผลลัพธ์จาก id ควรแสดง User ที่ไม่ใช่ Root (uid=10001) ขณะที่ hostname เป็น Hostname ภายใน Container และ PID ที่ Process เห็นอยู่ภายใต้ PID Namespace

#7.2 ตรวจสอบ Service Discovery ผ่าน Docker Network

docker compose exec web python -c "import socket; print(socket.gethostbyname('redis'))"

คำสั่งนี้ใช้ Docker DNS เพื่อแปลงชื่อ redis เป็น IP Address ภายใน Network ที่ Compose สร้างขึ้น โดยไม่จำเป็นต้องทราบ IP ล่วงหน้า

docker network ls
docker compose ps

อย่าสับสนระหว่าง Port ภายในกับ Port ที่ Publish: Flask ติดต่อ Redis ที่ redis:6379 ได้แม้ redis ไม่มี ports: เพราะเป็นการสื่อสารภายใน Network เดียวกัน

#7.3 ทดสอบ Named Volume Persistence

เริ่มจากดูค่าปัจจุบัน:

docker compose exec redis redis-cli GET visits

จากนั้นลบ Container และ Network ที่ Compose สร้าง แต่ ไม่ลบ Volume:

docker compose down
docker compose up -d
curl http://localhost:8000/

สิ่งที่ควรเกิดขึ้น: visits เพิ่มต่อจากค่าก่อน down แทนการกลับไปเริ่มที่ 1 เพราะ Redis เขียนข้อมูลลง Named Volume (redis_data) และเปิด Append Only File (AOF) สำหรับ Persistence ในตัวอย่างนี้

ตรวจสอบ Volume:

docker volume ls

ชื่อ Volume จริงอาจมี Prefix ตาม Compose Project เช่น container-virtualization-workshop_redis_data จึงไม่ควรสมมติว่าชื่อใน docker volume ls จะเป็น redis_data อย่างเดียว

#7.4 ดูการใช้ CPU และ Memory

docker stats --no-stream

เปรียบเทียบกับค่า mem_limit และ cpus ในไฟล์ Compose และลองอภิปรายว่าทำไม Memory ที่ใช้จริงอาจต่ำกว่า Memory Limit มาก


#8. Lab 5 — ทดลอง Health Check และ Service Failure

#Step 1: ตรวจสอบสถานะที่ทำงานปกติ

docker compose ps
curl -i http://localhost:8000/health

ในภาวะปกติ Endpoint ควรตอบ HTTP/1.1 200 OK

#Step 2: จำลอง Redis หยุดทำงาน

docker compose stop redis
curl -i http://localhost:8000/health
curl -i http://localhost:8000/

ผลลัพธ์ที่คาดหวัง: หลัง Redis หยุด Web API ยังรับ Request ได้ แต่ทั้ง /health และ / ตอบ HTTP 503 Service Unavailable เพราะไม่สามารถติดต่อ Redis

#Step 3: กู้คืน Redis

docker compose start redis
docker compose ps
curl -i http://localhost:8000/health

เมื่อ Redis กลับมาพร้อมใช้งาน /health จะตอบ 200 OK อีกครั้ง อาจใช้เวลาสั้น ๆ ระหว่างที่ Redis เริ่มทำงาน

คำถามอภิปราย: หากเปลี่ยนจาก Redis เป็น Database จริง เราควรมี Retry, Timeout, Circuit Breaker, Readiness Probe และการติดตาม Metrics เพิ่มเติมอย่างไร?


#9. Docker CLI Cheat Sheet

งานที่ต้องการ คำสั่ง
ดู Container ที่กำลังทำงาน docker ps
ดู Container ทั้งหมด docker ps -a
ดู Image docker images
สร้าง Image docker build -t demo:1.0 .
ดู Log docker logs <container-name>
เปิด Shell ใน Container docker compose exec web sh
ดูการใช้ทรัพยากร docker stats --no-stream
ตรวจสอบ Compose docker compose config --quiet
เปิด Stack docker compose up --build -d
ดู Stack docker compose ps
ดู Log แบบติดตาม docker compose logs -f
หยุดและลบ Stack แต่คง Volume docker compose down
ลบ Stack พร้อม Volume docker compose down -v

คำเตือน: docker compose down -v ลบข้อมูลใน Named Volume ของโปรเจกต์ การใช้คำสั่งนี้กับ Database จริงอาจทำให้ข้อมูลสูญหาย


#10. Security และ Production Best Practices

Workshop นี้เหมาะสำหรับการเรียนรู้บนเครื่องของผู้พัฒนา ไม่ใช่ Production Configuration ที่สมบูรณ์ การนำไปใช้งานจริงควรพิจารณาเพิ่มเติมดังนี้

  1. Least Privilege: ใช้ USER ที่ไม่ใช่ Root และตัด Linux Capabilities ที่ไม่จำเป็น
  2. Image Provenance: ใช้ Image จากแหล่งน่าเชื่อถือ ตรึง Version/Digest และสแกนช่องโหว่ก่อน Deploy
  3. Secrets Management: อย่าใส่ Credentials ใน Source Code, Dockerfile หรือ Image ให้ใช้ Secrets Manager หรือกลไกที่เหมาะสม
  4. Resource Governance: กำหนด CPU, Memory และ PIDs Limit ตามลักษณะ Workload
  5. Network Isolation: เปิด Publish Port เฉพาะที่จำเป็น และใช้ TLS เมื่อมีการเชื่อมต่อผ่านเครือข่าย
  6. Persistent Storage: สำรองข้อมูล Database และทดสอบการกู้คืนเป็นประจำ
  7. Observability: มี Logs, Metrics, Tracing และ Alerting ที่จำเป็น
  8. Patch Management: Build Image ใหม่เมื่อ Base Image หรือ Library อัปเดตด้านความปลอดภัย
  9. Runtime Hardening: พิจารณา Read-only Root Filesystem, Seccomp, AppArmor/SELinux, Rootless Mode หรือ User Namespace ตามความเหมาะสม
  10. Redis Security: Redis ในตัวอย่างไม่มี Authentication แต่ไม่ได้เปิด Port ไป Host จึงเหมาะเฉพาะ Lab ที่เชื่อถือได้; Production ควรใช้ Authentication/ACL, Network Policy และการเข้ารหัสตามข้อกำหนด

ข้อควรเข้าใจเรื่อง Port: หากกำหนด "8000:8000" โดยไม่ระบุ Host IP การ Publish อาจรับการเชื่อมต่อจากทุก Interface ของ Host ต่างจาก "127.0.0.1:8000:8000" ที่ใช้ใน Workshop

Production Note: หากต้องการ Scale Web API หลาย Replica ใน Compose ไม่ควร Bind Host Port 8000 แบบตายตัวให้ทุก Replica; ใช้ Reverse Proxy/Load Balancer และออกแบบ Port Mapping ใหม่


#11. Troubleshooting

อาการ สาเหตุที่พบบ่อย แนวทางตรวจสอบ/แก้ไข
Cannot connect to the Docker daemon Docker Engine หรือ Docker Desktop ยังไม่ทำงาน เปิด Docker Desktop หรือ Docker Service แล้วตรวจ docker info
port is already allocated Host Port 8000/8088 ถูกใช้งาน ตรวจโปรแกรมที่ใช้พอร์ต หรือเปลี่ยน Host Port ใน compose.yaml
Web Container ไม่ Healthy Flask เริ่มไม่สำเร็จ หรือ Redis ยังไม่พร้อม ตรวจ docker compose logs web และ docker compose logs redis
/health ตอบ 503 Redis ติดต่อไม่ได้ ตรวจ docker compose ps และเริ่ม Redis กลับมา
Name or service not known: redis Container ไม่อยู่ Network เดียวกัน/ชื่อผิด ตรวจ docker compose config และชื่อ Service
ModuleNotFoundError Dependencies ไม่ถูกติดตั้งใน Image ตรวจ requirements.txt และ docker compose up --build -d
Counter ไม่คงอยู่ ไม่ได้ Mount Volume หรือปิด Persistence ตรวจ volumes และ --appendonly yes
Image Pull ช้า Network/Registry มีปัญหา ตรวจอินเทอร์เน็ต Proxy หรือ Registry ที่ใช้งาน

#12. งานฝึกเพิ่มเติม (Challenge)

#Challenge A — เพิ่ม Endpoint สำหรับ Reset Counter

สร้าง POST /reset โดยสั่ง Redis ให้ตั้งค่า visits เป็น 0 แล้วลองทดสอบผ่าน cURL พร้อมอธิบายว่า Endpoint ที่เปลี่ยน State เหตุใดไม่ควรออกแบบเป็น GET

ข้อกำหนดเพิ่มเติม: สำหรับระบบจริง ควรมี Authentication/Authorization ก่อนเปิด Endpoint ที่ Reset ข้อมูล

#Challenge B — เพิ่ม Service อีกหนึ่งตัว

เพิ่ม Web API อีก Service ที่สามารถอ่านค่าจาก Redis เดิม โดยใช้ Docker Network เดียวกัน แล้วเปรียบเทียบจำนวนครั้งที่แสดงจากทั้งสอง API

#Challenge C — จำกัดสิทธิ์ Container

ทดลองเพิ่ม read_only: true และ tmpfs: ["/tmp"] ให้บริการ web แล้วทดสอบว่า Gunicorn ยังทำงานได้หรือไม่ พร้อมอภิปรายสาเหตุที่บาง Runtime จำเป็นต้องมี Temporary Directory ที่เขียนได้

#Challenge D — บันทึก Log และทำ Monitoring

ทดลองดู docker stats ขณะส่ง Request หลายครั้ง และออกแบบ Metrics ที่ควรมีอย่างน้อย 3 ตัว เช่น Request Rate, Error Rate และ Latency

#เกณฑ์ประเมิน Workshop (100 คะแนน)

รายการประเมิน คะแนน
อธิบาย Container, VM, Namespaces และ cgroups ได้ถูกต้อง 20
Build Image และรัน Flask + Redis ด้วย Compose ได้ 25
สาธิต Docker Network และ DNS redis ได้ 15
สาธิต Persistence ผ่าน Named Volume ได้ 15
ทดสอบ Health Check และจำลอง Failure ได้ 15
อธิบาย Security Risks และ Cleanup ได้ 10
รวม 100

#13. คำถามทบทวน

  1. เหตุใด Container บน Linux จึงมักใช้ทรัพยากรน้อยกว่า VM เมื่อเทียบแอปประเภทเดียวกัน?
  2. Namespaces และ cgroups รับผิดชอบงานต่างกันอย่างไร?
  3. เพราะเหตุใดการระบุ EXPOSE 8000 ใน Dockerfile จึงยังไม่ทำให้เครื่องภายนอกเข้าถึง API ได้?
  4. การใช้ redis แทน IP Address ของ Redis Container มีข้อดีอย่างไร?
  5. ทำไม docker compose down จึงมักไม่ทำให้ข้อมูล Redis ใน Workshop หาย แต่ docker compose down -v อาจทำให้หาย?
  6. ทำไม depends_on: condition: service_healthy จึงไม่สามารถแทน Runtime Retry/Circuit Breaker ได้ทั้งหมด?
  7. การ Publish Port ไปยัง 127.0.0.1 ต่างจาก 0.0.0.0 อย่างไร?
  8. หากต้องการใช้ Windows Container แทน Linux Container จะต้องพิจารณาเรื่อง Host Kernel และ Compatibility อย่างไร?

#14. Cleanup: จบ Workshop อย่างปลอดภัย

ในโฟลเดอร์โปรเจกต์ ใช้คำสั่ง:

docker compose down

หากต้องการลบข้อมูลตัวนับและข้อมูล Redis ทั้งหมดที่เก็บใน Volume สำหรับ Workshop นี้ด้วย:

docker compose down -v

โปรดตรวจสอบก่อนใช้ -v: เป็นคำสั่งลบข้อมูลถาวรใน Volume ของ Compose Project นี้และอาจกู้คืนไม่ได้หากไม่มี Backup

สามารถลบ Image ของ Workshop เพิ่มเติมได้เมื่อแน่ใจว่าไม่มี Container ที่อ้างอิง:

docker image rm seai/container-workshop:1.0

ไม่จำเป็นต้องลบ Docker Image พื้นฐาน (เช่น Python หรือ Redis) เพราะอาจถูกใช้ร่วมกับโปรเจกต์อื่น


#15. สรุปสิ่งที่ได้เรียนรู้

ใน Workshop นี้ เราได้ทดลองแนวคิด Container Virtualization จากภาพสถาปัตยกรรมสู่การใช้งานจริง ได้แก่ การใช้ docker run, การเขียน Dockerfile, การ Build Image, การรันหลาย Service ด้วย Docker Compose, การค้นหาบริการผ่าน Network DNS, การเก็บข้อมูลใน Named Volume, การกำหนด Resource Limits และการจัดการ Health Check เมื่อส่วนประกอบบางตัวล่ม

แนวคิดเหล่านี้เป็นพื้นฐานที่ต่อยอดไปสู่ CI/CD, Kubernetes, GitOps และ Cloud-Native Application Development ได้โดยตรง แต่ต้องเพิ่มการออกแบบด้าน Security, Availability, Monitoring และ Production Operations ให้เหมาะกับความเสี่ยงของระบบจริง


#เอกสารอ้างอิงและแหล่งเรียนรู้

  1. Docker Docs — What is a container?
  2. Docker Docs — Docker Engine Security
  3. Docker Docs — Docker Desktop: WSL 2 backend
  4. Docker Docs — Define services in Docker Compose
  5. Docker Docs — Control startup and shutdown order in Compose
  6. Docker Docs — Volumes
  7. Docker Docs — Building best practices
  8. Docker Docs — Compose Quickstart
  9. Open Container Initiative — Overview
  10. Kubernetes Docs — Containers