บทที่ 11 — CI/CD: merge แล้วเว็บอัปเดตเอง
CI = ตรวจโค้ดอัตโนมัติ (บทที่ 7) · CD = ส่งขึ้น server อัตโนมัติ (บทนี้)
รวมกันคือ: กด merge เข้า uat แล้วเดินไปชงกาแฟ กลับมาเว็บอัปเดตเรียบร้อย
ทำไมต้องอัตโนมัติ
deploy ด้วยมือ vs อัตโนมัติ
อัตโนมัติ
- ขั้นตอนเหมือนกันทุกครั้ง ไม่มีลืม
- ใครก็ deploy ได้ ไม่ต้องรอคนที่รู้วิธี
- มีบันทึกว่าใคร deploy อะไร เมื่อไหร่
- ตรวจสุขภาพเว็บให้อัตโนมัติหลังเสร็จ
- พังแล้วเห็นทันทีว่าพังที่ขั้นไหน
ด้วยมือ
- ต้องจำลำดับคำสั่ง 5-6 ขั้น
- ข้ามขั้นตอนตอนรีบ (มักเป็นตอนที่พังที่สุด)
- คนเดียวในทีมที่ทำเป็น = คอขวด
- ไม่มีใครรู้ว่าเวอร์ชันบน server มาจาก commit ไหน
- ลืมเช็คว่าเว็บยังขึ้นอยู่ไหมหลังทำเสร็จ
ท่อทั้งหมดเมื่อ merge เข้า uat
สิ่งที่เกิดขึ้นอัตโนมัติ (~3-4 นาที)
1. merge PR เข้า uat
การกระทำเดียวของคนในกระบวนการนี้ — ที่เหลือหุ่นยนต์ทำหมด
คน
2. GitHub Actions ตื่น
ไฟล์ deploy.yml ตั้งเงื่อนไขไว้ว่า 'เมื่อมีโค้ดเข้า uat หรือ main ให้ทำงาน'
on:
push:
branches: [uat, main]
3. build image
สร้าง Docker image จาก Dockerfile บนเครื่องของ GitHub มีระบบแคชช่วยให้รอบถัด ๆ ไปเร็วขึ้น
4. push ขึ้น GHCR
อัปโหลดพร้อมป้าย :uat (ล่าสุด) และ :uat-<รหัส commit> (ถาวร ไว้ย้อนกลับ)
5. SSH เข้า server
ใช้กุญแจเฉพาะงาน deploy ที่เก็บไว้ใน GitHub Secrets — ไม่ใช่กุญแจส่วนตัวของใคร ถ้าหลุดก็เพิกถอนเฉพาะอันนี้ได้
6. server ดึง image ใหม่
ล็อกอินเข้า GHCR ด้วย token ชั่วคราวของรอบนั้น ดึง image แล้ว logout ทันที — ไม่มีรหัสค้างบนเครื่อง
docker compose pull app
docker compose up -d
docker logout ghcr.io
7. รีสตาร์ท container
หยุดตัวเก่า เปิดตัวใหม่จาก image ที่เพิ่งดึงมา ผู้ใช้แทบไม่รู้สึก
8. ตรวจสุขภาพเว็บ
ยิงเข้าเว็บจริงแล้วเช็คว่าได้รหัส 200 ถ้าไม่ใช่ = ทั้งงานขึ้นแดง คุณจะรู้ทันทีว่า deploy แล้วเว็บไม่ขึ้น
curl -o /dev/null -w '%{http_code}' https://apexhuas.choiceflowhub.com/th
# ต้องได้ 200
ดูของจริงได้ที่แท็บ Actions บน GitHub — ทุก deploy มีบันทึกไว้หมด
Secrets: เก็บความลับยังไงไม่ให้หลุด
ระบบ deploy ต้องใช้ของลับ (กุญแจ SSH, ที่อยู่ server) แต่ ห้าม เขียนลงในโค้ดเด็ดขาด เพราะทุกคนที่เห็น repo จะเห็นด้วย
ความลับของโปรเจกต์นี้เก็บไว้ที่ไหน (คลิกดู)
คลิกชั้นใดก็ได้เพื่อดูรายละเอียด
GitHub Secrets
เก็บในหน้า Settings ของ repo ใส่เข้าไปได้แต่อ่านกลับออกมาไม่ได้ workflow เรียกใช้ได้ และถ้าเผลอ print ออกมา GitHub จะเซ็นเซอร์เป็น *** ให้อัตโนมัติ
GITHUB_TOKEN
token ที่ GitHub สร้างให้เฉพาะการรันครั้งนั้น หมดอายุทันทีที่งานจบ เราใช้มันให้ server ล็อกอินเข้า GHCR ตอน deploy — ไม่ต้องสร้าง token ถาวรที่ต้องคอยหมุนเปลี่ยน
.env บนเครื่อง dev
ไฟล์ตั้งค่าที่มีรหัสจริง อยู่ในเครื่องแต่ละคน ถูกกันไว้ด้วย .gitignore ส่วนไฟล์ .env.example ที่มีแต่ชื่อตัวแปรกับค่าปลอมนั้นขึ้น repo ได้
กุญแจ deploy แยกจากกุญแจส่วนตัว
สร้างกุญแจใหม่เฉพาะงาน deploy ให้สิทธิ์เท่าที่จำเป็น ถ้าวันหนึ่งต้องเพิกถอนก็ทำได้โดยไม่กระทบการเข้าเครื่องของคุณเอง
เว็บพังหลัง deploy ทำยังไง
แผนฉุกเฉิน
1. 1. ดูว่างาน deploy แดงตรงไหน
เปิดแท็บ Actions บน GitHub ดูว่าล้มที่ build, ที่ pull, หรือที่ health check — บอกทิศทางได้เยอะ
gh run list --branch uat --limit 3
2. 2. ดู log ของเว็บบน server
ถ้า container เปิดไม่ขึ้น log จะบอกสาเหตุ เช่นตัวแปรที่ต้องใช้หายไป
ssh ... 'sudo docker compose logs --tail=50 app'
3. 3. ย้อนกลับเวอร์ชันก่อนหน้า
แก้ไฟล์ compose ให้ชี้ป้ายเวอร์ชันเก่า (:uat-<commit เดิม>) แล้ว up -d ใหม่ — เว็บกลับมาก่อน ค่อยไปหาสาเหตุทีหลัง อย่ารีบแก้สด ๆ บน server
sudo docker compose pull app && sudo docker compose up -d
4. 4. แก้ที่โค้ดแล้วปล่อยตามท่อปกติ
เมื่อรู้สาเหตุแล้ว แก้ใน branch → PR → CI เขียว → merge — ไม่แก้บน server เพราะจะหายตอน deploy ครั้งหน้า
เว็บคู่มือเล่มนี้ก็ใช้ระบบเดียวกัน
หน้าที่คุณอ่านอยู่นี้ deploy ด้วยท่ออีกเส้นที่ทำงานคล้ายกัน: แก้ไฟล์ Markdown ใน docs/learning/ → merge เข้า uat → ระบบสร้างเว็บใหม่ → ส่งขึ้น server → เช็คว่าเปิดได้
สองท่อบนเครื่องเดียวกัน
-
apexhuas.…
deploy.yml
เว็บ ApexHuas — build Docker image แล้วให้ server ดึงไปรัน
-
learn.…
learning.yml
คู่มือเล่มนี้ — สร้างไฟล์ HTML แล้วส่งขึ้นโฟลเดอร์บน server ให้ Caddy เสิร์ฟ
-
ทั้งคู่ทำงานเมื่อโค้ดเข้า uat
และทั้งคู่จบด้วยการตรวจสุขภาพเว็บจริง ถ้าเว็บไม่ตอบ 200 = งานขึ้นแดง
เช็คความเข้าใจ
-
เพราะ 'คำสั่งรันสำเร็จ' ไม่เท่ากับ 'เว็บใช้งานได้' container อาจเปิดแล้วตายทันทีเพราะตั้งค่าผิด health check ยิงเข้าเว็บจริงจึงเป็นการยืนยันขั้นสุดท้ายว่าคนเข้าเว็บได้จริง
-
เพราะเป็นความลับที่อยู่ถาวรบนเครื่องที่มีคนหลายคนเข้าถึง ต้องคอยหมุนเปลี่ยนและมักถูกลืม การใช้ token ชั่วคราวที่หมดอายุทุกรอบ deploy ทำให้ไม่มีอะไรค้างให้หลุด
-
ทำให้เว็บกลับมาใช้งานได้ก่อนด้วยการย้อนไปเวอร์ชันก่อนหน้า (เรามีป้าย image ที่มีรหัส commit ไว้พอดี) แล้วค่อยไล่หาสาเหตุอย่างใจเย็น — ไม่ใช่นั่งแก้สดบน server ตอนที่ลูกค้าเข้าไม่ได้
บทหน้า: ลงมือทำจริงหนึ่งรอบเต็ม ตั้งแต่ประโยคที่พิมพ์สั่ง AI จนเว็บอัปเดต