From Logical to Physical (จาก Logical สู่ Physical)

จนถึงจุดนี้ เมื่อเราพูดถึง microservices เรามักพูดถึงมันในเชิง logical มากกว่าเชิง physical เราสามารถพูดถึงว่า microservice Invoice ของเราสื่อสารกับ microservice Order อย่างไร ดังที่แสดงใน Figure 8-1 โดยไม่ต้องมองดู physical topology ว่า service เหล่านี้ถูก deploy อย่างไรจริง ๆ มุมมองเชิง logical ของสถาปัตยกรรมมักจะซ่อนประเด็นเรื่องการ deploy ทางกายภาพเบื้องล่างไว้—แนวคิดนี้จำเป็นต้องเปลี่ยนไปสำหรับขอบเขตของบทนี้

bms2 0801

Figure 8-1. มุมมองเชิง logical แบบง่าย ๆ ของ microservices สองตัว

มุมมองเชิง logical ของ microservices ของเรานี้สามารถซ่อนความซับซ้อนมหาศาลได้เมื่อพูดถึงการรัน service เหล่านี้จริง ๆ บน infrastructure จริง มาดูกันว่ารายละเอียดแบบไหนที่อาจถูกซ่อนไว้โดย diagram แบบนี้

Multiple Instances (หลาย Instance)

เมื่อเราคิดถึง deployment topology ของ microservices สองตัว (ใน Figure 8-2 ) มันไม่ได้ง่ายเหมือนแค่สิ่งหนึ่งคุยกับอีกสิ่งหนึ่ง เริ่มต้นเลย ดูเหมือนว่าเราน่าจะมี instance มากกว่าหนึ่งตัวสำหรับแต่ละ service การมี instance หลายตัวของ service ช่วยให้เรารับโหลดได้มากขึ้น และยังช่วยเพิ่มความทนทานของระบบ เพราะเราสามารถทนต่อความล้มเหลวของ instance เดียวได้ง่ายขึ้น ดังนั้นเราอาจมี instance หนึ่งตัวหรือมากกว่าของ Invoice กำลังคุยกับ instance หนึ่งตัวหรือมากกว่าของ Order รายละเอียดว่าการสื่อสารระหว่าง instance เหล่านี้ถูกจัดการอย่างไรจะขึ้นอยู่กับลักษณะของกลไกการสื่อสาร แต่ถ้าเราสมมติว่าในสถานการณ์นี้เรากำลังใช้ HTTP-based API รูปแบบใดรูปแบบหนึ่ง load balancer ก็เพียงพอที่จะจัดการ routing ของ request ไปยัง instance ต่าง ๆ ได้ ดังที่เราเห็นใน Figure 8-2

bms2 0802

Figure 8-2. การใช้ load balancer เพื่อ map request ไปยัง instance เฉพาะของ microservice Order

จำนวน instance ที่คุณต้องการจะขึ้นอยู่กับลักษณะของแอปพลิเคชันของคุณ—คุณจะต้องประเมิน redundancy ที่ต้องการ ระดับโหลดที่คาดไว้ และสิ่งอื่น ๆ เพื่อหาตัวเลขที่ใช้งานได้จริง คุณอาจต้องพิจารณาด้วยว่า instance เหล่านี้จะรันอยู่ที่ไหน ถ้าคุณมี instance หลายตัวของ service เพื่อเหตุผลด้านความทนทาน คุณคงอยากให้แน่ใจว่า instance เหล่านี้ไม่ได้อยู่บน hardware เดียวกันทั้งหมด ยิ่งไปกว่านั้น อาจต้องมี instance กระจายกันไม่เพียงข้ามหลายเครื่อง แต่ข้าม data center ต่าง ๆ ด้วย เพื่อป้องกันไม่ให้ data center ทั้งหมดใช้งานไม่ได้ สิ่งนี้อาจนำไปสู่ deployment topology แบบที่เห็นใน Figure 8-3

bms2 0803

Figure 8-3. การกระจาย instance ไปยัง data center ต่าง ๆ หลายแห่ง

