Multi-container setup
บทเรียนนี้จะพาคุณสร้างภาพรวมของระบบหลายคอนเทนเนอร์ด้วย Docker Compose แบบเป็นขั้นตอน ตั้งแต่การแยกหน้าที่ของแต่ละ service, การตั้งชื่อ service ให้เรียกหากันได้, การใช้ depends_on, การแมปพอร์ตเพื่อเข้าจากภายนอก ไปจนถึงการสื่อสารภายใน network ของ Compose
ภาพรวม multi-container: frontend รับคำขอจากผู้ใช้, backend ประมวลผลธุรกิจ, และ database เก็บข้อมูล โดยแต่ละ service แยกหน้าที่ชัดเจน
ภาพเปรียบเทียบการสื่อสาร: ฝั่งซ้ายคือ External access จากภายนอกผ่าน port mapping, ฝั่งขวาคือ Internal service-to-service communication ผ่านชื่อ service บน network ภายใน
ภาพ flow การเริ่มระบบ: docker compose up -> parse compose file -> create network/volume -> start services -> attach logs
ภาพจำสำคัญบทนี้
แยก service ให้ชัด, ใช้ชื่อ service เป็นที่อยู่ภายใน, เข้าใจขอบเขตของ depends_on, และแยกให้ออกระหว่าง external ports กับ internal communication
ส่วนที่ 1
1. Multi-container setup คืออะไร
Multi-container setup คือการออกแบบระบบที่มีหลาย service ทำงานร่วมกัน โดยแต่ละ service รันใน container ของตัวเอง และให้ Docker Compose ช่วยจัดการทั้งระบบผ่านไฟล์เดียว (`docker-compose.yml`) แนวคิดหลักคือ ไม่ยัดทุกอย่างไว้ใน container เดียว แต่แยกตามหน้าที่ แล้วประกาศความสัมพันธ์กันให้ชัดเจน เช่น ใครต้องเรียกใคร, ใครเก็บข้อมูล, ใครรับทราฟฟิกจากผู้ใช้
ส่วนที่ 2
2. ทำไมระบบจริงมักไม่ใช้ container เดียว
ระบบจริงมักมีหลายส่วน เช่น หน้าบ้าน (Frontend), หลังบ้าน (Backend), และฐานข้อมูล (Database) ซึ่งแต่ละส่วนมีวงจรอัปเดตและทรัพยากรต่างกัน ถ้าเอาทุกอย่างยัดลง container เดียว จะดูแลง่ายช่วงแรกแต่ลำบากตอนขยายระบบ เพราะ debug ยาก, scale ยาก, และความเสี่ยงกระทบกันสูงเมื่อมีการเปลี่ยนแปลง
ประเด็นที่ 1
แยกส่วนแล้วแก้ปัญหาได้ตรงจุดกว่า
ประเด็นที่ 2
สเกลเฉพาะ service ที่คอขวดได้
ประเด็นที่ 3
อัปเดต service เดียวโดยไม่ต้องกระทบทั้งระบบ
ประเด็นที่ 4
โครงสร้างชัดเจนขึ้นสำหรับทีม
ส่วนที่ 3
3. ตัวอย่างระบบ เช่น frontend + backend + database
สมมติระบบร้านค้าออนไลน์แบบง่าย: - `frontend`: แสดงหน้าเว็บให้ผู้ใช้ - `backend`: รับคำขอ API และประมวลผลธุรกิจ - `db`: เก็บข้อมูลผู้ใช้และรายการสินค้า ทิศทางการคุยหลักคือ `frontend -> backend -> db` โดย frontend ไม่ควรคุย db โดยตรง
ส่วนที่ 4
4. หลักคิดในการแยก service
ให้แยก service ตาม "หน้าที่หลัก" ไม่ใช่แยกตามความสะดวกชั่วคราว หลักคิดนี้ช่วยให้ระบบอ่านง่ายและบำรุงรักษาได้นาน
| คำถาม | คำตอบที่ช่วยตัดสินใจ |
|---|---|
| ส่วนนี้ทำหน้าที่อะไรเป็นหลัก? | ถ้าหน้าที่ต่างกันชัดเจน ควรแยกเป็นคนละ service |
| ต้อง scale เท่ากันไหม? | ถ้าอัตราการใช้งานต่างกัน ควรแยก service |
| ใช้ runtime ต่างกันไหม? | เช่น Node.js กับ PostgreSQL ควรอยู่คนละ service |
| ข้อมูลต้องคงอยู่ไหม? | ถ้าต้องคงอยู่ ให้ service นั้นวางแผน volume |
ส่วนที่ 5
5. การตั้งชื่อ service ให้เข้าใจกันง่าย
ชื่อ service สำคัญมาก เพราะ Compose ใช้ชื่อนี้เป็น DNS ภายในให้ service อื่นเรียกหาได้ หลักตั้งชื่อที่ดี: - ใช้ชื่อสั้น ชัด และบอกบทบาท เช่น `frontend`, `backend`, `db` - เลี่ยงชื่อกำกวม เช่น `app`, `main`, `container1` - ใช้ชื่อคงที่ทั้งโค้ดและเอกสาร
- ดี: `frontend`, `backend`, `db`
- ดี: `auth-service`, `payment-service` (เมื่อแยกโดเมนชัด)
- ควรเลี่ยง: `myapp`, `server-new`, `temp-db`
- ถ้าเปลี่ยนชื่อ service ต้องตรวจ env และ URL ที่อ้างถึงทั้งหมด
ส่วนที่ 6
6. การสื่อสารกันภายใน compose network
เมื่อ service อยู่ใน Compose project เดียวกัน Docker จะสร้าง network ให้โดยอัตโนมัติ (ถ้าไม่กำหนดเอง) และให้ service คุยกันผ่านชื่อ service ตัวอย่าง: `backend` ต่อฐานข้อมูลด้วย `db:5432` แทนการใช้ IP คงที่
ส่วนที่ 7
7. depends_on คืออะไร
`depends_on` ใช้บอกลำดับการเริ่มต้น service แบบพื้นฐาน เช่น ให้ `backend` เริ่มหลัง `db` มันช่วยให้โครงสร้างการเริ่มระบบอ่านง่าย และลดปัญหาบางส่วนจากการสตาร์ทสลับลำดับ
ส่วนที่ 8
8. สิ่งที่ depends_on ช่วยได้ และสิ่งที่มันไม่ได้รับประกัน
สิ่งที่ `depends_on` ช่วยได้คือ "ลำดับ start" แต่ไม่ได้รับประกันว่า service ปลายทางพร้อมใช้งานเต็มที่ (ready) แล้วทันที เช่น db process อาจเริ่มแล้ว แต่ยังโหลดไฟล์/สร้างระบบภายในไม่เสร็จ ทำให้ backend ต่อไม่ติดในวินาทีแรก
| หัวข้อ | ได้/ไม่ได้ | คำอธิบาย |
|---|---|---|
| ควบคุมลำดับเริ่มต้น | ได้ | ช่วยให้ backend เริ่มหลัง db |
| รับประกัน readiness | ไม่ได้ | ต้องใช้ healthcheck, retry หรือ wait script เพิ่ม |
| แก้ปัญหา connection fail ทุกกรณี | ไม่ได้ | แอปควรรองรับ retry เมื่อปลายทางยังไม่พร้อม |
ส่วนที่ 9
9. port mapping สำหรับเข้าจากภายนอก
`ports` มีไว้ map พอร์ตจาก host ไปยัง container เพื่อให้เข้าจากภายนอกได้ เช่นเบราว์เซอร์ของเรา รูปแบบคือ `HOST_PORT:CONTAINER_PORT` เช่น `3000:3000` หมายถึงเข้า `localhost:3000` แล้วส่งต่อไปพอร์ต 3000 ใน container
ส่วนที่ 10
10. internal communication ระหว่าง service
การคุยกันระหว่าง service ใน Compose ไม่จำเป็นต้องผ่าน `localhost` และไม่จำเป็นต้อง publish พอร์ตเสมอไป ให้คิดแยก 2 ทาง: - External access: ผู้ใช้จากนอก Compose เข้า service ผ่านพอร์ตที่ map - Internal communication: service ภายในคุยกันผ่านชื่อ service บน network เดียวกัน
ประเด็นที่ 1
External: Browser -> localhost:3000 -> frontend
ประเด็นที่ 2
Internal: frontend -> http://backend:8000
ประเด็นที่ 3
Internal: backend -> db:5432
ประเด็นที่ 4
การ map ports ไม่ได้แทน service DNS ภายใน
ส่วนที่ 11
11. ตัวอย่าง compose file แบบ multi-container setup
ตัวอย่างนี้เป็นระบบพื้นฐานที่มี frontend + backend + database พร้อมชื่อ service ชัดเจน, depends_on แบบพื้นฐาน, และการสื่อสารภายในผ่านชื่อ service
ตัวอย่างพร้อมใช้เพื่อศึกษาความสัมพันธ์ระหว่าง services, ports และ internal host name
ส่วนที่ 12
12. อธิบาย flow การเริ่มระบบ
เมื่อสั่ง `docker compose up` ระบบจะอ่านไฟล์ compose แล้วทำงานต่อเนื่องเป็นลำดับภาพรวมดังนี้ 1) ตรวจและ parse ไฟล์ `docker-compose.yml` 2) สร้าง network/volume ที่ต้องใช้ 3) สร้างและสตาร์ท container ตาม services 4) แสดง log รวมของทุก service หากมี `depends_on` จะช่วยจัดลำดับ start แต่ readiness ยังต้องดูที่แอปและการเช็กสุขภาพเพิ่มเติม
อ่านไฟล์และคำนวณ dependency graph
เตรียม network/volume ก่อนเริ่ม service
สั่ง start service ตามลำดับที่กำหนด
attach log เพื่อดูสถานะและข้อผิดพลาด
ส่วนที่ 13
13. ตัวอย่างคำสั่งที่ใช้
คำสั่งพื้นฐานที่ใช้บ่อยกับ multi-container setup
- ใช้ `up` ตอนกำลัง debug เพราะเห็น log สด
- ใช้ `up -d` ตอนต้องการรันค้างไว้เบื้องหลัง
- ใช้ `down` เมื่ออยากปิด stack ทั้งชุดให้สะอาด
- ถ้าอยากลบ volume ด้วย ให้ใช้ `docker compose down -v` อย่างระวัง
ส่วนที่ 14
14. ข้อผิดพลาดที่พบบ่อย
ปัญหายอดฮิตของผู้เริ่มต้นมักเกิดจากความเข้าใจเรื่อง network และลำดับเริ่มระบบ
- backend ต่อ db ด้วย `localhost` แทนชื่อ service `db`
- db ยังไม่พร้อมแต่แอปรีบต่อทันที (เข้าใจว่า depends_on รับประกัน readiness)
- map port ซ้ำกัน เช่น frontend และ backend ใช้ `3000:3000` เหมือนกัน
- ลืมประกาศหรือ mount volume ให้ฐานข้อมูล ทำให้ข้อมูลหายหลัง recreate
- แก้ compose แล้วไม่ recreate service จึงคิดว่าไฟล์ไม่ทำงาน
ส่วนที่ 15
15. สรุปท้ายบทแบบจำง่าย
จำสั้น ๆ ได้ว่า: - Multi-container = หลายบทบาท หลาย service ทำงานร่วมกัน - Service name = ที่อยู่ภายในที่ service อื่นใช้เรียก - depends_on = ช่วยลำดับ start แต่ไม่การันตี ready - ports = ทางเข้าจากภายนอก - internal communication = คุยกันภายในผ่านชื่อ service ถ้าคิดครบ 5 จุดนี้ เวลาออกแบบ Compose file จะชัดขึ้นมาก
ส่วนที่ 16
16. แบบฝึกหัด 4 ข้อ พร้อมแนวเฉลย แบบกดแล้วค่อยเฉลย
ลองทำก่อนดูเฉลย เพื่อเช็กว่าเข้าใจภาพรวมจริง