ลองทำ CI/CD ด้วย Bitbucket Pipelines + DigitalOcean

ลองทำ CI/CD ด้วย Bitbucket Pipelines + DigitalOcean

ก่อนอื่นอยากให้ทุกคนทำความเข้าใจก่อนน่ะครับว่า CI/CD ไม่ใช่แค่การใช้เครื่องมือ มันไม่ใช่การที่เอาเครื่องมือไปวางแล้วบอกให้ทุกคนทำตามรูปแบบนี้ แต่มันต้องเกิดจากการที่ทีมงานตกลงกันว่า Workflow การทำงานตอนนี้มีอะไรบ้าง เพื่อมาดูว่าในแต่ละส่วนของการทำงานมันมีส่วนไหนที่ทำให้ Automate ได้ เครื่องมือไหนในส่วนนี้จะมาช่วยแบ่งเบาเราได้บ้าง

ผมแนะนำให้ศึกษาบทความนี้ก่อน เพื่อทำความเข้าใจกับหลักการเบื้องต้นครับ

สรุปสิ่งที่ได้จากการอ่านและพูดคุยกับพี่ปุ๋ยเรื่อง Continuous Integration
เนื้อหาต่อไปนี้คือสิ่งที่ได้จากการอ่านจากหนังสือ Continuous Integration: Improving Software Quality and Reducing Risk…
siamchamnankit.co.th

โดยวันนี้เราจะมาลองทำ workshop เล็ก ๆ ในการสร้าง CI/CD ด้วยเครื่องมือที่ชื่อว่า Bitbucket Pipelines กันครับ เพราะ ตอนนี้บริษัท I GEAR GEEK เราก็ใช้ Bitbucket เป็นหลักอยู่แล้ว เลยมองกันว่าไหน ๆ ก็ไหน ๆ แล้วเราใช้ CI/CD ของเค้าไปเลยแล้วกัน

ดังนั้นโจทย์ที่เราจะมาลองทำกันก็คือ

ทุกครั้งที่มีการ push code เราอยากให้ service มีการรีสตาร์ทขึ้นมาใหม่ด้วย code สดใหม่ที่เพิ่งมีการ push ขึ้นไป

ลองถอยกลับมาก่อนน่ะครับว่าถ้าเป็นก่อนหน้าโดยไม่มีเครื่องมือช่วย ใช้คนทำส่วนนี้นั้น สิ่งที่ต้องทำคือเราต้อง shell เข้าไปที่เครื่อง server จากนั้นก็ทำการสั่งคำสั่งดึง code ล่าสุดลงมาใหม่ และ ทำการรันคำสั่งเพื่อเปิด service ใหม่ขึ้นอีกทีนึง

ดังนั้นเราจะเปลี่ยนกระบวนการใหม่ ให้เป็น automate ขึ้น นั่นก็คือ…

ทุกครั้งที่ทำการ Push code ไปที่ branch master บน Bitbucket แล้วส่งโค๊ดล่าสุดไป ที่ Server และ มีการสั่งให้เปิด service ขึ้นมา แบบ Automate บนเครื่อง Server

สรุปขั้นตอนแบบกระชับจับใจความได้ดังนี้

Push code ไปที่ master (คน) ~> Pull code ~> Restart service

  • ตัวอักษรเข้ม คือ งานที่เราจะใช้ Bitbucket pipeline มาทำงานแทนเราในส่วนนี้ครับ

เตรียมการก่อนลงมือ

  • Server ที่เราจะใช้คือ DigitalOcean ครับ ผมเลือกใช้ Plan ถูกสุดเลย เพื่อทดสอบการทำครั้งนี้ และ ทำเลือก Droplet ที่ติดตั้ง Host เป็น Docker ไว้เลย เพราะโปรเจกต์ตัวอย่างที่ผมขึ้นโครงไว้ใช้ docker ในการรันขึ้นมาครับ

  • แวะกลับไปที่ Bitbucket ของเราก่อน ให้คุณสร้าง Repository ขึ้นมาใหม่ครับ โดยเลือกที่ Import repository
    ส่วนตรง URL ให้กรอก Repo ที่ผมสร้างไว้ : https://bitbucket.org/nitipat/docker-hello-world

เอาล่ะ… มาเริ่มกันเลยดีกว่า!

  1. ไปที่ Bitbucket repo ของเราที่สร้างขึ้นมาใหม่ ให้เลือกเมนู pipelines จากเมนูด้านข้างซ้ายมือครับ

ทำการ Enable เพื่อเปิดใช้งาน CI/CD pipeline (pipeline มันก็คือลำดับการทำงาน) ให้เรียบร้อย

ตัว pipeline ที่ผมสร้างไว้ก็จะถูกปลุกให้ทำงานขึ้นมา

