มาทำ Versioning + GitOps + K8s บน Monorepo กันเถอะ! Part I

Versioning and GitOps pipeline

For English version, please visit the link https://github.com/saenyakorn/monorepo-versioning-gitops/

📚 รวมเล่ม

ตอนแรกผมหนักใจมากเลยว่าจะเขียนเนื้อหาเยอะขนาดนี้ยังไงดี 555555 ก็เลยจะขอแบ่งออกเป็น 5 parts ย่อย ๆ ละกันครับ

  1. ปฐมบท — เป็นตอนที่จะเล่าปัญหาและความสำคัญของการทำ Monorepo, versioning และ GitOps ก่อน และจะทิ้งท้ายด้วย challenge ที่จะต้องเจอถ้าจะทำสิ่งนี้
  2. Versioning ฝึกหัด— เป็นตอนที่จะเล่าตั้งแต่ต้นเลยว่า versioning คืออะไร ทำยังไง และถ้าจะทำบน Monorepo จะมี tools ไหนมาช่วยได้บ้าง
  3. Development process ทั้งระบบ — เป็นตอนที่จะเล่าถึงวิธีการทำงานร่วมกับ Gitflow และเราจะ apply versioning กับ GitOps ไว้ตรงไหนของ workflows
  4. Implementation ของ workflow ทั้งหมด — จะเป็นตอนที่จะอธิบายว่าถ้าอยากได้ development process ในตอนที่ 3 แบบ automate จะต้องทำยังไง และแต่ละ step ของ workflow คืออะไรบ้าง
  5. 🚧 อยาก pre-release ต้องทำยังไง — จะเป็นตอนที่จะมาอธิบายเรื่อง automated pre-release เป็นพิเศษเลย เพราะเป็นเรื่องที่ซับซ้อนและยุบยับพอสมควร

⭐️ เกริ่นนำ

เชื่อว่าหลายคนที่หลงเข้ามาในบทความนี้ น่าจะมีข้อสงสัยประมาณว่า “Versioning, GitOps และ Monorepo มันเกี่ยวข้องกันได้ยังไง” หรือถ้าใครรู้จัก concept พวกนี้อยู่แล้ว ก็อาจจะสงสัยว่า “แล้วมันจะเอามารวมกันได้อย่างไรล่ะ?”

แม้ว่าในยุคนี้หลาย ๆ บริษัทจะเริ่มนำ GitOps concept มาใช้กันบ้างแล้ว เพื่อให้ deployment process ทำได้เร็วยิ่งขึ้น และสามารถ control ทุกอย่างได้ผ่าน Git แต่สำหรับ versioning เองก็ยังเป็นโจทย์ยากอยู่สำหรับหลาย ๆ บริษัท

ในบทความนี้เราจะพาเพื่อน ๆ ไปทำความรู้จักกับ development process และ automated pipelines สำหรับการทำ versioning และ deployment แบบหรูหราหมาเห่ากันตั้งแต่ตั้นน้ำยันปลายน้ำกันเลย

บทความนี้ออกจะมีเนื้อหาเยอะสักหน่อย เพราะเป็นบทเกริ่นนำ ถ้าเพื่อน ๆ ต้องการมาดู implementation สามารถไปตอนถัดไปได้เลยย

📜 เนื้อหาวันนี้

โดยรวม เราจะเล่าให้เพื่อน ๆ เห็นภาพตรงกันก่อนว่าปัญหาคืออะไร และความท้าทายของการทำสิ่งนี้คืออะไร และสิ่งนี้จะช่วยให้บริษัทหรือทีมทำงานได้เร็วขึ้นยังไง โดยเนื้อหาจะมีดังนี้เลยย

  1. Versioning สำคัญไฉน
  2. GitOps คืออะไร แล้วมีประโยชน์ยังไง?
  3. เปรียบเทียบ Monorepo vs others
  4. ความยากของการ versioning และ deploy บน Monorepo
  5. บทส่งท้าย
  6. ตัวอย่างตอนต่อไป! (ฝึกหัด versioning)

🔥 Versioning สำคัญไฉน?

ด้วยความที่ตลาดเทคโนโลยีแข่งขันกันสูง บริษัทต่าง ๆ จึงต้องการให้ feature ใหม่ ๆ ออกไปให้ผู้ใช้ได้ใช้ให้เร็วที่สุด ซึ่งหลายครั้ง developers เลยมีเวลาพัฒนาไม่พอบ้าง หรือมีเวลา test ไม่พอบ้าง

เลยทำให้หลายครั้งเวลาเอาขึ้น production แล้วก็จะเจอเหตุการณ์ “บุ้มม บัคกระจุยกระจาย” ได้

