Jenkins Controller & Agent
ผ่าโครงสร้างสมองกลจัดคิว (Controller) แยกขาดจากแรงงานลงมือรัน (Agent) ป้องกันระบบกลางล่มเมื่อเจอเวิร์กลามหนัก พร้อมเข้าใจ Failure Domain Isolation, Label Allocation และการเขียนสคริปต์ปลอดภัย
"Never Execute Heavy Builds on the Controller."
Controller คือจุดเปราะบางที่สุดของทั้งบริษัท (Single Point of Orchestration) การปล่อยให้โปรเจกต์คอมไพล์โค้ดบน Controller คือความเสี่ยงระดับวิกฤตที่วิศวกรระดับอาวุโสจะไม่ยอมให้เกิดขึ้น
เมื่อ Agent พัง หรือหน่วยความจำหมด ดับแค่ Pod/Node นั้น แต่ Jenkins Web UI และคิวงานของทีมอื่นยังรันต่อไปได้ราบรื่น
Introduced Topic & The Big Picture
Jenkins คืออะไร และทำหน้าที่อะไรในระบบสากล?
Jenkins คือ "ผู้จัดการฝ่ายปฏิบัติการอัตโนมัติ" ของทีมพัฒนาซอฟต์แวร์ หน้าที่ของมันคือคอยเฝ้าดูคลังโค้ด (Git) เมื่อไหร่ก็ตามที่มีคนส่งโค้ดใหม่เข้ามา มันจะทำหน้าที่รับคำสั่ง แล้วกระจายงานให้เครื่องจักรลูกน้องไปรันเทสต์ คอมไพล์โค้ด และส่งระบบขึ้นเซิร์ฟเวอร์ โดยที่โปรแกรมเมอร์ไม่ต้องมานั่งเปิด Terminal รันคำสั่งทีละบรรทัดด้วยมืออีกต่อไป
- รันบิลด์บนโน้ตบุ๊กตัวเอง: โปรแกรมเมอร์แต่ละคนมีสเปกเครื่อง เวอร์ชัน Java และเครื่องมือไม่เท่ากัน เจอปัญหาคลาสสิก "เครื่องฉันรันผ่าน ทำไมเครื่องเธอพัง"
- เสียเวลาเฝ้าหน้าจอ: ต้องกดคอมไพล์แล้วนั่งรอครึ่งชั่วโมง ไม่สามารถไปทำงานอย่างอื่นได้
- โยนไฟล์ขึ้นเซิร์ฟเวอร์ด้วยมือ: เสี่ยงต่อการลืมไฟล์บางตัว หรือก๊อปปี้ทับคอนฟิกที่สำคัญจนระบบจริงพัง
- Push แล้วจบ: นักพัฒนาแค่
git pushโค้ดเข้า Repository แล้วสลับไปเขียนฟีเจอร์ถัดไปได้ทันที - มาตรฐานกลางเดียวกัน: โค้ดถูกส่งไปรันบน Agent ที่ติดตั้งสภาพแวดล้อมมาตรฐาน สะอาด ไร้ข้ออ้างเรื่องเครื่องส่วนบุคคล
- รู้ผลความผิดพลาดทันที: ถ้ารันเทสต์ไม่ผ่าน Jenkins ยิงแจ้งเตือนกลับมาในแชตทีมทันทีภายใน 3 นาที
1. Jenkins Controller (สมองสั่งการ)
Brain- • หน้าที่หลัก: จัดคิวงาน (Build Queue), บันทึก Log, บริหารปลั๊กอิน, ให้บริการ Web UI
- • เก็บข้อมูล: จัดเก็บข้อมูลทั้งหมดไว้ในไดเรกทอรี
JENKINS_HOME(Job config, credentials, build history) - • กฎเหล็ก: ปิด Executor ให้เป็น 0 (ห้ามรันคำสั่งบิลด์หนักๆ บนนี้เด็ดขาด)
2. Jenkins Agent / Node (แรงงานภาคสนาม)
Worker- • หน้าที่หลัก: รันโปรแกรมจริง เช่น คำสั่ง Maven, Gradle, NPM, Docker build และ Test scripts
- • การเชื่อมต่อ: เชื่อมต่อไปยัง Controller ผ่านช่องทาง SSH หรือ Inbound TCP Agent (Remoting/JNLP protocol)
- • Label: ติดป้ายกำกับ (Label) เพื่อให้ Controller คัดเลือกงานส่งให้ตรงกับทักษะของเครื่อง
Everyday Mental Model & Intuition
หากคุณกำลังสงสัยว่าทำไมต้องแยก Controller กับ Agent ออกจากกัน ให้มองภาพเหมือนกับการบริหารจัดการในครัวระดับมิชลินสตาร์:
หัวหน้าเชฟ (Controller)
ยืนอยู่หน้ากระดานรับออเดอร์ คอยจัดคิวโพย ไม่ลงมือย่างสเต็กเอง เพราะถ้าหัวหน้าเชฟมัวแต่ก้มหน้าย่างเนื้อ แล้วกระทะไหม้ หัวหน้าเชฟจะดูออเดอร์โต๊ะอื่นไม่ได้อีกเลย ครัวพังทั้งร้าน
เชฟผู้ช่วยประจำเตา (Agents)
คนหนึ่งอยู่เตาย่างเนื้อ (Label: grill) คนหนึ่งอยู่สเตชันเตรียมสลัด (Label: cold-prep) รอรับโพยจากหัวหน้าเชฟมาทำ พอทำเสร็จก็ส่งจานขึ้นโต๊ะ
ใบสั่งอาหาร & สูตร (Jenkinsfile)
สูตรอาหารที่เขียนขั้นตอนชัดเจน: 1. ล้างผัก -> 2. ย่างเนื้อให้สุกระดับมีเดียมแรร์ -> 3. เช็ดจานให้สะอาดก่อนเสิร์ฟ ถ้าขั้นตอนไหนพัง ก็โยนทิ้งแล้วเริ่มใหม่
Real-World Engineering Context & War Story
จุดเริ่มต้น: บริษัทสตาร์ตอัปแห่งหนึ่งเซ็ต Jenkins ติดตั้งบน VM ตัวเดียว โดยให้มี Executor จำนวน 10 ช่องบน Controller โดยตรง เพราะสะดวก ไม่ต้องเสียเวลาต่อ Agent ข้ามเครื่อง ทุกอย่างทำงานได้ราบรื่นมาตลอด 6 เดือนแรก
วันที่พังยับเยิน: วันศุกร์เวลา 16:30 น. ก่อนรีลีสเวอร์ชันใหญ่ ทีม Backend จำนวน 4 คนกดรัน Integration Tests ซึ่งต้องดาวน์โหลด Docker Container และกินแรม 6GB ต่อบิลด์พร้อมกัน 4 บิลด์
สิ่งที่เกิดขึ้น: Linux Kernel ของเซิร์ฟเวอร์ Controller เกิดสภาวะ Memory Exhausted ตัวกลไก Out-Of-Memory Killer (OOM Killer) สั่งยิงดับโปรเซส Java ของ Jenkins ทันที หน้าจอ Web UI ดับสนิท คิวงานของทีม Frontend, Mobile และ QA ค้างเติ่งทั้งบริษัท ข้อมูลไฟล์ Config บางส่วนบันทึกไม่สมบูรณ์จนเกิดความเสียหาย ทีม DevOps ต้องใช้เวลากู้คืนและไล่ตรวจ integrity ย้อนหลังนานกว่า 12 ชั่วโมง
ตั้งค่า # of executors = 0 บน Built-in Controller โหนด และสั่งงานทั้งหมดไปยัง Agent ภายนอกเท่านั้น หากการทดสอบกินแรมจนพัง เครื่องที่จะดับมีแค่ Agent ตัวนั้น Controller จะยังคงทำงาน ส่งการแจ้งเตือนกลับมาใน Slack ได้ตามปกติ และจัดคิวงานอื่นต่อได้ทันที
Hands-on Syntax Breakdown (ลงมือทำทีละก้าว 3 ระดับ)
รันบน Agent ตัวไหนก็ได้ที่ว่าง (agent any)
pipeline { agent any // สั่งให้ Controller มอบหมายงานให้ Agent ตัวใดก็ได้ในระบบที่มีคิวว่าง stages { stage('Hello') { steps { echo 'Hello, Jenkins Distributed System!' } } } }
คำอธิบาย: เหมาะสำหรับการเริ่มต้นทดสอบไปป์ไลน์ แต่ในองค์กรที่มีโหนดหลากหลาย OS (Linux, Windows, macOS) การใช้ agent any อาจทำให้สคริปต์ Linux เผลอไปรันบนเครื่อง Windows จนพังได้
เจาะจง Agent ด้วยระบบ Label (agent { node { label '...' } })
pipeline { agent { node { label 'linux-build-worker' // บังคับให้ส่งงานเฉพาะโหนดที่มีป้ายกำกับตรงเป๊ะ } } stages { stage('Inspect Worker Node') { steps { echo "Build is safely executing on worker agent: ${env.NODE_NAME}" sh 'hostname && uname -a' // รันคำสั่ง Shell บน OS ของ Agent } } } }
คำอธิบาย: การใช้ label คือหัวใจของการจับคู่งาน ป้องกันไม่ให้คำสั่ง Shell Script หลุดไปรันผิดเครื่อง
แยก Agent อิสระต่อ Stage พร้อม Clean Workspace (agent none)
pipeline { agent none // ปิดการจอง Agent ที่ระดับ Global บังคับให้แต่ละ Stage ต้องจองแยกกัน stages { stage('Backend Maven Build') { agent { node { label 'java21-maven-agent' } } steps { echo 'Building JAR binary...' sh 'mvn clean package -DskipTests' } } stage('Frontend NPM Build') { agent { node { label 'node20-frontend-agent' } } steps { echo 'Bundling React assets...' sh 'npm ci && npm run build' } } } post { always { cleanWs() // ล้างขยะใน Workspace เสมอ ป้องกันปัญหาดิสก์เต็ม } failure { echo 'Build encountered critical error! Notifying channel...' } } }
คำอธิบาย: การใช้ agent none ช่วยให้เครื่อง Maven ไม่ต้องถูกจองค้างไว้ตอนที่งานกำลังรันฝั่ง Frontend ส่งผลให้คืนเครื่องกลับสู่ Pool ได้รวดเร็วกว่า และคำสั่ง cleanWs() การันตีว่าดิสก์ของ Agent จะไม่มีวันเต็ม
Interactive Simulator: Node Matching & Queue Allocator
จำลองสถานการณ์: Controller รับ Job เข้าคิว และพยายามส่งต่อให้ Agent
ลองปรับค่า Requested Label ใน Pipeline แล้วกดปุ่ม "Trigger Build" เพื่อดูพฤติกรรมของ Build Queue และสถานะของ Node
ตาม Best Practice ระดับโลก Controller ต้องปิด Executor เป็น 0 เพื่อป้องกัน OOM Crash