ทำไมต้องเล่าเรื่อง Pipeline ของตัวเอง?
เวลาเราพูดเรื่อง CI/CD กับลูกค้า คำถามที่ได้ยินบ่อยที่สุดคือ "แล้วพวกคุณเองใช้อะไร?"
เราเชื่อว่าวิธีที่ดีที่สุดในการพิสูจน์ความเชี่ยวชาญคือการโชว์ว่าเราใช้สิ่งที่เราแนะนำกับตัวเองด้วย บทความนี้จะเปิดเบื้องหลัง Pipeline จริงที่ส่งมอบเว็บไซต์ enersys.co.th — ตั้งแต่นักพัฒนากด push จนถึงเว็บไซต์ Live
ภาพรวม: จาก Code สู่ Production
Pipeline ของเราแบ่งเป็น 2 เส้นทางหลัก ที่ trigger จากเหตุการณ์ต่างกัน:
- PR Validation — ทุกครั้งที่เปิด Pull Request ระบบจะ Build และทดสอบอัตโนมัติ
- Release & Deploy — เมื่อ Tag version ใหม่ ระบบจะ Build, Push, และ Deploy ขึ้น Production
ทั้งสอง Pipeline รันบน Self-Hosted Runner ที่ตั้งอยู่บนเครื่อง Ubuntu ของเราเอง — ไม่ใช่ GitHub-hosted runner ที่ต้องเสียเวลา provision VM ใหม่ทุกครั้ง
เส้นทางที่ 1: PR Validation
ทุกครั้งที่นักพัฒนาเปิด Pull Request จะมี 2 Workflow ทำงานพร้อมกัน:
Build Validation
Workflow แรกทดสอบว่า Docker Image ทั้ง 2 ตัว (Web และ API) สามารถ Build สำเร็จหรือไม่ — แต่ ไม่ Push ไปที่ Registry เป็นการตรวจสอบว่าโค้ดใหม่ไม่ทำให้ Build พัง
สิ่งที่น่าสนใจคือเราใช้ Docker Buildx Cache ที่เก็บไว้บน Self-Hosted Runner — ครั้งถัดไปที่ Build, Layer ที่ไม่เปลี่ยนจะถูกข้ามไปทันที ทำให้ Build ที่ปกติใช้เวลาหลายนาที เหลือแค่ไม่กี่วินาที
Lighthouse Performance Audit
Workflow ที่สองจริงจังกว่า — มันจะ Build เว็บไซต์ทั้งหมด แล้ว Serve ขึ้นมาจริงๆ บน localhost เพื่อรัน Lighthouse Audit 3 รอบ
เกณฑ์ที่ต้องผ่าน:
- Performance ≥ 60% — ถ้าต่ำกว่านี้ PR จะ Fail ทันที
- Accessibility ≥ 80% — เราให้ความสำคัญกับการเข้าถึง
- SEO ≥ 80% — เว็บไซต์ต้องพร้อมสำหรับ Search Engine
- Best Practices ≥ 80% — มาตรฐานพื้นฐาน
ผลลัพธ์ถูกเก็บเป็น Artifact ไว้ดูย้อนหลังได้ 30 วัน ทำให้เราติดตามได้ว่า Performance เปลี่ยนแปลงอย่างไรในแต่ละ PR
เส้นทางที่ 2: Release & Deploy
เมื่อ PR ผ่านการ Review และ Merge แล้ว ขั้นตอนถัดไปคือการ Tag version เช่น v1.1.178 — นี่คือ Trigger ที่ทำให้ทุกอย่างเกิดขึ้น:
ขั้นตอนที่ 1: Docker Multi-Stage Build
หัวใจของ Pipeline คือ Docker Build แบบ Multi-Stage — เทคนิคที่ทำให้ Production Image เล็กและปลอดภัยที่สุดเท่าที่จะทำได้:
Web Frontend (3 Stages):
Stage แรกติดตั้ง Dependencies ทั้งหมดที่ต้องใช้ใน Monorepo Stage ที่สองรัน Build เพื่อ Export เว็บไซต์เป็น Static HTML/CSS/JS และ Stage สุดท้ายคือตัว Production จริง — Nginx Alpine ที่เสิร์ฟเฉพาะ Static Files ไม่มี Node.js ไม่มี Source Code ไม่มี Dependencies ที่ไม่จำเป็น
ผลลัพธ์คือ Image ขนาดประมาณ 22 MB ที่เบาและเร็ว
API Backend (2 Stages):
Stage แรก Compile TypeScript เป็น JavaScript พร้อม Generate Prisma Client Stage สุดท้ายเก็บเฉพาะ Compiled Code, Prisma Client และ Production Dependencies — ลบ Dev Dependencies, Source Code และ Build Tools ออกทั้งหมด
ขั้นตอนที่ 2: Push ไป Container Registry
Image ทั้ง 2 ตัวถูก Push ไปที่ DigitalOcean Container Registry พร้อม 2 Tags:
- Tag เฉพาะ version เช่น
v1.1.178— สำหรับ Rollback ถ้าจำเป็น - Tag
latest— สำหรับ Reference ล่าสุด
ขั้นตอนที่ 3: Kubernetes Deployment
Pipeline จัดการ Kubernetes แบบครบวงจร:
Secrets & Configuration — สร้าง Secrets สำหรับ Database Connection, API Keys ต่างๆ (Mailgun, LINE Messaging, Google Forms) ทั้งหมดจัดการผ่าน GitHub Secrets ไม่มีค่า Sensitive ใดถูก Hardcode
Rolling Update — Kubernetes จะค่อยๆ เปลี่ยน Pod ทีละตัว:
- Pod ใหม่เริ่มทำงานและรอให้ Readiness Probe ผ่าน (ตรวจสอบ
/healthzทุก 5 วินาที) - เมื่อ Pod ใหม่พร้อม Traffic จะถูกเปลี่ยนเส้นทาง
- Pod เก่าถูก Drain อย่างสุภาพ
- ไม่มี Downtime แม้แต่วินาทีเดียว
ขั้นตอนที่ 4: Autopublish
หลัง Deploy สำเร็จ Pipeline จะ Commit Kubernetes Manifest ที่อัปเดตแล้วกลับไปที่ main — ทำให้ Git Repository เป็น Single Source of Truth เสมอ ดูจาก Manifest ก็รู้ว่า Production กำลังรันอะไร Version อะไร