เรื่องนี้อาจดูระมัดระวังเกินไป—โอกาสที่ data center ทั้งแห่งจะใช้งานไม่ได้มีมากแค่ไหนกัน? ผมไม่สามารถตอบคำถามนี้สำหรับทุกสถานการณ์ได้ แต่อย่างน้อยเมื่อทำงานกับ cloud provider หลัก ๆ นี่คือสิ่งที่คุณต้องคำนึงถึงอย่างแน่นอน เมื่อพูดถึงสิ่งอย่าง managed virtual machine ทั้ง AWS, Azure และ Google ต่างก็ไม่ให้ SLA สำหรับเครื่องเดียว และก็ไม่ให้ SLA สำหรับ availability zone เดียวเช่นกัน (ซึ่งเป็นสิ่งที่ใกล้เคียงกับ data center มากที่สุดสำหรับ provider เหล่านี้) ในทางปฏิบัติ นี่หมายความว่า solution ใด ๆ ที่คุณ deploy ควรจะถูกกระจายไปยังหลาย availability zone

The Database (ฐานข้อมูล)

เมื่อขยายความต่อไปอีก มีอีกองค์ประกอบสำคัญที่เรายังไม่ได้พูดถึงจนถึงจุดนี้—นั่นคือฐานข้อมูล ดังที่ผมได้พูดถึงไปแล้ว เราต้องการให้ microservice ซ่อนการจัดการ internal state ของมันเอง ดังนั้นฐานข้อมูลที่ใช้โดย microservice เพื่อจัดการ state ของมันจึงถือว่าถูกซ่อนอยู่ภายใน microservice นั้น สิ่งนี้นำไปสู่คำขวัญที่พูดกันบ่อย ๆ ว่า "อย่า share database" ซึ่งผมหวังว่าคงได้อธิบายเหตุผลไปมากพอแล้วจนถึงตอนนี้

แต่มันจะทำงานอย่างไรเมื่อเราพิจารณาข้อเท็จจริงที่ว่าผมมี microservice instance หลายตัว? แต่ละ instance ของ microservice ควรมีฐานข้อมูลเป็นของตัวเองหรือไม่? คำตอบสั้น ๆ คือไม่ ในกรณีส่วนใหญ่ ถ้าผมไปที่ instance ไหนก็ตามของ Order service ผมต้องการที่จะได้ข้อมูลเกี่ยวกับ order เดียวกัน ดังนั้นเราจึงต้องการ shared state ในระดับหนึ่งระหว่าง instance ต่าง ๆ ของ logical service เดียวกัน ดังที่แสดงใน Figure 8-4

bms2 0804

Figure 8-4. Instance หลายตัวของ microservice เดียวกันสามารถ share ฐานข้อมูลได้

แต่นี่ไม่ได้ละเมิดกฎ "อย่า share database" ของเราหรือ? จริง ๆ แล้วไม่ ข้อกังวลหลักอย่างหนึ่งของเราคือเมื่อ share database ข้าม microservice ที่ต่างกัน logic ที่เกี่ยวข้องกับการเข้าถึงและจัดการ state นั้นจะกระจายไปทั่วหลาย microservice แต่ในกรณีนี้ข้อมูลถูก share โดย instance ต่าง ๆ ของ microservice ตัวเดียวกัน logic สำหรับการเข้าถึงและจัดการ state ยังคงอยู่ภายใน microservice เชิง logical ตัวเดียว

Database deployment and scaling (การ Deploy และ Scale ฐานข้อมูล)

เช่นเดียวกับ microservices ของเรา จนถึงตอนนี้เราพูดถึงฐานข้อมูลในเชิง logical เป็นส่วนใหญ่ ใน Figure 8-3 เราไม่ได้พูดถึงประเด็นเรื่อง redundancy หรือความต้องการ scaling ของฐานข้อมูลที่อยู่เบื้องล่าง

พูดกว้าง ๆ physical database deployment อาจถูก host อยู่บนหลายเครื่อง ด้วยเหตุผลหลายประการ ตัวอย่างที่พบบ่อยคือการแบ่งโหลดสำหรับการอ่านและเขียนระหว่าง primary node กับ node หนึ่งตัวหรือมากกว่าที่กำหนดไว้สำหรับ read-only (node เหล่านี้มักถูกเรียกว่า read replica) ถ้าเรานำแนวคิดนี้ไปใช้กับ Order service ของเรา เราอาจได้สถานการณ์แบบที่แสดงใน Figure 8-5