วิธีการแก้ปัญหาเฉพาะหน้าที่เร็วที่สุดคือการ “Rollback” กลับไปยัง version ก่อนที่จะพัง

แต่… แล้ว version ก่อนหน้าคืออะไรล่ะ? หลาย ๆ บริษัทไม่ได้ tag เอาไว้ว่า stable version มันอยู่ที่ commit ไหน เลยทำให้การ rollback ทำได้ล่าช้า ตัวอย่างเช่นรูปด้านล่าง เราอาจจะไม่รู้เลยว่า commit ไหนที่ถูก deploy ไปก่อนล่าสุด

จะ rollback กลับไปตอนไหนดี

นอกจากนี้ เพื่อน ๆ จำได้หรือเปล่าว่าในการ release แต่ละครั้ง เราเพิ่ม feature หรือแก้ bug อะไรไปบ้าง? ถ้าให้ต้องไล่ย้อนดู commit message ย้อนหลังทั้งหมดก็จะเสียเวลาเกินไป แถมถ้าเป็นคนทั่วไปไม่ใช่ contributors พวกเขาแทบจะไม่มีทางรู้ได้เลยว่า apps หรือ packages ที่เขาใช้มีอะไรเปลี่ยนไปบ้าง

จะดีกว่ามากเลย ถ้ามีพื้นที่อธิบายว่าการ release รอบนี้มีอะไรเปลี่ยนไปบ้าง เช่น ใน GitHub Release Notes หรือ CHANGELOG

คงจะดีถ้ามี release note สักหน่อย

ถึงจุดนี้ เพื่อน ๆ น่าจะพอเห็นภาพคร่าว ๆ กันแล้วว่าทำไมถึงควรจะทำ versioning แล้วมันทำยังไงล่ะ? ซึ่งเดี๋ยวผมจะพูดถึงในบทถัด ๆ ไป

อ่าน ความสำคัญของ Versioning ต่อได้ที่นี่เลย

❓ GitOps คืออะไร แล้วมีประโยชน์ยังไง?

GitOps เป็น concept ที่ถูกพูดถึงครั้งแรกในปี 2017 ซึ่งเป็น concept ที่กล่าวถึงการใช้ Git เป็น single source of truth ที่จะเอาไว้ declare ระบบที่อยู่บน cloud เช่น สมมติเราเก็บ Infrastructure-as-a-code (IaaC) ไว้ใน Git แล้วเมื่อไรก็ตามที่เราแก้ IaaC นั้น ระบบจริง ๆ ที่อยู่บน cloud ก็จะถูกแก้ไปด้วย และด้วย feature มากมายของ GitHub เราสามารถเปิด pull request ก่อนเพื่อให้ทีมช่วยกันตรวจสอบ และยังสามารถย้อนประวัติได้ด้วยว่าใครแก้อะไรไปบ้าง

เรียกได้ว่าเป็นการใช้ feature ของ Git และ GitHub ให้เป็นประโยชน์สุด ๆ !

ซึ่งในที่นี้เราจะเน้นไปในเรื่องการเก็บ Kubernetes manifest files ไว้ใน GitOps repository ซึ่งจะกล่าวต่อในบทถัด ๆ ไป

🌪️ เปรียบเทียบ Monorepo vs others

ถ้าจะพูดถึง Monorepo เทียบกับอย่างอื่นแล้วเนี่ย ผมอาจจะต้องขอย้อนประวัติศาสตร์กลับไปนิดหน่อย

Monolith

ในสมัยที่ modular framework (การพัฒนา software แบบแบ่งเป็น module ย่อย ๆ ) ยังไม่บูม และ hardware ก็ไม่ได้เยอะแยะอะไร นักพัฒนาก็เชื่อกันว่าการพัฒนา feature ทั้งหมดไว้ใน app เดียว repository เดียวจะเป็นวิธีที่กินทรัพยากรเครื่องน้อยที่สุด และจะทำให้พัฒนา software ได้เร็วที่สุด ซึ่งก็ถูกต้องนะ แต่มันจะเป็นได้แค่ช่วงแรกเท่านั้นแหละ…

พอเวลาผ่านไปได้สักพัก บริษัทก็เริ่มรับ developer เยอะขึ้น ส่วน feature ของ app ก็เริ่มเยอะขึ้นจน module นั้นผูกกับ module นี้เยอะแยะไปหมดทำให้เกิด coupling เยอะเกินจนไม่จำเป็น ทำให้ developer หน้าใหม่ที่เพิ่งเข้ามาก็ต้องมานั่งงมว่า “ถ้าจะใช้อันนี้ ต้องไปเรียกอันนั้น แล้วต้องไปดูอันนู้นด้วย เพราะ logic ผูกกัน” ทำให้ developer คนใหม่ ๆ ใช้เวลากับการ “อ่าน” Legacy code จนไม่ได้พัฒนา feature สักที

