11

ENABLE AND PRACTICE CONTINUOUS INTEGRATION (เปิดใช้งานและปฏิบัติการ Continuous Integration)

I ในบทที่แล้ว เราได้สร้างแนวปฏิบัติในการทดสอบอัตโนมัติเพื่อให้แน่ใจว่านักพัฒนาได้รับ Feedback ที่รวดเร็วเกี่ยวกับคุณภาพของงานที่ทำ ซึ่งสิ่งนี้ยิ่งมีความสำคัญมากขึ้นเมื่อจำนวนนักพัฒนาและจำนวน Branch ที่พวกเขาทำงานในระบบ Version Control เพิ่มสูงขึ้น

ความสามารถในการ "สร้าง Branch" ในระบบ Version Control ถูกสร้างขึ้นมาโดยมีวัตถุประสงค์หลักเพื่อให้นักพัฒนาสามารถทำงานในส่วนต่างๆ ของระบบซอฟต์แวร์ไปพร้อมกันได้โดยไม่ต้องเสี่ยงที่นักพัฒนาแต่ละคนจะ Commit การเปลี่ยนแปลงที่อาจทำให้ Trunk (ซึ่งบางครั้งก็เรียกว่า master หรือ mainline) ไม่เสถียรหรือเกิดข้อผิดพลาด *

อย่างไรก็ตาม ยิ่งปล่อยให้นักพัฒนาทำงานใน Branch ของตัวเองอย่างโดดเดี่ยวนานเท่าไหร่ การรวมและ Merge การเปลี่ยนแปลงของทุกคนกลับเข้าสู่ Trunk ก็ยิ่งยากขึ้นเท่านั้น ที่จริงแล้ว ความยากในการรวมการเปลี่ยนแปลงเหล่านั้นจะเพิ่มขึ้นแบบทวีคูณเมื่อจำนวน Branch และจำนวนการเปลี่ยนแปลงในแต่ละ Code Branch เพิ่มมากขึ้น

ปัญหาการรวมระบบส่งผลให้เกิดการทำงานซ้ำจำนวนมากเพื่อให้กลับมาอยู่ในสถานะที่พร้อม Deploy ได้ รวมถึงการเปลี่ยนแปลงที่ขัดแย้งกันซึ่งต้องถูก Merge ด้วยตนเอง หรือ Merge ที่ทำให้การทดสอบอัตโนมัติหรือ Manual Test ของเราเสีย ซึ่งโดยปกติแล้วต้องใช้ความพยายามของนักพัฒนาหลายคนในการแก้ไขให้สำเร็จ และเนื่องจากการรวมระบบนั้นทำกันในช่วงท้ายของโครงการเป็นประจำ ซึ่งใช้เวลานานกว่าที่วางแผนไว้มาก เราจึงมักถูกบังคับให้ตัดมุมเพื่อให้ทันกำหนดการวางจำหน่าย

สิ่งนี้ก่อให้เกิดวงจรขาลงอีกครั้ง: เมื่อการ Merge โค้ดเป็นเรื่องที่เจ็บปวด เรามักจะทำมันน้อยลง ซึ่งทำให้การ Merge ในอนาคตยิ่งแย่ลงไปอีก Continuous Integration ถูกออกแบบมาเพื่อแก้ปัญหานี้ โดยทำให้การ Merge เข้าสู่ Trunk กลายเป็นส่วนหนึ่งของงานประจำวันของทุกคน

HP's LaserJet Firmware (2014)

ขอบเขตของปัญหาที่ Continuous Integration สามารถแก้ไขได้นั้นกว้างอย่างน่าประหลาดใจ รวมถึงตัวแนวทางแก้ไขเอง ก็แสดงให้เห็นได้จากประสบการณ์ของ Gary Gruver ในตำแหน่งผู้อำนวยการฝ่ายวิศวกรรมของแผนก LaserJet Firmware ของ HP ซึ่งสร้าง Firmware ที่ใช้ในสแกนเนอร์ เครื่องพิมพ์ และอุปกรณ์มัลติฟังก์ชันทั้งหมดของบริษัท 1

ทีมงานประกอบด้วยนักพัฒนาสี่ร้อยคนกระจายตัวอยู่ทั่วทั้งสหรัฐอเมริกา บราซิล และอินเดีย แม้ทีมงานจะมีขนาดใหญ่ แต่พวกเขากลับทำงานช้าอย่างมาก เป็นเวลาหลายปีที่พวกเขาไม่สามารถส่งมอบฟีเจอร์ใหม่ๆ ได้เร็วเท่าที่ธุรกิจต้องการ