bms2 0805

Figure 8-5. การใช้ read replica เพื่อกระจายโหลด

Traffic ที่เป็น read-only ทั้งหมดจะไปที่ read replica node ตัวใดตัวหนึ่ง และคุณสามารถ scale read traffic เพิ่มเติมได้โดยการเพิ่ม read node เข้าไปอีก เนื่องจากวิธีการทำงานของ relational database มันยากกว่าที่จะ scale การเขียนด้วยการเพิ่มเครื่องเข้าไป (โดยปกติต้องใช้ sharding model ซึ่งเพิ่มความซับซ้อน) ดังนั้นการย้าย read-only traffic ไปยัง read replica เหล่านี้มักจะช่วยเพิ่ม capacity บน write node ให้สามารถ scale ได้มากขึ้น

ที่เพิ่มความซับซ้อนเข้าไปอีกก็คือข้อเท็จจริงที่ว่า database infrastructure เดียวกันสามารถรองรับฐานข้อมูลที่แยกกันในเชิง logical ได้หลายตัว ดังนั้นฐานข้อมูลของ Invoice และ Order อาจถูกให้บริการโดย database engine และ hardware เดียวกัน ดังที่แสดงใน Figure 8-6 สิ่งนี้ให้ประโยชน์อย่างมาก—มันช่วยให้คุณ pool hardware เพื่อให้บริการ microservice หลายตัว ลดต้นทุนค่า license และยังช่วยลดงานในการจัดการฐานข้อมูลเองด้วย

The Invoice and Order databases are hosted on the same underlying shared DB infrastructure

Figure 8-6. Physical database infrastructure เดียวกัน host ฐานข้อมูลที่แยกกันในเชิง logical สองตัว

สิ่งสำคัญที่ต้องตระหนักตรงนี้คือแม้ฐานข้อมูลทั้งสองตัวนี้จะรันอยู่บน hardware และ database engine เดียวกัน แต่พวกมันยังคงเป็นฐานข้อมูลที่แยกกันในเชิง logical มันไม่สามารถรบกวนกันได้ (เว้นแต่คุณจะอนุญาต) สิ่งสำคัญที่สุดที่ต้องพิจารณาคือถ้า shared database infrastructure นี้ล้มเหลว คุณอาจกระทบต่อ microservice หลายตัว ซึ่งอาจส่งผลกระทบแบบร้ายแรง

จากประสบการณ์ของผม องค์กรที่จัดการ infrastructure ของตัวเองและรันแบบ "on-prem" มักมีแนวโน้มที่จะมีฐานข้อมูลหลายตัว host จาก shared database infrastructure มากกว่า ด้วยเหตุผลด้านต้นทุนที่ผมได้กล่าวไปก่อนหน้านี้ การจัดหาและจัดการ hardware นั้นเป็นเรื่องยากลำบาก (และในอดีตอย่างน้อย ฐานข้อมูลมักไม่ค่อยรันบน virtualized infrastructure) ดังนั้นคุณจึงต้องการสิ่งเหล่านี้ให้น้อยลง

ในทางกลับกัน ทีมที่รันบน public cloud provider มีแนวโน้มที่จะจัดหา dedicated database infrastructure แยกตาม microservice มากกว่า ดังที่แสดงใน Figure 8-7 ต้นทุนของการจัดหาและจัดการ infrastructure นี้ต่ำลงมาก Relational Database Service (RDS) ของ AWS ตัวอย่างเช่น สามารถจัดการประเด็นอย่าง backup, upgrade และ multi-availability zone failover ให้โดยอัตโนมัติ และมีผลิตภัณฑ์ที่คล้ายกันจาก public cloud provider เจ้าอื่น ๆ ด้วย สิ่งนี้ทำให้การมี infrastructure ที่แยกจากกันมากขึ้นสำหรับ microservice ของคุณคุ้มค่ากว่ามาก ให้เจ้าของ microservice แต่ละตัวมีอำนาจควบคุมมากขึ้น แทนที่จะต้องพึ่งพา shared service