ในขณะที่ developer ที่อยู่ตั้งแต่ day 1 ก็ไม่กล้าแตะ code เก่า ๆ เพราะ logic พันกันยุบยับ จะรื้อก็กลัวพัง

การที่ module พันกันไปมาเรียกว่ามี coupling เยอะ ซึ่ง Software ที่ดี ควรจะมี coupling ให้น้อยที่สุด แต่มี cohesion (ความแน่นแฟ้นและเป็นหนึ่งเดียวใน module) เยอะ ๆ แทน

โดยสรุปการพัฒนาแบบ monolith แม้จะมีข้อดีคือ

  • พัฒนา software ได้เร็วในช่วงแรก

แต่ในช่วงหลังจะทำให้

  • Development speed ช้าลงไปมาก ๆ
  • Scale software ได้ยาก
  • ทำให้ apply technology ใหม่ ๆ เข้าไปยากมาก

ดังนั้น เพื่อให้ปัญหาดังกล่าวเกิดน้อยลง เราเลยจะไปสู่ยุคถัดไปนั่นคือ modular framework ที่ จะแบ่ง business logic ออกเป็นหลาย ๆ ส่วนให้แต่ละ module ทำงานเฉพาะส่วนที่ตัวเองรับผิดชอบ ทำให้ลดโอกาสที่จะมี coupling เยอะ ๆ ได้ โดยการจะแบ่ง logic ออกเป็นหลาย ๆ ส่วน ซึ่งทำได้ 2 วิธี คือ Polyrepo กับ Monorepo

ตามอ่าน ความแตกต่างระหว่าง Monolith กับ Modular ได้ที่นี่เลย

Modular + Polyrepo

ก็คือเราจะให้ 1 repository = 1 service ไปเลย ซึ่งแต่ละ service ก็จะทำหน้าที่เฉพาะที่แตกต่างกันไป แล้วพอเอามาประกอบกันกลายเป็น microservices architecture ได้ง่าย ๆ เรียกได้ว่าเป็นวิธีที่ตรงไปตรงมามาก ๆ

👍 ข้อดี

  1. Setup ง่ายมาก เพราะ 1 service 1 repository เป็นสิ่งที่หลาย ๆ คนน่าจะทำกันเป็นปกติอยู่แล้ว

👎 ข้อเสีย

  1. การสร้าง repository นึงขึ้นมาต้อง setup นู้นนี่เยอะมาก เช่น Lint, Prettier, Husky, CI/CD, Secrets, Branch Protection ,etc. และการจะ enforce ให้ทุก repository ใช้แบบเดียวกันยากมาก ๆ
  2. จากข้อเมื่อกี้ ลองจินตนาการว่าเราเปลี่ยน setup นิดเดียว (เช่นเพิ่ม ESLint rule นึง หรือแก้ GitHub Action นิดหน่อย) แล้วสมมติว่าเรามีสัก 10 repositories บอกเลยว่างานนี้มีกระอักเลือด
  3. Share code ระหว่างแต่ละ service ไม่ได้เลย เช่น สมมติจะ share utility function ข้าม services ก็ทำไม่ได้ เพราะอยู่กันคนละ Repository (Workaround คือเอา utility function ขึ้น NPM registry แต่ก็ทำได้ยากอีก)
  4. ถ้าต้องแก้ bug เดียว ที่ต้องแตะสัก 10 repositories ก็ต้อง commit อย่างต่ำ 10 ครั้งเปิด Pull Request อย่างต่ำ 10 ครั้ง ทั้ง ๆ ที่ทั้งหมดทำ 1 commit + 1 pull request ก็พอ

Modular + Monorepo

จะเป็น repository เดียว ที่รวมทุก apps และ packages เข้าไว้ด้วยกัน แต่ Monorepo != Monolith นะ

สำหรับใครที่เพิ่งจะเคยรู้จัก concept นี้ แนะนำให้อ่านอันนี้ครับ https://monorepo.tools/

👍 ข้อดี

  1. Share code ข้ามกันระหว่าง packages และ apps ได้ง่ายมาก เพราะอยู่ใน repository เดียวกัน (แต่การ share code ระหว่าง apps ห้ามทำเด็ดขาด บาปมาก!)
  2. ไม่ต้องกังวลว่าจะเกิด coupling เยอะ เพราะว่า share code ข้าม app ไม่ได้เลย
  3. ทำ microservices architecture ได้ง่ายกว่าเดิมมาก เพราะสามารถ share type definition ที่ generate จากอะไรก็แล้วแต่ เช่น protobuf ได้เลย ไม่ต้องเอาขึ้น NPM registry
  4. Setup ทุกอย่างในที่เดียว เช่น Prettier, ESLint, CI/CD, Secrets, etc. มั่นใจได้เลยว่าทุก apps/packages จะมี coding styles คล้าย ๆ กัน (หรือจะ customize config per app ก็ได้เหมือนกัน)
  5. ด้วยความที่ setup ง่ายมาก ๆ ทำให้ขึ้น apps หรือ packages ใหม่ได้ง่ายมาก ๆๆ แค่ copy + paste folder