Gruver บรรยายปัญหาดังนี้ "ฝ่ายการตลาดจะเข้ามาหาเราพร้อมกับไอเดียเป็นล้านๆ เพื่อทำให้ลูกค้าตื่นเต้น และเราก็แค่บอกพวกเขาว่า 'จากลิสต์ของคุณ เลือกสองอย่างที่คุณอยากได้ในหกถึงสิบสองเดือนข้างหน้านี้'" 2

พวกเขาทำการปล่อย Firmware เพียงสองครั้งต่อปี โดยเวลาส่วนใหญ่หมดไปกับการพอร์ตโค้ดเพื่อรองรับผลิตภัณฑ์ใหม่ Gruver ประมาณว่าเวลาเพียง 5% เท่านั้นที่ใช้ไปกับการสร้างฟีเจอร์ใหม่—เวลาที่เหลือหมดไปกับงานที่ไม่ก่อให้เกิดผลผลิตซึ่งเกี่ยวข้องกับ Technical Debt เช่น การจัดการ Code Branch หลายๆ ชุดและการทดสอบด้วยตนเอง ดังที่แสดงด้านล่าง: 3

• 20% กับการวางแผนอย่างละเอียด (ปริมาณงานที่ต่ำและ Lead Time ที่สูงถูกตีความผิดว่าเกิดจากการประมาณการที่ผิดพลาด ดังนั้น เพื่อหวังว่าจะได้คำตอบที่ดีขึ้น พวกเขาจึงถูกขอให้ประมาณงานอย่างละเอียดมากขึ้น)

• 25% กับการพอร์ตโค้ด ซึ่งทั้งหมดถูกดูแลแยกกันใน Code Branch ที่แตกต่างกัน

• 10% กับการรวมโค้ดระหว่าง Branch ของนักพัฒนา

• 15% กับการทดสอบด้วยตนเอง

Gruver และทีมของเขาสร้างเป้าหมายในการเพิ่มเวลาที่ใช้กับนวัตกรรมและฟังก์ชันการทำงานใหม่เป็นสิบเท่า ทีมงานหวังว่าเป้าหมายนี้จะสำเร็จได้ผ่าน: 4

• Continuous Integration และ Trunk-Based Development

• การลงทุนครั้งใหญ่ใน Test Automation

• การสร้าง Hardware Simulator เพื่อให้สามารถรัน Tests บน Virtual Platform ได้

• การจำลองความล้มเหลวของ Test บน Workstation ของนักพัฒนา

• สถาปัตยกรรมใหม่ที่รองรับการรันเครื่องพิมพ์ทั้งหมดจาก Build และ Release ร่วมกัน

ก่อนหน้านี้ ผลิตภัณฑ์แต่ละสายการผลิตต้องใช้ Code Branch ใหม่ โดยแต่ละรุ่นจะมี Firmware Build ที่ไม่ซ้ำกันพร้อมความสามารถที่กำหนดในขั้นตอนการ Compile † สถาปัตยกรรมใหม่จะมีนักพัฒนาทุกคนทำงานใน Codebase ร่วมกัน โดยมี Firmware Release เดียวที่รองรับ LaserJet ทุกรุ่นที่สร้างจาก Trunk โดยความสามารถของเครื่องพิมพ์จะถูกกำหนดขณะ Runtime ผ่านไฟล์การกำหนดค่า XML

สี่ปีต่อมา พวกเขามี Codebase เดียวที่รองรับผลิตภัณฑ์ LaserJet ทั้งหมดยี่สิบสี่สายการผลิตของ HP ซึ่งพัฒนาบน Trunk Gruver ยอมรับว่า Trunk-Based Development ต้องมีการเปลี่ยนแปลง Mindset ครั้งใหญ่ 6 วิศวกรคิดว่ามันจะไม่มีทางเวิร์ก แต่เมื่อพวกเขาเริ่มแล้ว ก็ไม่สามารถจินตนาการถึงการกลับไปใช้วิธีเดิมได้อีก ตลอดหลายปีที่ผ่านมา มีวิศวกรหลายคนลาออกจาก HP และพวกเขาจะโทรกลับมาบอก Gruver ว่าการพัฒนาซอฟต์แวร์ในบริษัทใหม่ของพวกเขาล้าหลังแค่ไหน โดยชี้ให้เห็นว่ามันยากแค่ไหนที่จะทำงานได้อย่างมีประสิทธิภาพและปล่อยโค้ดที่ดีเมื่อไม่มี Feedback ที่ Continuous Integration มอบให้ 7

