Handbook วิธีใช้งาน Jenkins Syntax Pipeline พร้อมการทำ Continuous Delivery ไปยัง Kubernetes
สวัสดีครับพี่ๆเพื่อนๆทุกคนหลังจากบทความก่อนเราได้เห็นเรื่องการ Overview Process ของการทำ Life Cycle ใน DevOps ไปบ้างแล้ว ทีนี้เราจะลองมาโฟกัสในเฉพาะส่วนของ Technical ด้วยการใช้ Tool อย่าง Jenkins ที่ใช้ในการสร้าง Deployment Pipeline กันครับ โดยบทความนี้จะครอบคลุมในส่วนของ
- Jenkins Concept ในการเชื่อมต่อกับ Version Control
- Credentials Management เพื่อไม่ต้องใส่ credentials ลงไปใน Source Code
- Integration Jenkins with Version Control ลง plugin ให้ทำ Webhook กับ Gitlab สำหรับ Project Multiple Branch
- Jenkinsfile Structure ในการสร้าง Pipeline สร้างเงื่อนไข Approve ตั้งเวลาต่างๆ
- Deploy to Azure Kubernetes Service ไปยัง AKS แบบไม่ลง Plugin ใดๆ
- Source Code ตัวอย่างพร้อมคำอธิบายในทุกๆ Pipeline Syntax เพื่อช่วยให้เข้าใจมากขึ้น โดย Source Code จะอยู่ข้างล่างสุด
Concept Jenkins
แนวคิดการทำงานทุกอย่างของ Jenkins หลักๆให้เราลองนึกถึงวิธีการออกแบบการเก็บ Source Code ของเราว่าจะประกอบไปด้วยการมี Branch การทำ Commit Message โดยเมื่อใดก็ตามที่มีการอัพ code ขึ้นไปยัง Version Control ตัว Version Control เองก็จะทำการส่ง Webhook Request ไปยัง Jenkins เกี่ยวกับรายละเอียดของการ push code ทั้งหมดไปให้ Jenkins รับรู้และตัว Jenkins
จะทำการอ่านไฟล์ที่ชื่อว่า “Jenkinsfile” เสมอถ้าหากพบว่ามีไฟล์นั้นอยู่ใน Project กระบวนการทำ Process Pipeline ก็จะดำเนินต่อไป (ซึ่ง Jenkinsfile นั้นจะใช้ภาษา Groovy ในการเขียน โดยจะคล้ายๆกับ Java ซึ่งพวก Method อะไรก็จะใช้ได้เหมือนใน Java เช่นกัน) ซึ่งแน่นอนครับว่าเราสามารถออกแบบได้ว่าอยากให้ Jenkins ทำงานแบบ Single Branch Pipeline หรืออ่านแยก Jenkinsfile จากแต่ล่ะ Branch ซึ่งจะเรียกว่า Multiple Branch Pipeline แต่การจะทำ Multiple Branch Pipeline ได้จำเป็นที่จะลง Plugin ตัวหนึ่งที่ชื่อว่า Multibranch Scan Webhook Trigger
User Interface: Overview
- จะเป็นส่วนแสดง Panel ของ Project ที่อยู่ในระบบซึ่งสังเกตดุว่าจะมีสัญลักษณ์ทั้งก้อนลูกบอลกลมๆ, รูปหนังสือและรูป folder ที่มี branch ซึ่งสัญลักษณ์พวกนี้เองจะแทนความหมายของ Project ใน Jenkins ซึ่งมีด้วยกันหลายแบบเช่นการสร้าง Project แบบ Single Branch ที่ก็จะ Detect Branch Master เป็นหลัก
- Build Executor Status จะเป็นตัวกำหนดจำนวนของตัว Build ที่ทำ Pipeline ในระบบว่ามีจำนวนกี่ตัวซึ่งเราสามารถเพิ่มลดจำนวนเหล่านี้ได้ (แต่ล่ะงานก็ทำบน executor ของตัวเอง) ดังนั้นถ้าหากเรามีต้องการ build หลายๆตัวพร้อมกันก็ต้องไปเพิ่มจำนวนของ Executor
- Console Management จะเป็นในส่วนของการเข้าไปเพื่อดูภาพรวมโปรเจคต่างๆ ซึ่ง Jenkins Version ใหม่จะมี UI Ocean Blue ซึ่งจะปรับปรุงให้ Interface ดูทันสมัยมากขึ้นโดยจุดประสงค์ก็ใช้ในการดูว่า Pipeline เป็นอย่างไรมีกี่ Step หรือต้องการใส่ Input อะไรลงไปบ้างก็จะมีปุ่ม GUI ให้กดใน Ocean Blue ด้วยเช่นกัน (จริงๆปกติก็มีอยู่แล้วแค่อาจจะดูไม่ทันสมัยเฉยๆ แต่ส่วนตัวรู้สึกว่าขี้เกียจไปกดใน Ocean Blue ครับ 5555 กดผ่านหน้าเก่าเดิมไปเลยย)
User Interface: Config/ Credentials
แน่นอนว่าในทุกๆ Project ตัว code ของเรามักจะมีการต่อกับ Database หรือ 3rd party บ่อยๆเช่นอยากทำ Google Authentication ก็จะต้องนำ Credentials บางอย่างที่ google ให้มาใส่ใน code ของเรา หรือการจะติดต่อกับ Database ได้ก็ต้องมีใส่ URL และ user + password ไปด้วยกัน ซึ่งถ้าหากเราฝัง credentials เหล่านี้ลงไปเลยในตัว Program แล้วเผลอไปใช้บน public repository ที่คนอื่นสามารถเห็น code เราได้ก็มีสิทธิที่จะโดน Compromised endpoint เหบ่านั้นได้เช่นไปลบ Database ของเราหรือโขมยข้อมูลไปใช้ ยิ่งเป็น credentials ของ public cloud แล้วถ้าหากเราไม่ได้ตั้งสิทธิบน IAM ให้สิทธิต่ำๆแล้วเผลอหลุด key เหล่านั้นไปก็อาจจะมีผู้ไม่ประสงค์ดีนำ key ของเราไปสร้าง Server เป็นร้อยเครื่องให้เราเปลืองเงินเล่นๆก็ได้ (ทีนี้ก็จะน้ำตาตกเลยทีเดียวครับ ฮือ) ดังนั้น Jenkins จึงมีส่วนในการออกแบบเก็บ Credentials เหล่านั้นไว้ใน Build Server แทน (เพื่อไม่ให้ต้องฝัง credentials ลงไปใน code) แต่แน่นอนว่าเราก็พยายามเสริม security บน Jenkins Server ของเราไม่ให้โดน Hack ด้วยนะครับไม่อย่างนั้นก็อาจจะกลายเป็นช่องโหว่ใหม่ได้เช่นเดียวกัน
ซึ่งเราจะลองกดเข้ามาดูที่ credentials ที่ชื่อว่า AZ_AKZ_USER เพื่อตรวจสอบว่า credentials ที่เคยถูกเรียกใช้ ที่ Proeject/ Branch ใดบ้าง ก้จะพบว่ามีทั้งหมด 4 จุดด้วยกัน ซึ่งตรงนี้จริงๆเราสามารถตั้ง Credentials ให้มองเห็นเฉพาะบาง Scope ได้เพื่อป้องกันการเรียกใช้ credentials ข้าม scope ของ Project กันในกรณีที่เรามี Project ขนาดใหญ่ แต่ส่วนตัวรู้สึกว่าตรงจุดนี้อาจจะเป็นจุดที่ Jenkins ยังจัดการได้ลำบากคือไม่สามารถ Group Credentials ต่อ Project ได้เหมือนกับ Gitlab, CircleCI วึ่งเหมือนกับว่า Jenkins ต้องมาสร้างที่ส่วนกลางแล้วจำกัด Access เอา แต่พวก Credentials/ Config ไม่ได้ฝังอยู่ต่อ Project ต้องมาดูจากส่วนกลางก่อนเสมอ ซึ่งจริงๆก็ไม่ได้เป็นข้อเสียซะอย่างเดียวนะครับ เพราะมันก้ช่วยให้เราเห็น Credentials ทั้งหมดในระบบ ไม่ต้องไปหาที่ล่ะ Project แต่ในขณะเดียวกันมันก็ Group ไม่ค่อยถนัด (หรือถ้าเพื่อนๆพบวิธีก็รบกวนบอกได้เลยนะครับกำลังหาทางอยู่ 5555)
ในส่วนของหน้าเพิ่ม Credentials ใหม่เราก็สามารถเลือกได้ว่าเราอยากจะเลือก Credentials เป็นแบบใดตัวอย่างเช่น Git Crdentials หรือจะเป็น Text ที่เป็นรหัสผ่านหรือข้อมูล Text ใดๆที่ไม่อยากใส่ลงไปใน Code ตรงๆ
ซึ่งสิ่งที่สำคัญมากๆคือการตั้ง ID ของ Credentials ที่ห้ามซ้ำกันเพราะว่ามันคือตัว Identified ว่าเราจะ Reference ไปหา Credentials ตัวนั้นได้อย่างไรซึ่งสามารถเลือกได้ระหว่างให้ Jenkins Auto Generated ให้หรือเราจะกำหนดเองก็ได้เช่นกันครับ
จากภาพจะเป็นตัวอย่างการนำ credentials ที่อยู่ใน Jenkins Server ไปใช้งานให้ Inject ผ่าน Jenkins ไฟล์เราจะต้องใช้ function ที่ชื่อว่า credentials(credentialId:String ) โดยให้ใส่ชื่อ ID ที่เราตั้งขึ้นมา (หรืออาจจะ Auto Generated มาจาก Jenkins Server)
credentials('CREDDENTIAL_ID')
ซึ่ง fucntion นี้จะ return ค่าออกมาเป็น String เราจึงสามารถนำตัวแปรมารับได้โดยให้เราประกาศตัวแปรเป็นชื่ออะไรก็ได้ตามใจเรา (แต่แนะนำว่าใช้ชื่อแนวเดียวกันก็ได้ครับป้องกันการสับสน)
Plugin Installation
แน่นอนว่าจุดเด่นของการที่ Jenkins ยืนหยัดและเติบโตมาได้จนถึงทุกวันนี้ก็คือเรื่องของความยืดหยุ่นในการลง Plugin ต่างๆซึ่ง Plugin เรานี้มีทั้งระดับผู้ใช้ทั่วๆไปอย่างเราเข้าไป Contribute หรือกระทั่ง Enterprise อย่าง Microsoft เองที่มาช่วยเขียน Plugin ในการเชื่อมต่อไปยัง Azure Kubernetes Service ก็มีเช่นเดียวกัน (Plugin พวกนี้จะมีการแสกนเรื่อง Security ตลอดถ้าหากตัวไหนมีปัญหาก็จะแจ้งเตือนตอนก่อนลง)
โดย Plugin สำคัญที่เราจะลงก็คือ Multibranch Scan Webhook Trigger
ซึ่ง Jenkins เองนั้นมีความสามารถในการอ่าน Trigger ตัวเองเมื่อเรา Request มาจาก Webhook ของ Version Control อย่างที่ได้กล่าวไปข้างต้นก็จริงครับแต่ปัญหาอย่างหนึ่งที่ผมเสียเวลาทำช่วงแรกคือๆ Jenkins ไม่ยอม Detect และทำ Pipeline ใดๆนอกจาก Master Branch ถึงแม้ผมจะพยายามเขียน Conditional มากมายแค่ไหนก็ตามแล้ว แต่ถ้า Project ของเราต้องการให้สามารถ Build Detect หลายๆ Branch ได้และก็เชื่อม Webhook ได้เหมือนจะต้องใช้ Plugin ตัวนี้เพิ่มเติม
โดยหลังจากที่เราติดตั้ง Plugin เสร็จแล้ว Jenkins ก็อาจจะให้เรา Restart ซึ่งก็สามารถกด Restart ได้เลยครับ
Create New Project/ Integrated with Version Control
ซึ่งตอนแรกๆก็เลยปวดหัวหนักเหมือนกันว่าทำไมมันไม่ข้าเงื่อนไขอะไรเลยสักทีทั้งๆที่ตัวแปร branch ก็บอกว่าเรา push code ไปที่อีก branch ชัดๆ…
แต่คือถ้าอยากใช้เงื่อนไข when ดักต้องใช้บน “Multibranch Pipeline”
ทีนี้เราจะถึงส่วนที่เป็นองค์ประกอบหลักๆในการ Config Project ในการทำ Pipeline กันแล้วครับ ซึ่งหลักการที่ผมออกแบบไว้คือผมอยากให้ Project ของเรามีคุณสมบัติดังนี้
- สามารถ Build Code จากหลายๆ Branch และตั้งเงื่อนไขเกี่ยวกับ branch ได้
- สามารถดึง Tags ที่เราออกแบบเป็น SemVer เพื่อนำไปใช้ในการติด tag ให้กับ container image และ push ไปเก็บใน container registry
- สามารถดึง commit message มาได้ว่า Dev เขียนอธิบายอะไรลงไปเพื่อนำไปใช้อธิบาย change-casue บน Kubernetes History
- สามารถมี Webhook ทำการ Trigger ตัวเองให้สั่ง Code Pipeline ทันทีเมื่อมีการ Push Code ไปยัง Version Control แบบอัตโนมัติไม่จำเป็นต้องมีการมากด Build Manual บน Jenkins Server เองแล้วค่อย Build Code ที่อยู่ใน Version Control
ซึ่งตรงนี้คือจุดประสงค์ในการออกแบบ Pipeline ของผมเองนะครับซึ่งเวลาเพื่อนๆไปออกแบบก็สามารถสร้างสรรค์ให้เป็นท่าของตัวเองหรือตาม Standard ที่ล่ะที่ทำงานของเพื่อนๆได้เลยครับที่สะดวก ไม่จำเป็นต้องเหมือนกันอิๆ
ส่วนสุดท้ายนี้จะมีสองส่วนคือ Build Configuration และ Scan Multiplebranch Pipeline Trigger ครับที่จะต้องใส่ token สำหรับโปรเจคนี้โดย Webhook เวลาทำการ Request ก็จะต้องแนบ token ตัวเดียวกันไปครับเพื่อยืนยันว่ามันคือโปรเจคนี้จริงๆนั่นเอง (ไม่อย่างนั้นก็จะไม่รู้ว่า Webhook จะไป Trigger โปรเจคไหนหากไม่มี token identify) ส่วนถ้าใครยังไม่เห็น Scan Multiplebranch Pipeline Trigger ก็อย่าลืมไปลง Plugin Multibranch Scan Webhook Trigger ก่อนนะครับผม จากนั้นก็กด Save
โดย Pattern ของ URL ก็จะเป็น HTTP/ HTTPS ขึ้นกับว่าเราทำ TLS หรือยังตามด้วย domain name ของ jenkins server และก็ port ที่ Jenkins Server กำลังทำงานอยู่ตามด้วย path /multibranch-webhook-trigger/invoke?token=wearedevops
http://JENKINS_SERVER_URL:8080/multibranch-webhook-trigger/invoke?token=MY_TOKEN_FROM_JENKINS
ในส่วนสุดท้ายก็จะเป็นส่วนของการ Deploy เข้าไปยัง Kubernetes Cluster บน Azure ซึ่งจริงๆแล้ว Jenkins เองก็มี Plugin ที่ช่วยให้เราเขียน Pipeline ในการ Deploy ได้ง่ายขึ้นเช่นกันแต่สำหรับการทำครั้งนี้ ผมตั้งใจจะใช้แบบ Manual เขียน Shell Script เพื่อความเข้าใจในพื้นฐานและการประยุกต์ใช้งานหลายแบบเลยอยากลองทำเองก่อนลองใช้หลายๆ คำสั่งเข้าด้วยกันเพื่อให้เข้าใจในหลายๆส่วนบน Jenkins ดังนั้นขั้นตอนจริงๆแบบ Optimize กับ Refactor แล้วอาจจะสั้นกว่านี้ได้
Pipeline to Kubernetes: Jenkinsfile
ตรงส่วนนี้จะเป็น Code ของ Pipeline Jenkins ซึ่งได้ทำการอธิบายพร้อมกับแปะลิ้งค์แนบไปยัง Doc ของ Jenkins ให้แล้วครับ
Jenkinsfile : สำหรับการทำ Pipeline
Kubernetes Deployment: Template สำหรับให้ sed เข้ามา replace ตัวแปร
ซึ่งเพื่อนจะเห็นว่าขั้นตอนของ Input จะเกิดการ Hold ไว้เสมอรอให้ผู้ใช้เข้าไป Input ข้อมูลซึ่งจะเห็นว่า Tag Version นั้นจะถูกดึง auto มาจาก Version Control ของเรา
kubectl rollout history deployment
จะพบว่า change cause ของเราถูก format ด้วย pattern มีที่มีความหมายมากขึ้นกว่าการใช้แค่ flag record=true ซึ่งแน่นอนว่าเพื่อนพี่ๆจะอยาก custom อย่างไรก็ได้ให้ขึ้นกับความต้องการเลยนะครับอันนี้จะเป็นในส่วนของไอเดียว่าถ้าเราลองประยุกต์การใช้ shell script ล้วนๆในการ Deploy Kubernetes จะช่วยอะไรได้บ้าง
ซึ่งถ้าทำมาถึงขั้นตอนนี้พี่ๆเพื่อนๆทุกคนก็จะพบว่า Template Jenkinsfile นี้แทบจะไป Reuse กับใดๆในโลกหลายๆ Project เลยก็ได้สำหรับการ Deploy Process แต่ถ้าลองสังเกตดูดีๆจะพบว่า การใช้ sed ก็เหมือนจะครอบคลุมนะแต่บางจุดมันดูซับซ้อนและก็เราต้องทำการ Open/ Close File ไปๆมาบ่อยๆตามจำนวนตัวแปรที่เราอยากแก้ไขอีกทั้งยังมี Steps ของโค้ดที่ค่อนข้างยาว ซึ่งจริงๆแล้วเราสามารถใช้ Helm มาช่วยในการจัดการ Package ด้วยการยุบ yaml หลายๆตัวให้เป็นชิ้นเดียวได้ซึ่งจะทำให้ขั้นตอนของการ Deploy Code สั้นลง แต่แน่นอนว่าเราก็ต้องไปหาที่ฝาก Helm Chart ในที่อื่นอีก (จริงๆผมทดลองบน Azure Container Registry แล้วพบว่าฝากได้) เพราะว่า Azure Container Registry นั้น Implement ด้วย OCI Format ทำให้เราสามารถนำ Helm Chart ซึ่ง implement spec ตาม OCI ไปฝากได้ด้วยเช่นกัน !
แต่ถ้าถามว่าการทำ Shell Script แบบล้วนๆเลยข้อดีคืออะไร ?
นั่นก้คือการฝึกให้เราเข้าใจ Process Step แบบลึกๆว่าการ Deployment ต้องใช้ Step อะไรบ้างนั่นเอง ซึ่งการจะไปท่า Helm จะค่อยๆเกิดขึ้นมาเองว่าตอนไหนที่เรารู้สึกแค่ว่า Shell Script มันไม่สะดวก ซึ่งถ้าเพื่อนๆหรือใครที่พึ่งมาใช้แล้วยังไม่รู้สึกว่ามันติดขัด ก็อาจจะยังไม่จำเป็นต้องรีบเปลี่ยนไปใช้ตามทั้งหมด แต่ให้ทดลองก่อนแล้วพบว่าปัญหาตรงนี้คืออะไรแล้วค่อยเปลี่ยนไปใช้ก็ได้ครับ เพราะพอเรารู้ Cause ปัญหาปุปก็จะทำให้เราเกิด Motive ในการ Implement Solution นั้นขึ้นมาได้มากขึ้น
ซึ่งสำหรับผมเองผมรู้สึกว่าปัญหาคือการ Deploy 2 Version พร้อมกัน นั้นเกิดขึ้นได้ยากมากถ้าหากใช้ท่าแบบ sed เพราะสมมติผมอยากจะ Deploy แบบ kubectl apply ณ ที่คอมผม สิ่งที่เกิดขึ้นคือผมต้องไปตามแก้ tempalte ตัวแปรทุกๆตัว แต่ถ้าเราใช้ Helm เราจะ bundle ทุกอย่างเป็นชิ้นเดียวกัน นึกถึงตอน run Container Image เราจะพบว่าบาง Image นั้นเราสามารถกำหนด ENV เพื่อระบุ parameter ได้ซึ่ง Helm ก็จะมาช่วยทำให้เราสามารถทำอะไรแบบนั้นได้บน Kubernetes นั่นเองครับ ซึ่งจริงๆเบื้องหลังมันก้คือเหมือน sed นี่แหละที่ทำเป็นตัวแปรทิ้งไว้ก่อน เพียงแต่ Helm นั้นจะใช้ Go Template ในการเขียน format ไว้ทำให้ได้ความสามารถลึกซึ้งกว่าการใช้แค่ sed ค่อยๆเข้าไปแก้ทีล่ะไฟล์ครับ ลองนึกถึง Ansible Template ที่ใช้ Jinja2 หรือจะเป็น Template Frontend อะไรประมาณนั้นก็ได้เหมือนกันคับผม ( ̄︶ ̄)↗
สำหรับพี่เพื่อนๆคนไหนที่ยังไม่เห็นภาพรวมของ DevOps Process กับ Part ของ Jenkins สามารถลองเข้าไปอ่านที่บทความเก่าได้ที่ลิ้งค์ด้านล่าง
ทุกคนคือ DevOps เพราะ Culture & Mindsets คือพวกเรา!
Github: Source Code
ซึ่งประสบการณ์หลังจากเริ่มทำงานมาหนึ่งเดือนนี้ก็รู้สึกว่าเป็นงานที่สนุกเลยมากเลยครับที่พี่ๆในบริษัทช่วยกันสนับสนุนให้ผมได้มีโอกาสการเรียนรู้เนื้อหาดีๆกับวงการ DevOps มากมายซึ่งเป็นหัวข้อที่สนใจมาตั้งแต่ตอนเรียนแล้ว
ซึ่งต้องขอบคุณ “สำนักงานส่งเสริมเศรษฐกิจดิจิทัล (depa)” และอาจารย์ “คณะเทคโนโลยีสารสนเทศ มจธ. (SIT)” ที่ให้การสนับสนุน “ทุนเพชรพระจอมเกล้าเพื่อพัฒนาเทคโนโลยีและนวัตกรรมดิจิทัล (KMUTT-depa)”