👎 ข้อเสีย

  1. Setup ยากมาก ๆ เพราะ root working directory คือ workspace ไม่ใช่แต่ละ apps ซึ่งทำให้ automated workflows ต้องดูเรื่อง path ดี ๆ
  2. Repository จะใหญ่มากกก ถ้าเริ่มมี apps และ packages เยอะ

ความยากของการ versioning และ deploy บน Monorepo

Versioning และ deployment บน Monorepo ต้องขอบอกเลยว่ายากมาก และเป็นเหตุลส่วนใหญ่ที่คนไม่ใช้ Monorepo กัน เช่น

  1. สมมติว่าในตอนที่เราจะทำ versioning และสมมติว่าเรามี 10 apps อยู่ใน repository แล้วเรามี changes แค่ 2 apps เท่านั้น แล้ว workflow จะรู้ได้ไงว่า มีแค่ 2 apps นี้เท่านั้นที่ต้อง release (bump version) และ deploy
  2. สมมติว่าในมี app X และ app Y ใช้ internal packages K และสมมติว่า package K มีการแก้ไข และเราต้องการจะ bump version แปลว่า app X และ app Y ก็ได้รับกระทบไปด้วย ก็ต้อง bump version เหมือนกัน จะรู้ได้ไงว่าอะไรกระทบอะไร แล้วจะ bump version เป็นอะไร
dependencies graph เวลาเกิด changes และใครได้รับผลกระทบบ้าง (affected)

ซึ่งปัญหาเหล่านี้เดี๋ยวเราจะมาชี้แจงแถลงไขในตอนหน้า! โปรดติดตามตอนต่อไป!!

🙏 บทส่งท้าย

วันนี้เราได้คุยกันไปในเรื่อง

  1. Versioning สำคัญไฉน — เพื่อให้การ rollback ทำได้ง่ายมากขึ้น และเพื่อระบุด้วยว่าการ release นั้น ๆ มี feature, bug fixes หรือ breaking change
  2. GitOps คืออะไร แล้วมีประโยชน์ยังไง?— เพื่อใช้ Git เป็น single source of truth และใช้มันเพื่อควบคุมระบบ
  3. เปรียบเทียบ Monorepo vs others — Monorepo มีข้อดีเยอะมากและช่วยทำให้ทีมทำงานได้เร็วขึ้นแต่แลกกับการ setup ที่โคตรยาก
  4. ความยากของการ Versioning และ Deploy บน Monorepo — เพื่อน ๆ น่าจะพอเห็น challenges กันบ้างแล้ว แต่เดี๋ยวมาเล่า solution ในตอนหน้า

หวังว่า blog นี้จะเป็นประโยชน์ไม่มากก็น้อย สำหรับคนที่ต้องการทำ automated full pipeline สำหรับการ versioning, deployment และ GitOps บน Monorepo ถ้าคิดว่าบทความนี้เป็นประโยชน์อย่าลืมแชร์ให้เพื่อน ๆ อ่านกันด้วยล่ะ! ❤️

ขอบคุณทุกท่านที่ตามอ่านมาจนถึงตรงนี้นะครับ อย่าลืมติดตามตอนต่อไปกันด้วยล่ะ!

▶️ ตัวอย่างตอนต่อไป

ตอนต่อไป เราจะพาเพื่อน ๆ ไปลองทำ versioning บน Monorepo กัน เราจะแก้ปัญหาเรื่อง internal dependecies ยังไง ถ้า bump version ของ package หนึ่งแล้ว app ที่มีการใช้ package นั้นจะต้อง bump version ด้วยหรือเปล่า ตอนหน้ามีคำตอบ!

ติดตามตอนต่อไปได้ที่นี่เลยย

Appendix

ทั้งนี้ automated full pipelines demo ของการ versioning, deployment และ GitOps บน Monorepo อยู่ที่นี่แล้ว https://github.com/saenyakorn/monorepo-versioning-gitops หากใครสนใจสามารถเข้าไปอ่านได้เลยยย (จะเข้าไปก๊อปโค๊ตก็ได้ ตามอัธยาศัย)

ปล. ถ้าชอบก็อย่าลืมกด Star เป็นกำลังใจให้ด้วยนะครับ 🫠

--

--