อย่างไรก็ตาม Trunk-Based Development ทำให้พวกเขาต้องสร้าง Automated Testing ที่มีประสิทธิภาพมากขึ้น Gruver กล่าวว่า "หากไม่มี Automated Testing แล้ว Continuous Integration ก็เป็นแค่วิธีที่เร็วที่สุดในการได้กองขยะขนาดใหญ่ที่ไม่เคย Compile หรือรันได้อย่างถูกต้อง" 8 ในตอนแรก รอบการทดสอบด้วยตนเองทั้งหมดใช้เวลาหกสัปดาห์

เพื่อให้ Firmware Build ทั้งหมดได้รับการทดสอบโดยอัตโนมัติ พวกเขาลงทุนอย่างหนักในเครื่องจำลองเครื่องพิมพ์ และสร้าง Testing Farm ภายในหกสัปดาห์—ภายในไม่กี่ปี ก็มีเครื่องจำลองเครื่องพิมพ์สองพันเครื่องทำงานบน Rack Servers หก Rack ที่โหลด Firmware Build จาก Deployment Pipeline ของพวกเขา ระบบ CI ของพวกเขารันชุดทดสอบอัตโนมัติทั้งหมด ทั้ง Unit, Acceptance และ Integration Tests กับ Build จาก Trunk ตามที่อธิบายไว้ในบทที่แล้ว นอกจากนี้ พวกเขายังสร้างวัฒนธรรมที่หยุดงานทั้งหมดทุกครั้งที่นักพัฒนาทำให้ Deployment Pipeline เสีย เพื่อให้แน่ใจว่านักพัฒนาจะนำระบบกลับสู่สถานะสีเขียวได้อย่างรวดเร็ว 9

การทดสอบอัตโนมัติสร้าง Feedback ที่รวดเร็ว ทำให้นักพัฒนาสามารถยืนยันได้ทันทีว่าโค้ดที่พวกเขา Commit ไปนั้นทำงานได้จริง Unit Test จะรันบน Workstation ของพวกเขาภายในไม่กี่นาที และการทดสอบอัตโนมัติสามระดับจะรันในทุก Commit รวมถึงทุกสองและสี่ชั่วโมง ส่วนการ Regression Testing ครั้งสุดท้ายจะรันทุกยี่สิบสี่ชั่วโมง ในระหว่างกระบวนการนี้ พวกเขา: 10

• ลดการ Build เหลือวันละหนึ่งครั้ง และในที่สุดก็ทำได้สิบถึงสิบห้าครั้งต่อวัน

• จากที่มี Commit ประมาณยี่สิบครั้งต่อวันโดย Build Boss กลายเป็นมากกว่าหนึ่งร้อยครั้งต่อวันโดยนักพัฒนาแต่ละคน

• ทำให้นักพัฒนาสามารถเปลี่ยนแปลงหรือเพิ่มโค้ดระหว่างเจ็ดหมื่นห้าพันถึงหนึ่งแสนบรรทัดต่อวัน

• ลดระยะเวลาการทดสอบ Regression จากหกสัปดาห์เหลือหนึ่งวัน

ระดับผลผลิตนี้ไม่สามารถเกิดขึ้นได้ก่อนที่จะนำ Continuous Integration มาใช้ เมื่อการสร้าง Build ที่เป็นสีเขียวยังต้องใช้ความพยายามอย่างหนักเป็นเวลาหลายวัน ผลประโยชน์ทางธุรกิจที่ได้นั้นน่าทึ่งมาก: 11

• เวลาที่ใช้ในการขับเคลื่อนนวัตกรรมและการเขียนฟีเจอร์ใหม่เพิ่มขึ้นจาก 5% ของเวลานักพัฒนาเป็น 40%

• ต้นทุนการพัฒนาโดยรวมลดลงประมาณ 40%

• โปรแกรมที่อยู่ระหว่างการพัฒนาเพิ่มขึ้นประมาณ 140%

• ต้นทุนการพัฒนาต่อโปรแกรมลดลง 78%

สิ่งที่ประสบการณ์ของ Gruver แสดงให้เห็นคือ หลังจากใช้ Version Control อย่างครอบคลุมแล้ว Continuous Integration เป็นหนึ่งในแนวปฏิบัติที่สำคัญที่สุดที่ช่วยให้งานไหลเวียนอย่างรวดเร็วใน Value Stream ของเรา ช่วยให้ทีมพัฒนาหลายทีมสามารถพัฒนา ทดสอบ และส่งมอบคุณค่าได้อย่างอิสระ อย่างไรก็ตาม Continuous Integration ยังคงเป็นแนวปฏิบัติที่มีการโต้แย้ง