The Invoice and Order databases each have their own dedicated DB infrastructure

Figure 8-7. แต่ละ microservice ใช้ dedicated DB infrastructure ของตัวเอง

Environments (สภาพแวดล้อม)

เมื่อคุณ deploy ซอฟต์แวร์ของคุณ มันจะรันอยู่ใน environment หนึ่ง แต่ละ environment มักจะมีจุดประสงค์ที่แตกต่างกัน และจำนวน environment ที่แน่นอนที่คุณอาจมีจะแตกต่างกันไปมากขึ้นอยู่กับว่าคุณพัฒนาซอฟต์แวร์อย่างไรและซอฟต์แวร์ของคุณถูก deploy ไปยังผู้ใช้ปลายทางอย่างไร บาง environment จะมีข้อมูล production ในขณะที่บาง environment ไม่มี บาง environment อาจมี service ทั้งหมดอยู่ในนั้น ในขณะที่บาง environment อาจมีเพียง service จำนวนน้อย โดย service ที่ไม่มีอยู่จะถูกแทนที่ด้วย service ปลอมเพื่อจุดประสงค์ในการทดสอบ

โดยทั่วไปแล้ว เราคิดว่าซอฟต์แวร์ของเราเคลื่อนผ่าน preproduction environment หลายตัว โดยแต่ละตัวมีจุดประสงค์บางอย่างเพื่อให้ซอฟต์แวร์ถูกพัฒนาและทดสอบความพร้อมสำหรับ production—เราได้สำรวจเรื่องนี้ไปแล้วใน "Trade-Offs and Environments" จาก laptop ของนักพัฒนาไปจนถึง continuous integration server, integrated test environment และอื่น ๆ—ลักษณะและจำนวน environment ที่แน่นอนของคุณจะขึ้นอยู่กับปัจจัยหลายอย่าง แต่ขับเคลื่อนหลักโดยวิธีที่คุณเลือกพัฒนาซอฟต์แวร์ ใน Figure 8-8 เราจะเห็น pipeline สำหรับ microservice Catalog ของ MusicCorp microservice เคลื่อนผ่าน environment ต่าง ๆ ก่อนที่มันจะเข้าสู่ production environment ในที่สุด ซึ่งเป็นที่ที่ผู้ใช้ของเราจะได้ใช้ซอฟต์แวร์ใหม่

bms2 0808

Figure 8-8. Environment ที่ต่างกันใช้สำหรับส่วนต่าง ๆ ของ pipeline

Environment แรกที่ microservice ของเรารันอยู่คือที่ที่นักพัฒนากำลังทำงานกับโค้ดก่อน check-in—น่าจะเป็น laptop ของตัวเอง หลังจาก commit โค้ดแล้ว กระบวนการ CI ก็เริ่มต้นด้วย fast test ทั้ง fast test และ slow test stage จะ deploy ไปยัง CI environment ของเรา ถ้า slow test ผ่าน microservice จะถูก deploy ไปยัง preproduction environment เพื่อให้มีการตรวจสอบด้วยมือ (ซึ่งเป็นทางเลือกทั้งหมด แต่ก็ยังสำคัญสำหรับหลายคน) ถ้าการตรวจสอบด้วยมือนี้ผ่าน microservice จะถูก deploy ไปยัง production

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

เรายังต้องการปรับแต่ง environment ที่อยู่ช่วงต้นของกระบวนการนี้เพื่อให้ได้ feedback ที่รวดเร็ว มันสำคัญมากที่เราต้องรู้โดยเร็วที่สุดว่าซอฟต์แวร์ของเราทำงานได้หรือไม่ เพื่อที่เราจะได้แก้ไขได้อย่างรวดเร็วหากจำเป็น ยิ่งเรารู้ปัญหาของซอฟต์แวร์เร็วเท่าไร ก็ยิ่งแก้ไขได้เร็วเท่านั้น และผลกระทบของความเสียหายก็จะน้อยลง มันดีกว่ามากที่จะพบปัญหาบน laptop ของเราเองมากกว่าไปเจอในระหว่างการทดสอบ preproduction แต่ในทำนองเดียวกัน การพบปัญหาในระหว่างการทดสอบ preproduction ก็อาจจะดีกว่าการพบปัญหาใน production มาก (แม้เราจะสำรวจ trade-off สำคัญบางอย่างเกี่ยวกับเรื่องนี้ใน Chapter 9 )

