- แชร์ประสบการณ์ความหงุดหงิดของนักพัฒนากับ feedback loop ที่ช้าและกระบวนการดีบักที่ซับซ้อนของ GitHub Actions
- ในโปรเจกต์ tmplr มีการสร้างเอกสารด้วย CUE ผ่าน
build.rs แต่เมื่อ CI build ล้มเหลว ปัญหาก็เริ่มต้นขึ้น
- จาก 4 แพลตฟอร์ม มีเพียง Linux ARM ที่ build ล้มเหลว โดยสาเหตุมาจากพฤติกรรมของ GitHub Actions ที่ซ่อนไบนารี x86_64 บน runner arm64 ระหว่างการ cross build
- feedback loop ที่ไม่มีประสิทธิภาพนี้ต้องใช้เวลา 2–3 นาที ต่อการทดสอบการเปลี่ยนแปลงเพียงครั้งเดียว
- ทางแก้คือการลบ
build.rs และเปลี่ยนไปใช้ GNU Makefile เพื่อควบคุมตรรกะของ CI โดยตรงและแก้ปัญหาได้สำเร็จ
ที่มาของปัญหา
- tmplr เป็นเครื่องมือ scaffold ไฟล์/โปรเจกต์ที่ใช้ไฟล์เทมเพลตซึ่งมนุษย์อ่านและเขียนได้
- ใน
build.rs ใช้ CUE เพื่อสร้าง README.md, CHANGELOG.md และไฟล์เวอร์ชัน/ไฟล์ช่วยเหลือ เพื่อรับประกันความสอดคล้อง
- งานนี้เสร็จภายในเวลาประมาณ 1.5 ชั่วโมง และเขียนบทความที่เกี่ยวข้องเสร็จแล้วด้วย
- บนเครื่องโลคัลทำงานได้ตามปกติ แต่ในสภาพแวดล้อม CI ของ GitHub Actions build ล้มเหลวเพราะไม่ได้ติดตั้งไบนารี CUE
สาเหตุของการ build ล้มเหลว
- จาก 4 แพลตฟอร์ม (Linux ARM, macOS ARM, Linux x86_64, macOS x86_64) มีเพียง Linux ARM ที่เกิดข้อผิดพลาด “command not found”
- สาเหตุคือ matrix cross build ถูกแยกสภาพแวดล้อมอย่างเข้มงวด ทำให้ CUE ถูกติดตั้งเฉพาะบน โฮสต์ Linux x86_64 และ โฮสต์ macOS ARM เท่านั้น
- macOS สามารถรันไบนารี x86_64 ได้ไม่มีปัญหา
- Linux x86_64 ก็รันไบนารี x86_64 ได้ไม่มีปัญหา
- แต่ GitHub Actions ซ่อนไบนารี x86_64 บน runner arm64 ทำให้ไม่สามารถรันได้
feedback loop ที่ไม่มีประสิทธิภาพ
- ขั้นตอนที่ต้องทำซ้ำเพื่อแก้ปัญหา:
1. ค้นหาวิธีแก้ที่เป็นไปได้
2. แก้ไข ci.yml
3. commit และ push (jj squash --ignore-immutable && jj git push)
4. เปิดแท็บ "Actions"
5. เปิดการรันล่าสุด
6. เปิดการรัน Linux ARM
7. รอสักสองสามวินาที
8. สิ้นหวัง
9. ทำซ้ำ
- ใช้เวลา 2–3 นาที ต่อการเปลี่ยนแปลงหนึ่งครั้ง
- ในอุดมคติ GitHub ควรมี local runner ที่มีฟีเจอร์ครบถ้วน หรืออย่างน้อยก็มีวิธีให้ตรวจสอบความคืบหน้าได้อย่างรวดเร็วหลัง push
- เช่นฟีเจอร์แบบ "scratch commit": วิธีทดสอบการรันหลายแบบโดยไม่ทำให้ประวัติ Git และบันทึกการรันของ Actions สกปรก
- แต่ตอนนี้ยังไม่มีความสามารถแบบนั้น
วิธีแก้ปัญหา
- หลังจากวนลูปอยู่ 30 นาทีจึงหยุด
- นำวิธีที่เป็นที่รู้จักบนอินเทอร์เน็ตมาใช้: "อย่าให้ GitHub Actions จัดการตรรกะเอง แต่ให้ควบคุมสคริปต์โดยตรง แล้วให้ Actions มีหน้าที่แค่เรียกสคริปต์นั้น"
- ลบ
build.rs (แม้จะรู้สึกเสียดาย แต่ก็ต้องยอมเสียสละ)
- ย้ายงานสร้างทั้งหมดไปไว้ใน GNU Makefile
- commit ไฟล์ที่ถูกสร้างไว้ลงในรีโพซิทอรี และย้อนการเปลี่ยนแปลงของ CI กลับ
- แก้ปัญหาได้เรียบร้อย
บทสรุป
- GitHub Actions เป็นสาเหตุที่ทำให้บางสิ่งดี ๆ ใช้งานไม่ได้
- เสียเวลาไปมากกับการดีบัก runner และการปรับแต่งกระบวนการ build
- แต่ก็ยังมีข้อดีอย่าง การ build บน macOS ที่ยากจะหาได้จากวิธีอื่น
- แน่นอนว่า ยังไม่รู้จักระบบอื่นที่ตั้งค่าได้ง่ายกว่า GitHub Actions
> We are all doomed to GitHub Actions. …but at least I dodged the bullet early.
3 ความคิดเห็น
GitHub Actions ควรทำแค่เซ็ตอัปสภาพแวดล้อม (OS, build toolchain, …) กับรันสคริปต์ (shell, Python, bat, ps1…) เท่านั้น ต่อให้ GitHub ล่ม ถ้ามีแค่สภาพแวดล้อมพร้อม ก็ควรจะ build ได้จากที่ไหนก็ได้ พอมอง workflow ของ GitHub Actions ช่วงนี้แล้วก็รู้สึกว่า จำเป็นต้องไปขุดหาของพวกนี้มาใช้ถึงขนาดนั้นเลยเหรอ สมัยนานมาแล้ว(?) Ansible ก็เคยเป็นแบบนั้นแล้วก็พังไป
ความเห็นจาก Hacker News
ปัญหาหลักของ GitHub Actions คือวงจรฟีดแบ็กช้าเกินไป
แค่จะเช็กความล้มเหลวง่าย ๆ ก็ต้อง push แล้วรอ น่าหงุดหงิดมาก
คิดว่าควรแยกงาน CI ออกเป็นสคริปต์ที่รันในเครื่องได้ และใช้ความสามารถของ Actions แค่เป็นการเสริมแบบค่อยเป็นค่อยไป
ชุด
workflow_dispatchกับgh workflow runก็ใช้ได้ดี แต่ที่ตัวหลังไม่ให้ URL ของ workflow ที่รันทันทีนั้นไม่สะดวกถือว่าช่วยให้ได้ฟีดแบ็กเร็วขึ้นค่อนข้างสำเร็จ
ถ้ามีปัญหาก็ debug ได้ในสภาพแวดล้อมที่แทบจะเหมือน GitHub Actions
มันควรเป็นข้อกำหนดพื้นฐานของทุกระบบ CI
สุดท้ายสิ่งสำคัญคือฟีเจอร์อย่างการจัดคิว การวิเคราะห์เอาต์พุต และเทเลเมทรีประวัติการบิลด์
gh workflow runถ้าอยากได้ URL ต้องไปดึงรายการ workflow run ผ่าน GitHub API อีกรอบถ้ามีหลายงานรันพร้อมกันอาจสับสนได้ แต่ตอนนี้ก็ยังพอใช้งานได้ดี
ลองสรุปทิปสำหรับออกแบบ CI ไว้
shell ธรรมดาก็ควรพอ
ควรกำหนด CI target ใน
Makefileแล้วเรียกง่าย ๆ แบบmake ci-testหลังจากนั้นก็จัดการ CI ทั้งหมดด้วยตัวครอบง่าย ๆ อย่าง
make buildถ้ามีตัวแบ่งอย่าง
BeginStep("Step Name")ก็คงดีปัญหาไม่ได้อยู่ที่ GitHub Actions เองเท่าไร แต่อยู่ที่ระบบอัตโนมัติที่พอกทับไว้แบบเละเทะด้านบน
ควรทำ logic เป็นสคริปต์ด้วยภาษาอย่าง Python เพื่อให้รันในเครื่องได้ด้วย
ทุกครั้งต้องแก้ workflow, push, แล้วรอ
ฉันทำ CI ทั้งหมดภายในคอนเทนเนอร์
แพลตฟอร์ม CI มีหน้าที่แค่รันคอนเทนเนอร์นั้น จึงทำให้รันในเครื่องได้เหมือนกัน
พวกแพลตฟอร์มไม่ชอบวิธีนี้ เพราะมันทำลายvendor lock-in
ถ้าล้มเหลวสามารถ SSH เข้าไป debug ได้ทันที และแก้ manifest แล้วรันใหม่ได้โดยไม่ต้อง push branch
แต่ต้อง self-hosting
ทำให้มาตรฐานง่ายขึ้น แต่ก็มี trade-off เรื่องการดูแลรักษาอิมเมจ
จริง ๆ แทบไม่มี lock-in แต่ผู้คนกลับติดอยู่กับCI/CD cargo cult
ฉันชอบ GitHub Actions
มันดีกว่า Travis ที่เคยใช้ และมีประโยชน์มากในฐานะทรัพยากรฟรีสำหรับโปรเจกต์ OSS
หลังจากใช้ Nix แล้ว ความสามารถในการทำซ้ำสภาพแวดล้อมก็ดีขึ้นมาก ทำให้เข้ากับ Actions ได้ดีขึ้นเยอะ
คอนเทนเนอร์ที่ทำด้วย flake สามารถรันใน Actions ได้ตรง ๆ
โปรเจกต์ตัวอย่าง
คิดว่า GitHub Actions ควรเป็นโครงสร้างที่แค่เรียกสคริปต์ bash หรือ python
bash มีข้อจำกัดเยอะ ส่วน Python ยืดหยุ่นกว่าและรันในเครื่องก็ง่าย
วิธีแบบในบทความนี้ที่ติดตั้ง uv อัตโนมัติและจัดการ dependency ให้ถือว่าเหมาะที่สุด
แม้จะซับซ้อนกว่า bash แต่ในสภาพแวดล้อมของ Actions ประเด็นเรื่องประสิทธิภาพไม่ได้ใหญ่มาก
ปัญหาใหญ่ที่สุดของ Actions คือวิธีโฆษณาให้ “ประกอบ workflow เข้าด้วยกัน”
มันแทบ debug ไม่ได้ และการตั้งค่า cache ก็จุกจิกจนทำให้ build ช้า
ด้วยเหตุนี้ วิธีแบบรันตรงบน VM ถาวรจึงดูน่าสนใจ
ฉันเป็นผู้ก่อตั้ง Depot
กำลังให้บริการ runner สำหรับ GitHub Actions ที่เร็วกว่าและถูกกว่า
ความไม่พอใจที่หลายคนรู้สึกนั้นตรงกับความเห็นของคนส่วนใหญ่จริง ๆ
ระบบ Actions ไม่มีประสิทธิภาพในเชิงโครงสร้าง และเรายังเจอคอขวดใหม่ ๆ ทุกสัปดาห์
ฉันมั่นใจว่ามีวิธีที่ดีกว่านี้ และกำลังทดลองอยู่
ดูรายละเอียดเพิ่มเติมได้ที่ depot.dev
เมื่อสุดสัปดาห์ก่อนฉันทำเครื่องมือชื่อ
gg watch actionมันเป็นเครื่องมือที่ช่วยหา action ล่าสุดหรือ action ที่กำลังรันอยู่ของ branch ปัจจุบัน
ลิงก์ GitHub
ghCLI มาตั้งแต่แรก”แต่ก็มีบั๊กที่ทำให้ไม่เห็น repository ในคำสั่ง
gg tuiสงสัยว่าเครื่องมืออย่าง
actจะช่วยได้ไหมnektos/act
แม้การรันในเครื่องกับออนไลน์อาจต่างกันเพราะสถาปัตยกรรมไม่เหมือนกัน แต่ก็ดูยังมีประโยชน์
มีความเข้ากันได้ประมาณ 80%
SourceHut รองรับสิ่งนี้ ทำให้สะดวกมาก
ดูเหมือนว่านี่จะเป็นปัญหาที่หลีกเลี่ยงไม่ได้ เพราะโครงสร้างบังคับให้ต้องใส่ logic ลงไปใน yaml
ดูเหมือนว่าบทความข้างบนจะเสนอคำตอบไว้คร่าว ๆ แบบด้านล่าง แต่ถ้าแทนส่วนของสคริปต์ด้วย Dagger ก็ชวนให้คิดว่านี่อาจเป็นคำตอบที่ถูกต้องก็ได้
"อย่าปล่อยให้ GitHub Actions จัดการ logic แต่ให้ควบคุมสคริปต์เองโดยตรง และให้ Actions มีหน้าที่แค่เรียกใช้สคริปต์นั้น"