ส่วนที่เหลือของบทนี้จะอธิบายแนวปฏิบัติที่จำเป็นในการนำ Continuous Integration ไปใช้ รวมถึงวิธีรับมือกับข้อโต้แย้งทั่วไป

Small Batch Development และสิ่งที่เกิดขึ้นเมื่อเรา Commit โค้ดเข้าสู่ Trunk ไม่บ่อยนัก

ดังที่เราได้อธิบายไว้ เมื่อใดก็ตามที่มีการเปลี่ยนแปลงใน Version Control ที่ทำให้ Deployment Pipeline ของเราล้มเหลว เราจะรีบมารวมตัวกันเพื่อแก้ไขปัญหาโดยเร็ว ทำให้ Deployment Pipeline กลับสู่สถานะสีเขียว

อย่างไรก็ตาม ปัญหาสำคัญเกิดขึ้นเมื่อนักพัฒนาทำงานใน Private Branch ที่มีอายุยาวนาน (หรือที่เรียกว่า "Feature Branch") โดย Merge กลับเข้าสู่ Trunk เป็นครั้งคราวเท่านั้น ส่งผลให้ Batch Size ของการเปลี่ยนแปลงมีขนาดใหญ่ ดังที่เห็นในตัวอย่างของ HP LaserJet ผลที่ได้คือความโกลาหลและการทำงานซ้ำจำนวนมากเพื่อให้โค้ดกลับมาอยู่ในสถานะที่พร้อมปล่อย

Jeff Atwood ผู้ก่อตั้งเว็บไซต์ Stack Overflow และผู้เขียนบล็อก Coding Horror สังเกตว่า แม้จะมีกลยุทธ์การ Branch มากมาย แต่ทั้งหมดสามารถจัดอยู่ในสเปกตรัมต่อไปนี้: 12

• เพิ่มประสิทธิภาพการทำงานของแต่ละบุคคล: ทุกคนในโครงการทำงานใน Private Branch ของตนเอง ทุกคนทำงานอย่างอิสระ และไม่มีใครสามารถรบกวนงานของคนอื่นได้ อย่างไรก็ตาม การ Merge กลายเป็นฝันร้าย การทำงานร่วมกันกลายเป็นเรื่องยากเย็นจนน่าขัน—งานของทุกคนต้องถูก Merge กับงานของคนอื่นอย่างพิถีพิถันเพื่อที่จะได้เห็นแม้เพียงส่วนเล็กๆ ของระบบที่สมบูรณ์

• เพิ่มประสิทธิภาพการทำงานของทีม: ทุกคนทำงานในพื้นที่ส่วนกลางเดียวกัน ไม่มี Branch มีเพียงเส้นทางการพัฒนาที่ตรงยาวไม่ขาดสาย Commit นั้นง่าย แต่ Commit แต่ละครั้งสามารถทำให้ทั้งโครงการพังและหยุดความคืบหน้าทั้งหมดลงอย่างกระทันหัน

ข้อสังเกตของ Atwood ถูกต้องอย่างยิ่ง—หากพูดให้ชัดเจนยิ่งขึ้น ความพยายามที่ต้องใช้ในการ Merge Branch กลับเข้าด้วยกันให้สำเร็จนั้นเพิ่มขึ้นแบบทวีคูณเมื่อจำนวน Branch เพิ่มขึ้น ปัญหาไม่ได้อยู่ที่แค่งานซ้ำที่ "นรกของการ Merge" ("merge hell") สร้างขึ้น แต่ยังรวมถึง Feedback ที่ล่าช้าจาก Deployment Pipeline ของเราด้วย ตัวอย่างเช่น แทนที่การทดสอบประสิทธิภาพกับระบบที่รวมกันแล้วจะเกิดขึ้นอย่างต่อเนื่อง มันมักจะเกิดขึ้นเฉพาะตอนท้ายกระบวนการเท่านั้น

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

นี่คือผลข้างเคียงที่น่ากังวลอีกประการหนึ่งของการ Merge ที่มี Batch Size ขนาดใหญ่: เมื่อการ Merge เป็นเรื่องยาก เราจะมีความสามารถและแรงจูงใจน้อยลงในการปรับปรุงและ Refactor โค้ดของเรา เพราะการ Refactor มักจะทำให้เกิดงานซ้ำสำหรับคนอื่นๆ เมื่อสิ่งนี้เกิดขึ้น เราจะยิ่งลังเลที่จะแก้ไขโค้ดที่มี Dependencies กระจายทั่ว Codebase ซึ่ง (ที่น่าเศร้าคือ) อาจเป็นจุดที่ให้ผลตอบแทนสูงที่สุด

