หัดเขียน .gitlab-ci.yml เบื้องต้น
ไฟล์ .gitlab-ci.yml เป็นไฟล์ที่จะบอก gitlab ว่าเราจะทำอะไรบ้างหลังจากที่มีคนนำ code เข้ามารวมใน repository ดังนั้นในบทความนี้เราจะมาทำความเข้าใจวิธีการเขียน .gitlab-ci.yml กันว่าเรา .gitlab-ci.yml มีโครงสร้างยังไงบ้าง
โครงสร้างของ .gitlab-ci.yml
โครงสร้างของ .gitlab-ci.yml จะประกอบไปด้วย
- Pipeline ทั้งไฟล์ .gitlab-ci.yml จะเรียกว่า pipeline ซึ่ง pipeline อาจถูก trigger ขึ้นมาทำงานได้จากการ
- Push
- Merge request(Pull request)
- Scheduler(ตั้งเวลา)
- Manual(เรากดปุ่ม run pipeline เอง)
- stage เป็นการแบ่ง phase ของการทำงาน โดย default จะเป็น build, test และ deploy ตามลำดับ
- job เป็น unit ของการทำงาน ถ้า script ตัวใดตัวหนึ่ง fail ก็จะ fail ทั้ง job การทำงานของ job ในแต่ละ stage จะเป็นงานที่สามารถ run พร้อมๆกัน(parallel) ได้
- script เป็น step การทำงานเป็นหน่วยที่ย่อยที่สุดในการทำงาน
ซึ่งไฟล์ .gitlab-ci.yml ทั้่งไฟล์จะเป็น pipeline ใน pipeline จะประกอบไปด้วย stage ซึ่ง default gitlab จะนิยามไว้ 3 stage ดังนี้
- build
- test
- deploy
ซึ่งเราสามารถจะปรับเปลี่ยน stage ได้ด้วยการนิยาม stage ไว้ใน .gitlab-ci.yml เช่น ถ้าเราต้องการให้ stage ของ deploy มาก่อน test ให้เขียนเป็น
stages:
- build
- deploy
- test
# job ต่างๆ
ในแต่ละ job จะทำการระบุไว้ว่า job นี้อยู่ใน stage ไหน ยกตัวอย่างเช่น
|
|
จากตัวอย่าง yml ด้านบนเราจะกำหนด stage ไว้ 3 stages
- build ใน build stage จะมี job ชื่อว่า build1
- test ใน test stage จะมี job ที่ชื่อว่า test1 และ test2
- deploy ใน deploy stage จะมี job ชื่อว่า deploy1
การระบุ image ที่จะใช้งาน
ถ้าเราเลือก executor ไว้เป็น docker(ลองอ่านเรื่องการติดตั้ง gitlab runner ได้ที่นี่) เราจะสามารถระบุ image ที่ต้องการใช้งานได้
โดยที่เราสามารถระบุเป็น
image: busybox:latest
# job ต่างๆที่อยู่ใน pipline
หรือ
default:
image: busybox:latest
# job ต่างๆที่อยู่ใน pipline
ทั้งสองแบบนี้จะให้ผลแบบเดียวกันคือ script ต่างๆที่ run อยู่ใน job จะเป็นการ run ด้วย shell ข้างใน container เช่น script ที่ echo ในตัวอย่าง้านล่างจะ run ภายใน container ที่สร้างขึ้นมาจาก image busybox version ล่าสุด
|
|
แต่ถ้าต้องการระบุ image เฉพาะใน job ที่ต้องการ เช่น ถ้าเราต้องการจะให้ run script ใน busybox version 1.35 ซึ่งไม่ใช่ version ล่าสุด เราจะเขียนแบบนี้ ส่วน job อื่นๆก็จะใช้ default image ที่อยู่ด้านนอก
|
|
ถ้าเราใช้ shell executor เราจะไม่สามารถระบุ image ได้
การเลือก runner ด้วย tag
ตอนที่เราสร้าง job ขึ้นมา gitlab จะทำการเลือก runner ที่มี tag ที่ถูกนิยามไว้ เราจึงต้องทำการระบุด้วยว่าใน job นี้จะใช้ tag อะไร
|
|
หรือจะทำการระบุทีเดียวใน default ได้เลย
|
|
ถ้าไม่ได้ระบุ tag ไว้ gitlab จะเลือกสร้าง job ใน runner ที่กำหนดไว้ใน config ว่าให้ run untag job
การประกาศตัวแปร
ตัวแปรใน gitlab จะมีอยู่ 2 ประเภทคือ
- Predefined variables คือตัวแปรที่ gitlab นิยามไว้ให้ใช้อยู่แล้ว เช่น CI_COMMIT_MESSAGE หรือ CI_JOB_ID
- User-defined variables คือตัวแปรที่เรากำหนดเอง ซึ่งเราสามารถประกาศตัวแปรไว้ในไฟล์ .gitlab-ci.yml หรือจะสร้างตัวแปรผ่านทาง UI ก็ได้
variables:
BUILD_NAME: "demo-project-$CI_JOB_ID"
ในตัวอย่างนี้เราประกาศตัวแปรที่ชื่อว่า BUILD_NAME ขึ้นมาและกำหนดค่าเป็นตำว่า demo-project- แล้วต่อด้วย running number ของ job(เป็น auto running จะไม่ซ้ำกันถ้าอยู่บน instance เดียวกัน)
การใช้ beforescript และ afterscript
ถ้าเราต้องการ run script ก่อน(before)หรือหลัง(after) script ที่อยู่ใน job ให้เราเขียนใน beforescript และ afterscript แบบในตัวอย่างนี้
before_script:
- echo "Before script section"
- echo "For example you might run an update here or install a build dependency"
- echo "Or perhaps you might print out some debugging details"
after_script:
- echo "After script section"
- echo "For example you might do some cleanup here"
เราสามารถใส่ command ที่ใช้ในการ install, update หรือ debug ค่าต่างๆใน before_script และ คำสั่งที่ใช้ในการ clean up ต่างๆ เช่น close database หรือ remove file ต่างๆ ใน after_script
การสร้างเงื่อนไข
ในแต่ละ job ที่เราสร้างขึ้นมาเราสามารถกำหนดเงื่อนไขในการเลือกว่าจะ run หรือ ไม่ run ได้ด้วย keyword only,except และ rules
การใช้ only และ except
only และ except นั้นจะตรงข้ามกัน โดยที่
-
only จะ run job นี้ก็ต่อเมื่อเข้าเงื่อนไข เช่น
build1: stage: build script: - echo "Do your build here" only: refs: - main
เราจะ run build1 เมื่อเป็น commit บน main branch เท่านั้น
-
except เราจะ run job นี้ก็ต่อเมื่อไม่เข้าเงื่อนไขนี้ เช่น
build1: stage: build script: - echo "Do your build here" except: refs: - main
เราจะ run job นี้ใน commit ที่เข้าในทุกๆ branch ยกเว้น main branch
การใช้ rules
การใช้ rule จะแบ่งออกเป็น 3 รูปแบบด้วยกันคือ
-
if กำหนดเงื่อนไขด้วยการเปรียบเทียบค่าของตัวแปรด้วย if ตัวอย่าง เช่น
build1: stage: build script: - echo "Do your build here" rules: - if: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^feature/
เราจะ run job นี้ก็ต่อเมื่อเป็นการ merge request มาจาก branch ที่ชื่อขึ้นต้นด้วยคำว่า feature
-
changes กำหนดเงื่อนไขด้วยการเปลี่ยนแปลง file ถ้า file ที่กำหนดมีการเปลี่ยนแปลงก็จะทำการ run job นี้ ตัวอย่างเช่น
docker-build: script: docker build -t my-image:$CI_COMMIT_REF_SLUG . rules: - changes: - Dockerfile
ถ้า Dockerfile มีการเปลี่ยนแปลงก็จะ run คำสั่ง docker build นอกจากนี้เรายังสามารถใช้ keyword compare_to เพื่อทำการเปรียบเทียบกับ file นี้ใน branch อื่นได้ด้วย เช่น
docker-build: script: docker build -t my-image:$CI_COMMIT_REF_SLUG . rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" changes: paths: - Dockerfile compare_to: 'refs/heads/main'
ในตัวอย่างนี้เราจะ run คำสั่ง docker build เมื่อ
- เป็นการ merge request (จากเงื่อนไขใน rules:if)
- Dockerfile ใน branch ปัจจุบันไม่ตรงกับ main branch
-
exists กำหนดเงื่อนไขด้วยการมีของไฟล์ ถ้ามีไฟล์จะ run job นี้แต่ถ้าไม่มีก็ไม่ run ตัวอย่างเช่น
docker-build: script: docker build -t my-image:$CI_COMMIT_REF_SLUG . rules: - exists: - Dockerfile
การใช้ When
เราสามารถใช้ keywork when ในการกำหนดเงื่อนไขในการ run ซึ่งถ้าไม่กำหนดจะใช้ค่า default ที่เป็น on_success
ค่าที่สามารถกำหนดได้
- on_success (default): จะ run job นี้เมื่อไม่มี job ใน stage ก่อนหน้า fail
- on_failure: จะ run job นี้ก็ต่อเมื่อมีอย่างน้อย 1 job ใน stage ก่อนหน้า fail(ตรงข้ามกับ on_success)
- never: จะไม่ run job นี้ไม่ว่า stage ข้างหน้าจะเป็นยังไงก็ตาม เราจะใช้กับ workflow: rules
- always: จะ run job นี้ไม่ว่า stage ข้างหน้าจะเป็นยังไงก็ตาม เราจะใช้กับ workflow: rules (ตรงข้ามกับ never)
- manual: จะ run job นี้ด้วยมือเท่านั้น เวลา run ใน pipeline จะขึ้นปุ่ม play ให้เรากด ส่วนใหญ่จะใช้กับ
- delayed: จะ delay การทำงานจนกว่าจะครบเวลาที่กำหนด(เราจะกำหนดช่วงเวลาที่ต้องการให้ delay)
job ไหนที่กำหนด allow_failure: true จะถูกมองว่า success เสมอไม่ว่าจะจบยังไงก็ตาม