นี่หมายความว่า environment ที่ใกล้กับนักพัฒนามากขึ้นจะถูกปรับแต่งให้ feedback ที่รวดเร็ว ซึ่งอาจลดทอนความ "เหมือน production" ของมันลง แต่เมื่อ environment เข้าใกล้ production มากขึ้น เราจะต้องการให้มันเหมือน production environment สุดท้ายมากขึ้นเรื่อย ๆ เพื่อให้แน่ใจว่าเราจะจับปัญหาได้

ในฐานะตัวอย่างง่าย ๆ ของเรื่องนี้ในทางปฏิบัติ ลองย้อนกลับไปดูตัวอย่างก่อนหน้าของ Catalog service และดู environment ต่าง ๆ กัน ใน Figure 8-9 laptop ของนักพัฒนาในเครื่องมี service ของเรา deploy เป็น instance เดียวรันอยู่ในเครื่อง ซอฟต์แวร์ build ได้อย่างรวดเร็ว แต่ถูก deploy เป็น instance เดียวที่รันบน hardware ที่แตกต่างจาก production มาก ใน CI environment เรา deploy service ของเราสองสำเนาเพื่อทดสอบ ให้แน่ใจว่า load balancing logic ของเราทำงานได้ดี เรา deploy ทั้งสอง instance ไปยังเครื่องเดียวกัน—ซึ่งช่วยลดต้นทุนและทำให้เร็วขึ้น และยังให้ feedback เพียงพอในขั้นตอนนี้ของกระบวนการ

bms2 0809

Figure 8-9. Microservice สามารถแตกต่างกันได้ในวิธีการ deploy จาก environment หนึ่งไปยังอีก environment หนึ่ง

สุดท้าย ใน production microservice ของเราถูก deploy เป็น 4 instance กระจายไปยัง 4 เครื่อง ซึ่งกระจายอยู่ใน data center ที่แตกต่างกัน 2 แห่ง

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

สิ่งสำคัญคือ topology ที่แน่นอนของ microservice ของคุณจะเปลี่ยนไปจาก environment หนึ่งไปยังอีก environment หนึ่ง ดังนั้นคุณจึงต้องหาวิธีเปลี่ยนจำนวน instance จาก environment หนึ่งไปยังอีก environment หนึ่ง พร้อมกับ configuration เฉพาะของแต่ละ environment คุณยังต้องการ build service instance ของคุณเพียงครั้งเดียวเท่านั้น ดังนั้นข้อมูลเฉพาะ environment ใด ๆ จึงจำเป็นต้องแยกออกจาก deployed service artifact

วิธีที่คุณจะปรับเปลี่ยน topology ของ microservice จาก environment หนึ่งไปยังอีก environment หนึ่งจะขึ้นอยู่กับกลไกที่คุณใช้สำหรับการ deploy อย่างมาก และยังขึ้นอยู่กับว่า topology แตกต่างกันมากแค่ไหนด้วย ถ้าสิ่งเดียวที่เปลี่ยนไปจาก environment หนึ่งไปยังอีก environment หนึ่งคือจำนวน microservice instance นี่อาจง่ายเพียงแค่ parameterize ค่านี้เพื่อให้ตัวเลขที่แตกต่างกันถูกส่งเข้ามาเป็นส่วนหนึ่งของกิจกรรมการ deploy

สรุปแล้ว microservice เชิง logical ตัวเดียวสามารถถูก deploy ไปยังหลาย environment ได้ จาก environment หนึ่งไปยังอีก environment หนึ่ง จำนวน instance ของแต่ละ microservice สามารถแตกต่างกันได้ตามความต้องการของแต่ละ environment