นี่คือวิธีที่ Ward Cunningham ผู้พัฒนา Wiki แรกของโลก อธิบาย Technical Debt ไว้แต่แรก: "เมื่อเราไม่ Refactor Codebase อย่างจริงจัง มันจะยากขึ้นที่จะทำการเปลี่ยนแปลงและบำรุงรักษาเมื่อเวลาผ่านไป ทำให้ความเร็วในการเพิ่มฟีเจอร์ใหม่ช้าลง" 13

การแก้ปัญหานี้เป็นหนึ่งในเหตุผลหลักเบื้องหลังการสร้าง Continuous Integration และ Trunk-Based Development เพื่อเพิ่มประสิทธิภาพการทำงานของทีมมากกว่าประสิทธิภาพของแต่ละบุคคล เราจะลงรายละเอียดเพิ่มเติมเกี่ยวกับการนำ Trunk-Based Development มาใช้ในส่วนถัดไปของหนังสือเล่มนี้

Adopt Trunk-Based Development Practices (นำแนวปฏิบัติ Trunk-Based Development มาใช้)

มาตรการรับมือของเราต่อการ Merge ที่มี Batch Size ขนาดใหญ่คือการนำ Continuous Integration และ Trunk-Based Development มาใช้ โดยนักพัฒนาทุกคนจะ Check-in โค้ดเข้าสู่ Trunk อย่างน้อยวันละครั้ง การ Check-in โค้ดบ่อยครั้งเช่นนี้ช่วยลด Batch Size ให้เหลือเพียงงานที่ทีมนักพัฒนาทั้งหมดทำในหนึ่งวัน ยิ่งนักพัฒนา Check-in โค้ดเข้าสู่ Trunk บ่อยเท่าไหร่ Batch Size ก็ยิ่งเล็กลงเท่านั้น และเราก็ยิ่งเข้าใกล้แนวคิดในอุดมคติของ Single-Piece Flow มากขึ้น

การ Commit โค้ดบ่อยครั้งสู่ Trunk หมายความว่าเราสามารถรันการทดสอบอัตโนมัติทั้งหมดบนระบบซอฟต์แวร์โดยรวม และรับการแจ้งเตือนเมื่อการเปลี่ยนแปลงบางอย่างทำให้ส่วนอื่นของแอปพลิเคชันเสียหายหรือรบกวนงานของนักพัฒนาคนอื่น และเนื่องจากเราสามารถตรวจจับปัญหา Merge ได้ตั้งแต่ยังเล็ก เราจึงสามารถแก้ไขได้เร็วยิ่งขึ้น

เราอาจกำหนดค่า Deployment Pipeline ให้ปฏิเสธ Commit ใดๆ (เช่น การเปลี่ยนแปลงโค้ดหรือสภาพแวดล้อม) ที่ทำให้เราหลุดจากสถานะที่พร้อม Deploy ได้ วิธีการนี้เรียกว่า Gated Commits โดยที่ Deployment Pipeline จะยืนยันก่อนว่าการเปลี่ยนแปลงที่ส่งเข้ามาจะ Merge ได้สำเร็จ Build ได้ตามที่คาดหวัง และผ่านการทดสอบอัตโนมัติทั้งหมด ก่อนที่จะถูก Merge เข้าสู่ Trunk จริงๆ หากไม่ผ่าน นักพัฒนาจะได้รับการแจ้งเตือน ทำให้สามารถแก้ไขได้โดยไม่ส่งผลกระทบต่อผู้อื่นใน Value Stream

ระเบียบวินัยในการ Commit โค้ดทุกวันยังบังคับให้เราแบ่งงานออกเป็นชิ้นเล็กลง ขณะที่ยังคงรักษา Trunk ให้อยู่ในสถานะที่ทำงานได้และพร้อมปล่อย Version Control กลายเป็นกลไกสำคัญที่ทีมใช้สื่อสารระหว่างกัน—ทุกคนมีความเข้าใจร่วมกันเกี่ยวกับระบบที่ดีขึ้น รับรู้สถานะของ Deployment Pipeline และสามารถช่วยเหลือกันเมื่อมันเสียหาย ส่งผลให้เรามีคุณภาพสูงขึ้นและ Deploy Lead Time ที่เร็วขึ้น

