- 1. Container Virtualization คืออะไร?
- 2. สถาปัตยกรรมการทำงานของ Docker
- 3. เตรียมเครื่องมือสำหรับ Workshop
- 4. Lab 1 — รัน Container แรกด้วย Nginx
- 5. Lab 2 — สร้าง Docker Image สำหรับ Flask API
- 6. Lab 3 — เชื่อม Flask API กับ Redis ด้วย Docker Compose
- 7. Lab 4 — ทดลอง Isolation, Network, Volume และ Resource Limits
- 8. Lab 5 — ทดลอง Health Check และ Service Failure
- 9. Docker CLI Cheat Sheet
- 10. Security และ Production Best Practices
- 11. Troubleshooting
- 12. งานฝึกเพิ่มเติม (Challenge)
- 13. คำถามทบทวน
- 14. Cleanup: จบ Workshop อย่างปลอดภัย
- 15. สรุปสิ่งที่ได้เรียนรู้
- เอกสารอ้างอิงและแหล่งเรียนรู้
เป้าหมาย: เรียนรู้แนวคิดการจำลองสภาพแวดล้อมระดับระบบปฏิบัติการ (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 มาก่อน
สารบัญ
- แนวคิด Container Virtualization และ VM
- สถาปัตยกรรมและองค์ประกอบที่เกี่ยวข้อง
- เครื่องมือและการเตรียมสภาพแวดล้อม
- Lab 1: รัน Container แรกด้วย Nginx
- Lab 2: สร้าง Container Image สำหรับ Flask API
- Lab 3: เชื่อม Flask และ Redis ด้วย Docker Compose
- Lab 4: ตรวจสอบ Isolation, Network, Volume และ Resources
- Lab 5: Health Check และจำลอง Service Failure
- Best Practices, Troubleshooting และงานฝึกเพิ่มเติม

#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 Port8088เข้ากับ Container Port80โดยรับจาก 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:
FROM python:3.12-slimกำหนด Base Image สำหรับรัน Python บน LinuxWORKDIR /appกำหนด Working DirectoryCOPY requirements.txtตามด้วยpip installทำให้ใช้ Docker Build Cache ได้ดีเมื่อ Source Code เปลี่ยนแต่ Dependencies ไม่เปลี่ยนUSER appuserลดความเสี่ยงจากการรันแอปด้วยสิทธิ์ RootEXPOSE 8000เป็น Metadata เพื่อสื่อถึง Port ภายใน ไม่ได้ Publish Port ออก Host เอง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 ที่สมบูรณ์ การนำไปใช้งานจริงควรพิจารณาเพิ่มเติมดังนี้
- Least Privilege: ใช้
USERที่ไม่ใช่ Root และตัด Linux Capabilities ที่ไม่จำเป็น - Image Provenance: ใช้ Image จากแหล่งน่าเชื่อถือ ตรึง Version/Digest และสแกนช่องโหว่ก่อน Deploy
- Secrets Management: อย่าใส่ Credentials ใน Source Code, Dockerfile หรือ Image ให้ใช้ Secrets Manager หรือกลไกที่เหมาะสม
- Resource Governance: กำหนด CPU, Memory และ PIDs Limit ตามลักษณะ Workload
- Network Isolation: เปิด Publish Port เฉพาะที่จำเป็น และใช้ TLS เมื่อมีการเชื่อมต่อผ่านเครือข่าย
- Persistent Storage: สำรองข้อมูล Database และทดสอบการกู้คืนเป็นประจำ
- Observability: มี Logs, Metrics, Tracing และ Alerting ที่จำเป็น
- Patch Management: Build Image ใหม่เมื่อ Base Image หรือ Library อัปเดตด้านความปลอดภัย
- Runtime Hardening: พิจารณา Read-only Root Filesystem, Seccomp, AppArmor/SELinux, Rootless Mode หรือ User Namespace ตามความเหมาะสม
- 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. คำถามทบทวน
- เหตุใด Container บน Linux จึงมักใช้ทรัพยากรน้อยกว่า VM เมื่อเทียบแอปประเภทเดียวกัน?
- Namespaces และ cgroups รับผิดชอบงานต่างกันอย่างไร?
- เพราะเหตุใดการระบุ
EXPOSE 8000ใน Dockerfile จึงยังไม่ทำให้เครื่องภายนอกเข้าถึง API ได้? - การใช้
redisแทน IP Address ของ Redis Container มีข้อดีอย่างไร? - ทำไม
docker compose downจึงมักไม่ทำให้ข้อมูล Redis ใน Workshop หาย แต่docker compose down -vอาจทำให้หาย? - ทำไม
depends_on: condition: service_healthyจึงไม่สามารถแทน Runtime Retry/Circuit Breaker ได้ทั้งหมด? - การ Publish Port ไปยัง
127.0.0.1ต่างจาก0.0.0.0อย่างไร? - หากต้องการใช้ 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 ให้เหมาะกับความเสี่ยงของระบบจริง
#เอกสารอ้างอิงและแหล่งเรียนรู้
- Docker Docs — What is a container?
- Docker Docs — Docker Engine Security
- Docker Docs — Docker Desktop: WSL 2 backend
- Docker Docs — Define services in Docker Compose
- Docker Docs — Control startup and shutdown order in Compose
- Docker Docs — Volumes
- Docker Docs — Building best practices
- Docker Docs — Compose Quickstart
- Open Container Initiative — Overview
- Kubernetes Docs — Containers