เห้ย… แล้วทีนี้มันถูกตั้งค่า pipeline ไว้ที่ไหนกันล่ะ?

เอาล่ะทีนี้ให้ลองดูที่ไฟล์ bitbucket-pipelines.yml ครับ

pipelines:

default:

- step:

    script:

      - ls

นี่คือการตั้งค่าว่าที่ผมทำไว้ โดยมันคือการบอกว่าเราอยากจะสั่งให้ทำงานอะไรนั่นเอง ทีนี้ให้ลองดูไล่มาตรง default ครับ อันนี้คือการระบุว่าโดยทั่วไปไม่ว่าจะ branch ไหนให้มาทำงานในส่วนตรงนี้ด้วย ซึ่งมันสามารถประกอบไปด้วยหลาย ๆ Step

มาที่ Step ครับ มันก็คือการระบุขั้นตอนว่ามี pipeline อะไรบ้างที่ต้องการให้ทำงาน (แต่ละ step จะรัน container แยกกันน่ะตามคอนเซปต์)
ซึ่งแต่ละ Step นั้นก็ประกอบไปด้วย script ได้หลายอัน ๆ และใน script ก็จะมี command ต่าง ๆ ที่เราจะใช้

สรุป ตามลำดับการทำงานและลักษณะของมันก็ตามนี้ครับ

pipelines > default > step > script > command

สำหรับรายละเอียดการใช้งาน แนะนำให้ดูต่อที่ลิงค์ด้านล่างนี้น่ะครับ

Configure bitbucket-pipelines.yml - Atlassian Documentation
Pull changes from your Git repository on Bitbucket Cloud
confluence.atlassian.com

โดย Concept ของ CI/CD ใน bitbucket จะแปลงค่า configuration พวกนี้และสั่งทำงานผ่าน Docker container ครับ หรือ ชื่อที่เท่ห์ในปัจจุบันก็คือ “pipeline as a code”

2. เราจะลองเชื่อมต่อ Bitbucket pipeline ไปที่เครื่อง Server digitalocean ที่เราสร้างไว้ในตอนแรกกันดูครับ

ให้ลองแก้ไขไฟล์ bitbucket-pipelines.yml ให้เป็นการตั้งค่าใหม่ตามนี้เลยครับ

ซึ่งรอบนี้ผมได้เปลี่ยนเป็นสนใจการกระทำที่เกิดกับ master branch เท่านั้นครับ โดยจะทดลองต่อ ssh ไปที่ {YOUR IP} ข้างต้นที่เราสร้างไว้ก่อนหน้า และ ทดลองสั่ง ls เพื่อดูว่ามีไฟล์อะไรบ้างบน server ที่เราสามารถเชื่อมต่อไปได้เพื่อเป็นการทดสอบ (อันนี้ ls คนละผลกันกับก่อนหน้าน่ะครับ ก่อนหน้าคือการสั่งให้ ls ดูใน step นั้นเท่านั้น)

ทีนี้ลองแก้ไขไฟล์เสร็จแล้ว ก็ลองมา push master กันดูครับ

.

ผ่างง….

.

แน่นอนครับว่ามันต่อเข้าเครื่องไม่ได้ ฮ่า ๆ
แต่อย่างน้อยเราก็พอทราบได้ว่าค่าที่เราตั้งไว้ในการสนใจแค่ master branch ก็สามารถทำงานได้ถูกต้องน่ะครับ

ต่อไปเราจะแก้ปัญหานี้โดยการนำเอา public key ของ bitbucket pipeline นั้นมาใส่ไว้ใน server ของเรา เพื่อทำให้ตัวเครื่อง pipeline สามารถเชื่อมต่อเข้ามาที่ server ได้ โดยไม่ต้องใช้รหัสผ่านครับ สำหรับเนื้อหาแนวการทำคุณสามารถดูแบบเต็ม ๆ ได้ที่นี่

กลับมาที่ Repo ของเราครับ จากนั้นไปที่ Settings > PIPELINES > SSH keys

กด “Generate keys” เพื่อสร้าง public key ของเครื่องที่รัน pipeline ของเราครับ

Public key ที่เราจะนำเอาไปใช้ต่อได้

ก่อนจะไปเพิ่ม public key ใน server ให้ทำการเพิ่ม known host เสียก่อนน่ะครับ เสมือนเป็นการตรวจสอบอีกชั้นว่า server ที่เราไปเชื่อมต่อนั้นน่ะมันเป็นของจริง ไม่โดน hack แบบ man in the middle

ทำการกรอก ip ของ server เราเอง จากนั้นกด “Fetch” เพื่อให้ได้ finger print มา จากนั้นให้กด “Add” อีกทีนึง ก็เป็นอันเสร็จเรียบร้อยในขั้นตอนนี้ครับ