เมื่อมีแนวปฏิบัติเหล่านี้ เราก็สามารถปรับเปลี่ยนคำจำกัดความของ "done" ได้อีกครั้ง (ส่วนที่ปรับเปลี่ยนเป็นตัวหนา): "เมื่อสิ้นสุดแต่ละช่วงการพัฒนา เราต้องมีโค้ดที่ถูกรวม ทดสอบ ทำงานได้ และพร้อมส่งมอบ แสดงให้เห็นในสภาพแวดล้อมที่คล้าย Production ที่สร้างจาก Trunk โดยใช้กระบวนการคลิกเดียว และตรวจสอบความถูกต้องด้วยการทดสอบอัตโนมัติ"

การยึดมั่นในคำจำกัดความของ "done" ที่ปรับปรุงใหม่นี้ช่วยให้เรามั่นใจในความสามารถในการทดสอบและการ Deploy โค้ดที่เรากำลังผลิตได้อย่างต่อเนื่อง โดยการรักษาโค้ดให้อยู่ในสถานะที่พร้อม Deploy เราสามารถกำจัดแนวปฏิบัติทั่วไปที่มีช่วงการทดสอบและการทำให้เสถียรแยกต่างหากในช่วงท้ายของโครงการได้

CASE STUDY (กรณีศึกษา)

Continuous Integration at Bazaarvoice (2012) (การทำ Continuous Integration ที่ Bazaarvoice ปี 2012)

Ernest Mueller ซึ่งช่วยขับเคลื่อนการเปลี่ยนแปลง DevOps ที่ National Instruments ต่อมาได้ช่วยเปลี่ยนแปลงกระบวนการพัฒนาและปล่อยซอฟต์แวร์ที่ Bazaarvoice ในปี 2012 14 Bazaarvoice จัดหา Content ที่สร้างโดยลูกค้า (เช่น รีวิว และคะแนน) ให้กับร้านค้าปลีกหลายพันแห่ง เช่น Best Buy, Nike และ Walmart

ในขณะนั้น Bazaarvoice มีรายได้ 120 ล้านดอลลาร์และกำลังเตรียมตัวเข้าตลาดหลักทรัพย์ ‡ ธุรกิจหลักขับเคลื่อนโดยแอปพลิเคชัน Bazaarvoice Conversations ซึ่งเป็นโมโนลิธิก Java ขนาดเกือบห้าล้านบรรทัด ย้อนกลับไปถึงปี 2006 ครอบคลุมไฟล์หนึ่งหมื่นห้าพันไฟล์ บริการนี้รันบนเซิร์ฟเวอร์ 1,200 เครื่องในสี่ Data Center และผู้ให้บริการ Cloud หลายราย 15

ส่วนหนึ่งเป็นผลมาจากการเปลี่ยนมาใช้กระบวนการพัฒนาแบบ Agile และช่วงการพัฒนาสองสัปดาห์ จึงมีความต้องการอย่างมากที่จะเพิ่มความถี่ในการปล่อยซอฟต์แวร์จากตารางการปล่อย Production ทุกสิบสัปดาห์ในปัจจุบัน พวกเขายังเริ่มแยกส่วนต่างๆ ของแอปพลิเคชันโมโนลิธิกออกเป็น Microservices

ความพยายามครั้งแรกในการปล่อยทุกสองสัปดาห์เกิดขึ้นในเดือนมกราคม 2012 Mueller กล่าวว่า "มันไม่ดีเลย มันก่อให้เกิดความโกลาหลครั้งใหญ่ โดยมีเหตุการณ์ Production ที่ถูกร้องเรียนโดยลูกค้าถึงสี่สิบสี่ครั้ง ปฏิกิริยาหลักจากผู้บริหารคือ 'อย่าทำอย่างนั้นอีก'" 16

Mueller เข้ามารับผิดชอบกระบวนการปล่อยซอฟต์แวร์หลังจากนั้นไม่นาน โดยมีเป้าหมายในการปล่อยทุกสองสัปดาห์โดยไม่ทำให้ลูกค้าหยุดชะงัก วัตถุประสงค์ทางธุรกิจในการปล่อยบ่อยขึ้นรวมถึงการทำให้ A/B Testing (ซึ่งจะอธิบายในบทต่อๆ ไป) ทำได้เร็วขึ้น และเพิ่มการไหลของฟีเจอร์เข้าสู่ Production Mueller ระบุปัญหาหลักสามประการ: 17

