Skip to main content
Case Studies

เบื้องหลัง CI/CD Pipeline ของ Enersys — จาก git push ถึงเว็บไซต์ Live ใน 5 นาที

เปิดเบื้องหลังวิธีที่เราส่งมอบเว็บไซต์ enersys.co.th ขึ้น Production — ตั้งแต่ Pull Request, Docker Multi-Stage Build, DigitalOcean Registry จนถึง Kubernetes Rolling Update บน Self-Hosted Runner

13 มี.ค. 20268 นาที
CI/CDDevOpsDockerKubernetesGitHub ActionsSelf-Hosted Runner

ทำไมต้องเล่าเรื่อง Pipeline ของตัวเอง?

เวลาเราพูดเรื่อง CI/CD กับลูกค้า คำถามที่ได้ยินบ่อยที่สุดคือ "แล้วพวกคุณเองใช้อะไร?"

เราเชื่อว่าวิธีที่ดีที่สุดในการพิสูจน์ความเชี่ยวชาญคือการโชว์ว่าเราใช้สิ่งที่เราแนะนำกับตัวเองด้วย บทความนี้จะเปิดเบื้องหลัง Pipeline จริงที่ส่งมอบเว็บไซต์ enersys.co.th — ตั้งแต่นักพัฒนากด push จนถึงเว็บไซต์ Live

CI/CD Pipeline Overview — จาก Git Push ถึง Kubernetes Deploy


ภาพรวม: จาก Code สู่ Production

Pipeline ของเราแบ่งเป็น 2 เส้นทางหลัก ที่ trigger จากเหตุการณ์ต่างกัน:

  1. PR Validation — ทุกครั้งที่เปิด Pull Request ระบบจะ Build และทดสอบอัตโนมัติ
  2. 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 ทำงานพร้อมกัน:

PR Validation Pipeline — Build และ Lighthouse ทำงานคู่ขนาน

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 ที่ทำให้ทุกอย่างเกิดขึ้น:

Release & Deploy Pipeline — จาก Tag ถึง Kubernetes Rolling Update

ขั้นตอนที่ 1: Docker Multi-Stage Build

หัวใจของ Pipeline คือ Docker Build แบบ Multi-Stage — เทคนิคที่ทำให้ Production Image เล็กและปลอดภัยที่สุดเท่าที่จะทำได้:

Docker Multi-Stage Build — 3 ขั้นสำหรับ Web, 2 ขั้นสำหรับ API

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 อะไร


ตัวเลขจริงจาก GitHub Actions: ก่อน vs หลัง Self-Hosted Runner

เราไม่ได้แค่รู้สึกว่า "เร็วขึ้น" — เรามีข้อมูลจาก 49 Release ที่ผ่านมาพิสูจน์ได้ชัดเจน

ภาพรวมเปรียบเทียบ

Metric ubuntu-latest (48 runs) Self-Hosted (v1.1.179+)
เวลาเฉลี่ย 8.5 นาที 5.3 นาที
เร็วสุด 6.7 นาที 5.3 นาที
ช้าสุด 19.0 นาที 5.3 นาที

ลดลง 38% ตั้งแต่ Release แรกที่รันบน Self-Hosted Runner และที่สำคัญกว่าตัวเลขเฉลี่ยคือ — ไม่มี Spike ที่ 15-19 นาที อีกต่อไป

เจาะลึก: เวลาแต่ละขั้นตอน

เปรียบเทียบ Release จริง 2 ตัว — v1.1.177 (ubuntu-latest ตัวสุดท้าย) กับ v1.1.179 (self-hosted ตัวแรก):

ขั้นตอน ubuntu-latest (v1.1.177) Self-Hosted (v1.1.179) ผลต่าง
Build Web Image 219 วินาที 100 วินาที -54%
Build API Image 131 วินาที 121 วินาที -8%
Deploy to K8s 45 วินาที 34 วินาที -24%
รวมทั้งหมด 7.1 นาที 5.3 นาที -25%

ความแตกต่างที่ใหญ่ที่สุดอยู่ที่ Web Image Build — เร็วขึ้นกว่าเท่าตัว เพราะ Docker Layer Cache บน Self-Hosted Runner คงอยู่ข้าม Run ได้ ขณะที่ ubuntu-latest ต้อง Build ใหม่ทุกครั้ง

ทำไม ubuntu-latest ถึง Spike ไป 19 นาที?

ข้อมูลจาก 48 runs บน ubuntu-latest แสดงให้เห็นว่าส่วนใหญ่จบใน 7-8 นาที แต่มี 3 ครั้งที่พุ่งไป 15-19 นาที — สาเหตุหลักคือ GitHub-hosted runner ต้อง Provision VM ใหม่ทุกครั้ง บางครั้งเจอคิวรอ หรือ Network ระหว่าง Runner กับ Registry ช้าลงชั่วคราว

Self-Hosted Runner ตัดปัญหาเหล่านี้ออกทั้งหมด เพราะเครื่องรันพร้อมตลอด Cache พร้อม Network path เดิม


ทำไมเลือก Self-Hosted Runner?

สรุปจากตัวเลขข้างบน เหตุผลที่เราเปลี่ยนมาใช้ Self-Hosted Runner บนเครื่อง Ubuntu ของเราเองมี 3 ข้อ:

ความเร็วที่คาดเดาได้ — ไม่ใช่แค่เร็วขึ้นเฉลี่ย 38% แต่ที่สำคัญกว่าคือ ไม่มี Outlier อีกต่อไป ทุก Deploy จบภายในเวลาที่คาดเดาได้

Docker Cache ที่คงอยู่ — Web Image Build ลดจาก 219 วินาทีเหลือ 100 วินาที (-54%) เพราะ Layer Cache ไม่หายไปหลัง Job จบ

ต้นทุน — ลดการใช้ GitHub Actions Minutes โดยเฉพาะสำหรับ Job ที่รันบ่อย (เรา Deploy มากกว่า 10 ครั้งต่อวัน)


Nginx Configuration — รายละเอียดเล็กๆ ที่สร้างความแตกต่าง

Production Web Server ของเราไม่ใช่แค่ "เสิร์ฟ Static Files" แต่ถูก Optimize อย่างละเอียด:

  • Gzip Compression ระดับ 6 — บีบอัดทุกไฟล์ที่ใหญ่กว่า 256 bytes
  • Font Caching 1 ปี — ฟอนต์ไม่เปลี่ยนบ่อย Cache ยาวได้เลย
  • Image Caching 30 วัน — สมดุลระหว่าง Performance กับการอัปเดต
  • Next.js Hashed Assets 1 ปี — ไฟล์ที่มี Hash ใน URL เปลี่ยนชื่อเมื่อเนื้อหาเปลี่ยน Cache ยาวได้อย่างปลอดภัย
  • SPA Routing — จัดการ Client-side Navigation ให้ทำงานได้ถูกต้อง
  • Security Headers — ปิด Server Tokens ไม่เปิดเผยข้อมูลที่ไม่จำเป็น

Health Checks — ระบบที่ดูแลตัวเอง

ทุก Service มี Health Check ที่ Kubernetes ใช้ตัดสินใจ:

Service Endpoint Readiness Liveness
Web /healthz ทุก 5 วิ (3 ครั้งก่อน Fail) ทุก 30 วิ
API /health ทุก 10 วิ (3 ครั้งก่อน Fail) ทุก 30 วิ
PostgreSQL pg_isready Built-in Built-in

ถ้า Health Check ล้มเหลว Kubernetes จะ:

  • Readiness Fail → หยุดส่ง Traffic ไปที่ Pod นั้น
  • Liveness Fail → Restart Pod อัตโนมัติ

ไม่ต้องมีคนมานั่งเฝ้า ระบบดูแลตัวเองได้


ผลลัพธ์ที่ได้

Pipeline นี้ทำให้เรา:

  • Deploy ได้ทุกเมื่อ — ไม่มีขั้นตอน Manual ที่ต้องรอ
  • มั่นใจทุก Deploy — ทุก PR ผ่าน Build Test และ Lighthouse ก่อนถึง Production
  • Rollback ได้ทันที — ทุก Version ถูก Tag ไว้ กลับไป Version ก่อนได้ใน 1 คำสั่ง
  • Zero Downtime — Rolling Update ทำให้ผู้ใช้ไม่รู้สึกถึงการ Deploy
  • ติดตามได้ — Manifest ใน Git บอกสถานะ Production ได้เสมอ

ทั้งหมดนี้เกิดขึ้นใน ราว 5 นาที จาก git push --tags ถึง Live บน enersys.co.th — ลดลง 38% จากค่าเฉลี่ย 8.5 นาทีบน GitHub-hosted runner และไม่มี Spike 15-19 นาทีอีกต่อไป


แหล่งข้อมูล

บทความที่เกี่ยวข้อง

PDPA Compliance Automation — เมื่อต้องจัดการข้อมูลส่วนบุคคล 500,000 Records แล้วจะรู้ได้ยังไงว่าไม่หลุด?

Case Study การสร้างระบบ PDPA Compliance อัตโนมัติสำหรับองค์กรที่มีข้อมูลส่วนบุคคลกว่า 500,000 Records — ตั้งแต่ Data Mapping, Consent Management จนถึง Breach Detection ที่ทำงาน 24/7

AI Chatbot ROI — เมื่อลูกค้าถาม 3,000 คำถาม/วัน แล้ว Bot ตอบถูกแค่ 60% จะปรับยังไง?

Case Study การปรับปรุง AI Chatbot ที่ตอบถูกแค่ 60% ให้กลายเป็นระบบที่ Accuracy 94% ลด Cost per Interaction 68% และ Handle 85% ของคำถามทั้งหมดโดยไม่ต้องส่งต่อคน

Zero Downtime ERP Migration — เมื่อต้องย้ายระบบ ERP ที่ใช้มา 8 ปี โดยห้ามปิดระบบแม้แต่วินาทีเดียว

Case Study การย้ายระบบ ERP เก่าอายุ 8 ปีที่มี Transaction 15,000 รายการ/วัน ไปสู่ระบบใหม่แบบ Zero Downtime — ตั้งแต่ Data Migration Strategy, Dual-write Pattern จนถึง Cutover ที่ไม่มีใครรู้ว่าเกิดขึ้น

"Empowering Innovation,
Transforming Futures."

ติดต่อเราเพื่อทำให้โปรเจกต์ของคุณเป็นจริง