บทที่ 10 — Server, โดเมน และ HTTPS
เว็บที่ deploy แล้วต้องอยู่บนคอมพิวเตอร์สักเครื่องที่เปิดตลอด 24 ชั่วโมง มีเน็ต และคนทั่วโลกเข้าถึงได้ — เครื่องนั้นเรียกว่า server และเราเช่ามันจาก cloud
เครื่องที่เว็บนี้รันอยู่จริง
| รายการ | ค่าจริงของโปรเจกต์นี้ |
|---|---|
| ผู้ให้บริการ | Google Cloud (Compute Engine) |
| ชื่อเครื่อง | apexhuas |
| ที่ตั้ง | สิงคโปร์ (asia-southeast1) — ใกล้ไทย เว็บโหลดเร็ว |
| สเปก | 2 vCPU, RAM 2 GB, ดิสก์ 30 GB |
| ระบบปฏิบัติการ | Debian 13 |
| เลข IP | 34.143.223.60 |
| โดเมน | apexhuas.choiceflowhub.com |
ทำไมต้องแยกเครื่องของตัวเอง: โปรเจกต์นี้มีกฎว่างานลูกค้าต้องไม่ไปปนกับเครื่องที่รันของส่วนตัว — เวลาส่งมอบงานหรือให้สิทธิ์ใครเข้าถึงจะได้ไม่พันกัน
ตามรอย request หนึ่งอัน
นี่คือสิ่งที่เกิดขึ้นภายในเสี้ยววินาที ตั้งแต่คนพิมพ์ URL จนเห็นหน้าเว็บ กดถัดไปเพื่อเดินทีละขั้น
จากคนพิมพ์ URL จนได้หน้าเว็บ (~0.3 วินาที)
1. พิมพ์ชื่อเว็บ
คนพิมพ์ apexhuas.choiceflowhub.com แต่คอมพิวเตอร์คุยกันด้วยเลข IP ไม่ใช่ชื่อ — จึงต้องแปลงก่อน
2. ถาม DNS
เบราว์เซอร์ถามระบบ DNS ว่าชื่อนี้อยู่ IP ไหน ได้คำตอบว่า 34.143.223.60 (เราตั้งค่านี้ไว้ที่ Cloudflare เรียกว่า A record)
dig +short apexhuas.choiceflowhub.com
# 34.143.223.60
3. เคาะประตูที่ port 443
เบราว์เซอร์ต่อไปที่เครื่องนั้นทาง port 443 (ช่องมาตรฐานของ HTTPS) — ต้องเปิดไฟร์วอลล์ของ Google Cloud ไว้ ไม่งั้นถูกปิดกั้นตั้งแต่ตรงนี้
4. Caddy รับสาย
Caddy คือโปรแกรมที่ยืนรับ request ด่านแรก จัดการเข้ารหัส HTTPS ให้เรียบร้อย แล้วดูว่าชื่อเว็บที่ขอมาคืออันไหน
5. ส่งต่อให้เว็บ
Caddy ส่ง request ต่อไปให้ container ของเว็บที่รันอยู่ข้างในเครื่องที่ port 3000 — port นี้ไม่เปิดออกอินเทอร์เน็ต เข้าถึงได้จากข้างในเท่านั้น
apexhuas.choiceflowhub.com {
reverse_proxy app:3000
}
6. Next.js สร้างหน้าเว็บ
โค้ดของเรารันตรงนี้ ประกอบ HTML ขึ้นมา (ตอนนี้ดึงจากข้อมูลจำลอง อนาคตจะดึงจากฐานข้อมูลจริง)
7. ส่งกลับ
HTML วิ่งกลับตามทางเดิม ผ่าน Caddy เข้ารหัสแล้วส่งถึงเบราว์เซอร์ ทั้งหมดนี้ใช้เวลาราว 0.3 วินาทีจากประเทศไทย
Caddy ทำอะไรให้บ้าง
หน้าที่ของ Caddy (คลิกดูทีละอย่าง)
คลิกชั้นใดก็ได้เพื่อดูรายละเอียด
ทำ HTTPS ให้อัตโนมัติ
ขอใบรับรองจาก Let's Encrypt ให้เอง ติดตั้งให้เอง และต่ออายุให้เองก่อนหมด — สิ่งที่เมื่อก่อนต้องทำมือทุก 3 เดือน ตอนนี้เขียน config 3 บรรทัดจบ
เปลี่ยน http เป็น https
ใครเข้ามาทาง http:// จะถูกส่งต่อไป https:// ทันที (นี่คือสาเหตุที่ทดสอบด้วย curl แล้วเห็นรหัส 308)
แยกเว็บตามชื่อโดเมน
เครื่องนี้เสิร์ฟสองเว็บ: apexhuas.choiceflowhub.com ส่งต่อให้ container ส่วน learn.choiceflowhub.com (คู่มือที่คุณอ่านอยู่) เสิร์ฟไฟล์ static ตรง ๆ
ซ่อนเว็บจริงจากอินเทอร์เน็ต
container ของเว็บไม่ได้เปิด port ออกสู่ภายนอกเลย ทุก request ต้องผ่าน Caddy ก่อนเสมอ
DNS: ทำไมต้องรอ และคำที่ต้องรู้
| คำ | ความหมาย |
|---|---|
| A record | บอกว่าชื่อนี้ → IP นี้ (เราสร้าง apexhuas → 34.143.223.60) |
| subdomain | ชื่อย่อยหน้าโดเมนหลัก เช่น learn ใน learn.choiceflowhub.com สร้างกี่อันก็ได้ฟรี |
| TTL | นานแค่ไหนที่เครื่องอื่นจะจำคำตอบเก่าไว้ ตั้ง Auto ไว้ก็พอ |
| DNS only / Proxied | ของ Cloudflare — "DNS only" คือชี้ตรงไปเครื่องเรา เหมาะกับกรณีนี้เพราะให้ Caddy จัดการ HTTPS เอง |
สำคัญ: ต้องตั้ง DNS ให้ชี้มาที่เครื่องก่อน Caddy ถึงจะขอใบรับรอง HTTPS ได้ เพราะผู้ออกใบรับรองจะยิงกลับมาตรวจว่าเราเป็นเจ้าของโดเมนนั้นจริง — ถ้ายังไม่ตั้ง DNS จะเห็น error เรื่องใบรับรองตลอด
เข้าไปดู server ตอนมีปัญหา
# เข้าเครื่อง
ssh -i ~/.ssh/google_compute_engine [email protected]
# ดูว่าอะไรรันอยู่บ้าง
sudo docker compose -f ~/apexhuas/compose.yaml ps
# ดู log ของเว็บ (ล่าสุด 50 บรรทัด)
sudo docker compose -f ~/apexhuas/compose.yaml logs --tail=50 app
# ดูพื้นที่ดิสก์ (เต็มแล้วเว็บล่มได้)
df -h /SSH key คืออะไร: กุญแจคู่หนึ่ง — ครึ่งหนึ่ง (public) ฝากไว้บน server อีกครึ่ง (private) เก็บในเครื่องคุณห้ามให้ใคร เวลาเชื่อมต่อ ระบบจะจับคู่กันเอง ไม่ต้องใช้รหัสผ่าน ปลอดภัยกว่ามาก
เว็บล่ม — ไล่ดูตรงไหนก่อน
ไล่จากนอกเข้าใน
- 1. เปิดเว็บดูเอง ได้ error อะไร
- 2. DNS ยังชี้ถูกไหม (dig)
- 3. container ยังรันอยู่ไหม (docker ps)
- 4. log ของเว็บพ่นอะไรออกมา (docker logs)
- 5. ดิสก์เต็มหรือ RAM หมดไหม (df -h, free -h)
สิ่งที่ไม่ควรทำ
- รีสตาร์ททุกอย่างทันทีโดยไม่ดู log — หลักฐานหายหมด
- แก้ไฟล์บน server ตรง ๆ แล้วไม่ commit — ครั้งหน้า deploy ทับหาย
- ลบ container ทิ้งเพราะคิดว่าสร้างใหม่ได้
- เดาว่าน่าจะเป็นที่โค้ด ทั้งที่ยังไม่ได้เปิด log ดู
เช็คความเข้าใจ
-
เพื่อให้ทุก request ต้องผ่าน Caddy ก่อน — Caddy จัดการ HTTPS, บังคับเปลี่ยน http เป็น https, และแยกเว็บตามโดเมนให้ ถ้าเปิดตรงจะเข้าถึงได้แบบไม่เข้ารหัสและข้ามการควบคุมทั้งหมด
-
อาจเป็นเพราะ DNS ยังไม่กระจายทั่ว (รอสักครู่), Caddy ยังขอใบรับรองไม่สำเร็จ (ดู log ของ Caddy), หรือไฟร์วอลล์ไม่ได้เปิด port 80 ซึ่ง Let's Encrypt ใช้ตรวจสอบความเป็นเจ้าของโดเมน
-
ไม่ควร เพราะ deploy ครั้งถัดไปจะเอา image ใหม่มาทับ การแก้ของคุณจะหายไปเงียบ ๆ และไม่มีใครรู้ว่าเคยแก้อะไร — ให้แก้ในโค้ดแล้วปล่อยผ่านท่อปกติเสมอ
บทหน้า: ต่อท่อสุดท้าย — merge แล้ว deploy เองอัตโนมัติทำงานยังไง