• การขาดระบบทดสอบอัตโนมัติทำให้การทดสอบทุกระดับในช่วงสองสัปดาห์ไม่เพียงพอที่จะป้องกันความล้มเหลวในวงกว้าง

• กลยุทธ์การ Branch ใน Version Control ทำให้นักพัฒนาสามารถ Check-in โค้ดใหม่ได้จนถึงวินาทีสุดท้ายก่อนการปล่อย Production

• ทีมที่ดูแล Microservices ก็ทำการปล่อยซอฟต์แวร์อย่างอิสระเช่นกัน ซึ่งมักก่อให้เกิดปัญหาระหว่างการปล่อย Monolith

Mueller สรุปว่ากระบวนการ Deploy ของแอปพลิเคชันโมโนลิธิก Conversations จำเป็นต้องถูกทำให้เสถียร ซึ่งต้องใช้ Continuous Integration ในหกสัปดาห์ต่อมา นักพัฒนาหยุดทำงานฟีเจอร์ใหม่เพื่อหันมาเขียนชุดทดสอบอัตโนมัติแทน รวมถึง Unit Test ใน JUnit, Regression Test ใน Selenium และสร้าง Deployment Pipeline ที่รันใน TeamCity "การรัน Tests เหล่านี้ตลอดเวลาทำให้เรารู้สึกว่าเราสามารถเปลี่ยนแปลงโค้ดได้ด้วยความปลอดภัยในระดับหนึ่ง และที่สำคัญที่สุด เราสามารถค้นหาได้ทันทีเมื่อมีคนทำอะไรพัง แทนที่จะค้นพบเมื่อมันอยู่ใน Production แล้ว" 18

พวกเขายังเปลี่ยนมาใช้โมเดล Release แบบ Trunk/Branch โดยทุกสองสัปดาห์พวกเขาจะสร้าง Release Branch ใหม่โดยเฉพาะ โดยไม่อนุญาตให้มีการ Commit ใหม่ใน Branch นั้นยกเว้นในกรณีฉุกเฉิน—การเปลี่ยนแปลงทั้งหมดจะต้องผ่านกระบวนการอนุมัติ ไม่ว่าจะเป็นแบบต่อ Ticket หรือต่อทีมผ่าน Wiki ภายใน Branch นั้นจะผ่านกระบวนการ QA ก่อนที่จะถูกเลื่อนขั้นสู่ Production การปรับปรุงด้านความสามารถในการคาดเดาและคุณภาพของการปล่อยซอฟต์แวร์นั้นน่าทึ่งมาก: 19

• January 2012 release: สี่สิบสี่เหตุการณ์ถูกร้องเรียนโดยลูกค้า (เริ่มต้นความพยายาม Continuous Integration)

• March 6, 2012 release: ช้าห้าวัน ห้าเหตุการณ์ถูกร้องเรียนโดยลูกค้า

• March 22, 2012 release: ตรงเวลา หนึ่งเหตุการณ์ถูกร้องเรียนโดยลูกค้า

• April 5, 2012 release: ตรงเวลา ไม่มีเหตุการณ์ถูกร้องเรียนโดยลูกค้า

Mueller อธิบายเพิ่มเติมว่าความพยายามนี้ประสบความสำเร็จเพียงใด:

เราประสบความสำเร็จอย่างมากกับการปล่อยทุกสองสัปดาห์ จนเราก็ขยับไปปล่อยทุกสัปดาห์ ซึ่งแทบไม่ต้องเปลี่ยนแปลงอะไรจากทีมวิศวกรรมเลย เพราะการปล่อยซอฟต์แวร์กลายเป็นเรื่องปกติ มันก็แค่การเพิ่มจำนวนการปล่อยเป็นสองเท่าในปฏิทินและปล่อยเมื่อปฏิทินบอกให้ปล่อย

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

หลังจากนั้น เราเริ่มทำงานไปสู่เป้าหมายถัดไป ซึ่งนำไปสู่การเร่งเวลาทดสอบจากสามกว่าชั่วโมงเหลือน้อยกว่าหนึ่งชั่วโมง ลดจำนวนสภาพแวดล้อมจากสี่เหลือสาม (Dev, Test, Production ยกเลิก Staging) และย้ายไปสู่โมเดล Continuous Delivery ที่สมบูรณ์แบบ ซึ่งเราสามารถทำ Deploy ได้อย่างรวดเร็วด้วยคลิกเดียว 20