ขั้นตอนต่อไปเราต้องนำเอา Public key ที่ได้มาเมื่อสักครู่ไปใส่ไว้ที่ server ของเราครับ ก็ทั่วไปคือเราต้อง ssh เข้าไปในเครื่องให้ได้ จากนั้นแก้ไขไฟล์ ~/.ssh/authorized_keys โดยเพิ่ม public key ที่ได้มาลงไป

ประมาณนี้แล้วกัน

จากนั้น ทำการบันทึกไฟล์ให้เรียบร้อย

กลับมาที่ pipeline ของเราที่มันพังในตอนแรก ให้กด “Rerun” เพื่อเรียกการทำงานขึ้นมาใหม่อีกทีนึง

ผลที่ออกมา… สวยงามครับ

เราสามารถเชื่อมต่อและเรียกใช้งานคำสั่ง ls บนเครื่อง server ได้

3. อัพโหลดการเปลี่ยนแปลงของไฟล์ล่าสุดไปที่เครื่อง Server ด้วย rsync โดยแก้ไขไฟล์ตามนี้

บรรทัดที่ 6 คือการอัพโหลดไฟล์ทั้งหมดไปที่ /web บน server จากนั้นบรรทัดถัด ๆ มาก็คือการ ssh เข้าไปในเครื่อง server แล้วทดสอบ ls ดูในโฟลเดอร์ /web ว่ามีไฟล์ของเราใหม่

ซึ่งผลลัพธ์นั้นควรจะต้องแสดงออกมาได้อย่างถูกต้อง คือมีไฟล์ถูกย้ายไปไว้บน server ในโฟลเดอร์ /web

4. สั่งรีสตาร์ทด้วยคำสั่ง docker-compose (คำสั่งนี้จะไปเรียกใช้งานการตั้งค่าจาก docker-compose.yml ครับ สำหรับรายละเอียดผมขอข้ามไปน่ะครับ)

ในการตั้งค่ารอบนี้ เราจะทำการปรับแต่งนิดหน่อย โดยเปลี่ยนจากคำสั่ง ls เป็นบรรทัดที่ 9 และ 10 สำหรับการ docker-compose down คือการสั่งปิด service ทั้งหมด จากนั้นบรรทัดถัดมา docker-compose up คือการสั่ง build image ใหม่และรัน service นั้นขึ้นมา ( — build คือการให้ build image ใหม่ทุกครั้ง และ -d คือการสั่งรันแบบ background mode)

ซึ่งความจริงแล้วมันอาจจะไม่จำเป็นต้องรันแบบนี้น่ะครับ คุณอาจจะ build เป็น image มาตั้งแต่ใน bitbucket pipeline แล้ว ในส่วนของ server เองก็แค่ไปดึงเอา image มารันใหม่เลยไม่ต้อง build แล้ว แต่ที่ทำกันวันนี้แค่ลองจำลองรูปแบบการทำงานที่เกิดขึ้นทำให้เห็นภาพมากยิ่งขึ้นน่ะครับ

เมื่อเราทดลองแก้ไขไฟล์ index.html และ สั่ง push master สิ่งที่ออกมาเมื่อเราทดลองใช้งานผ่าน {YOUR IP} ก็จะอัพเดทตามด้วยแบบ Automate แล้วครับ ~

มาถึงตอนนี้เราก็ได้พอรู้ขั้นตอนการใช้งานแบบเบื้องต้นแล้วน่ะครับ กลับมาที่ราคาการใช้งานกันครับ ของดีไม่ฟรีแน่นอน เมื่อเราลองดูจะพบว่ามันนับราคาตามจำนวนเวลาที่เราใช้ในการ build ทั้งหมดต่อเดือน (build minutes) ซึ่งราคาก็ค่อนช้างสูงเอาเรื่องเหมือนกัน ดังนั้นส่วนตัวแล้วผมมองว่ามันเหมาะกับทีมเล็ก ๆ หรือ ไม่ก็ใช้กับโปรเจกต์ส่วนตัวมากกว่า หรือ อีกเคสคือเรายังไม่อยากใช้เวลาในการ maintain CI Server เอง ซึ่งถ้าจะเล่นเองแบบลงเองเจ็บเองในเครื่องของเราเองแล้ว มันก็มีตัว self-hosted ให้นำไปติดตั้งได้ครับ เจ้าที่ดัง ๆ นอกจากเจ้านี้ก็มี Gitlab และ Jenkins ไว้มีโอกาสหน้าผมได้เจอเทคนิคใหม่ก็จะนำเอามาแชร์กันอีกทีครับ

https://bitbucket.org/product/pricing