การระบุและแก้ไขปัญหาหลักสามประการอย่างเป็นระบบ กรณีศึกษานี้แสดงให้เห็นพลังของแนวปฏิบัติอย่าง Feature Freezes (ในกรณีนี้เพื่อทำงานด้านการทดสอบอัตโนมัติ) และการใช้ Trunk-Based Development เพื่อให้ Batch Size เล็กลงและเร่งรอบการปล่อยซอฟต์แวร์

Continuous Learning (การเรียนรู้อย่างต่อเนื่อง)

Continuous Integration ช่วยให้ทีมได้รับ Feedback อย่างรวดเร็ว และมีส่วนช่วยในการส่งมอบ Continuous Delivery และประสิทธิภาพระดับสูง งานวิจัยแสดงให้เห็นว่าความสามารถนี้มีความสำคัญ โดยรายงาน State of DevOps Report ปี 2014–2019 ได้สนับสนุนเรื่องราวที่แชร์ในบทนี้ด้วยข้อมูลเชิงประจักษ์

Trunk-Based Development น่าจะเป็นแนวปฏิบัติที่มีการโต้แย้งมากที่สุดในหนังสือเล่มนี้ อย่างไรก็ตาม ข้อมูลจาก DORA's State of DevOps Report ปี 2016 และ 2017 นั้นชัดเจน: Trunk-Based Development ทำนายปริมาณงานที่สูงกว่า ความเสถียรที่ดีกว่า และความพร้อมใช้งานที่ดีกว่าหากทีมปฏิบัติตามแนวปฏิบัติเหล่านี้: 21

• มี Branch ที่ยังใช้งานอยู่ใน Repository ของแอปพลิเคชันสาม Branch หรือน้อยกว่า

• Merge Branch เข้าสู่ Trunk อย่างน้อยทุกวัน

• ไม่มี Code Freeze หรือช่วงการรวมระบบ

ประโยชน์ของ Continuous Integration และ Trunk-Based Development นั้นขยายออกไปเกินกว่าความสามารถในการส่งมอบซอฟต์แวร์ของเรา งานวิจัยของ DORA แสดงให้เห็นว่ามันมีส่วนช่วยให้ความพึงพอใจในงานสูงขึ้นและอัตราการหมดไฟในการทำงานลดลง 22

Conclusion (บทสรุป)

ในบทนี้ เราได้พูดถึงความสามารถในการทำงานอัตโนมัติและแนวปฏิบัติเชิงพฤติกรรมที่จะช่วยให้เราส่งมอบโค้ดที่ "เสร็จแล้ว" ได้อย่างรวดเร็วและบ่อยครั้ง เราได้สร้างบรรทัดฐานทางวัฒนธรรมในการพัฒนาบน Trunk และการ Check-in โค้ดอย่างน้อยวันละครั้ง แนวปฏิบัติและบรรทัดฐานเหล่านี้ช่วยให้เราสามารถขยายขนาด รองรับโค้ดจากนักพัฒนาหลายคนหรือหลายร้อยคน เราจะสามารถส่งมอบโค้ดได้ทุกเวลา โดยไม่ต้องมี Code Freeze หรือช่วงการรวมระบบที่เจ็บปวด

แม้ว่าการโน้มน้าวนักพัฒนาอาจเป็นเรื่องยากในตอนแรก แต่เมื่อพวกเขาได้เห็นประโยชน์ที่ยอดเยี่ยมแล้ว พวกเขามักจะกลายเป็นผู้ศรัทธาตลอดชีวิต ดังที่ตัวอย่างของ HP LaserJet และ Bazaarvoice แสดงให้เห็น แนวปฏิบัติ Continuous Integration เป็นเวทีสำหรับก้าวถัดไป ซึ่งก็คือการทำให้กระบวนการ Deploy เป็นอัตโนมัติและการปล่อยซอฟต์แวร์ที่มีความเสี่ยงต่ำ

*

การ Branch ใน Version Control ถูกใช้ในหลายรูปแบบ แต่โดยทั่วไปแล้วใช้เพื่อแบ่งงานระหว่างสมาชิกในทีมตาม Release, การเลื่อนขั้น, งาน, คอมโพเนนต์, แพลตฟอร์มเทคโนโลยี และอื่นๆ

Compile Flags (#define และ #ifdef) ถูกใช้เพื่อเปิด/ปิดการทำงานของโค้ดสำหรับการมีเครื่องถ่ายเอกสาร ขนาดกระดาษที่รองรับ และอื่นๆ 5

การปล่อย Production ล่าช้าเนื่องจากการเข้าตลาดหลักทรัพย์ (ที่ประสบความสำเร็จ) ของพวกเขา