หลายๆ คนน่าจะได้ยินคำว่า Microservices อยู่บ่อยๆ ในช่วงนี้ ผมไปเจอบทความ Using Containers to Build a Microservices Architecture อ่านแล้วชอบเลยเอามาลองสรุปดู ซึ่งตัว microservices นั้นเป็นวิธีการหนึ่งที่ทำให้การพัฒนาระบบที่เป็น Web-based ทำให้เราสามารถรองรับการเปลี่ยนแปลงได้เร็ว และก็สามารถดูแลรักษาตัว code base ของเราได้ง่ายขึ้น และการเข้ามาของเทคโนโลยี containers ช่วยสร้าง microservices ได้อย่างไร
ประวัติศาสตร์ของการพัฒนา Web
ก่อนที่จะเข้าเรื่อง Microservices เรามาลองดูประวัติศาสตร์ของการพัฒนา Web ก่อนดีกว่า ในช่วงยุคแรกๆ ของการพัฒนา Web พวกระบบต่างๆ ก็จะใช้ Common Gateway Interface (CGI) บางคนอาจจะคุ้นเคยกับคำนี้ดี 🙂 โดยตัว CGI นี้เป็นตัวที่ทำให้เราสามารถรันสคริปอย่าง Perl ได้ แล้วเราก็ใช้สคริปพวกนี้จัดการ HTTP request ที่เข้ามาจากบราวเซอร์ CGI มีปัญหาคือมัน scale ได้ไม่ค่อยดี เนื่องจากมันจะเปิด process มาเพื่อรับแต่ละ request ที่เข้ามา ถ้าเข้ามา 100 requests เราก็จะมี 100 processes! กินทรัพยากรของเครื่องกระฉูดแน่นอน ไม่ต้องสืบ ทีนี้เลยมีพระเอกในยุคนั้น (จนถึงยุคนี้) ที่ชื่อ Apache สร้างโมดูลอย่าง mod_perl
มา ซึ่งทำให้เราสามารถรันโค้ด Perl ภายในตัว Web server ได้เลย เร็วขึ้นเยอะ
แต่ว่าก็ยังมีปัญหาอยู่ดีในการพัฒนา เพราะว่าโค้ดสำหรับส่วนที่เอาไปแสดงผลบนบราวเซอร์นั้นผูกติดกับส่วนที่เป็น business logic อย่างรุนแรง ถ้าเราอยากจะแก้ไขหน้าฟอร์ม หรือตารางอะไรสักอย่าง ทำให้เราต้องลงไปแก้ถึงในระดับ low-level application code เลยทีเดียว เพราะใช้สคริปในการสร้างหน้าตาพวกนั้น เวลาจะทำอะไร แก้ไขอะไรก็ลำบากเนอะ ทำให้ต่อมามีการพัฒนาเป็น “server pages” ทำให้เราสามารถเอาส่วนโค้ดที่เป็นสคริปใช้คู่ไปกับส่วนแสดงผลหรือ HTML ได้ ลองนึกถึงสมัยที่เราเขียน PHP ปนๆ กับ HTML ดู ทำให้เราสามารถแยกส่วนของ application logic กับ view logic ออกจากกันได้ดีขึ้น ในโลกของ Java จะมี design pattern ที่ชื่อว่า “Model 2” เกิดขึ้นมา ซึ่งจะนำเอาส่วนของ application code ไปอยู่ในส่วนที่เรียกว่า servlets นำส่วนของ data ไปอยู่ใน Beans และส่วนของ view logic ไปอยู่ใน server pages (*.jsp) ลองดูรูป Figure 1 ด้านล่างนี้ประกอบ
Model 2 เนี่ย ถูกปรับให้กลายเป็น design pattern หนึ่งที่ใช้กันอย่างแพร่กลายจากอดีตจนถึงยุคปัจจุบัน กล่าวคือ Model-View-Controller (MVC) นั่นเอง! โดยที่ตัว “controller” จะเป็นตัวที่รู้ว่าเวลาที่เข้า URL แบบนี้ จะต้องไปรันโค้ดตรงไหน ทำตัวเสมือนเป็น router โดยที่ controller จะใช้งาน “model” ที่เป็นตัวครอบ business logic กับ data ของ application เอาไว้ มีข้อดีคือเราสามารถทดสอบ business logic ได้โดยไม่ต้องไปยุ่งกับ “view” ที่ controller เป็นตัวสั่งให้แสดงผล แต่ละส่วนก็จะแบ่งแยกกันได้ดีขึ้น ดูแลรักษาง่ายขึ้นมาก ดู Figure 2 ประกอบ
กำเนิด REST
Interprocess Communication (IPC) เป็นสิ่งที่เกิดขึ้นมาพร้อมๆ กับ MVC ทำให้เราสามารถส่งข้อมูลผ่านโดยใช้ฟอร์แมตอย่าง XML และ JSON ได้ แล้วก็โปรโตคอลอย่าง SOAP ก็ยอมให้ IPC ส่งข้อมูลผ่าน HTTP ได้ ทำให้การพัฒนา Web ไม่ใช่แค่สร้าง application ที่เอาไว้แสดงผลเนื้อหาบนบราวเซอร์เพียงอย่างเดียว แต่ก็ทำให้เกิดสิ่งที่เรียกว่า Web service ขึ้นมา และการทำสถาปัตยกรรม services-based ก็ได้พิสูจน์แล้วว่าเค้าแจ่มจริง เนื่องจากมันสามารถตัด dependency ต่างๆ ได้ สามารถ decouple ระหว่างส่วนต่างๆ ของ application ได้ เวลาเราแก้โค้ดใน service ใด service หนึ่ง ก็จะมีผลกระทบต่อ service อื่นๆ น้อยมาก หรือไม่มีเลย 😀
แต่.. SOAP มันก็มาพร้อมกับความเจ็บปวด เพราะว่ามันไปผูกกับมาตรฐาน WS-* ทำให้มัน complex มากขึ้น แล้วก็ผูกติดกับ application servers มากขึ้น โดยเฉพาะที่เป็นของ Microsoft นั่นทำให้นักพัฒนาได้หันมาเริ่มในสิ่งที่ light-weight มากกว่าอย่าง REST อีกทั้งการใช้งานบน mobile การที่ user experience (UX) ของคนที่ใช้งาน Web เริ่มไปตื่นตาตื่นใจกับ AJAX และพวก JavaScript ต่างๆ แล้ว ทำให้การนำ REST มาใช้เป็นสิ่งที่คู่ควร และคุ้มค่า
ประจวบเหมาะที่เราใช้ MVC นั้นทำให้ชีวิตการพัฒนา REST endpoints ง่ายขึ้นด้วย เพราะว่า REST นั้นมีคอนเซปของ controller และ model ดูรูป Figure 3 ประกอบนะครับ
สถาปัตยกรรม Monolithic
อ่านว่า Mon`o*lith”ic คำๆ นี้เหมือนเป็นคำที่ไม่คุ้นหูเท่าไหร่ แต่มันเป็นสิ่งที่เราเกือบทุกคนผ่านมากันแล้วครับ นั่นก็คือระบบเดียวใหญ่ๆ รวมทุกอย่างไว้ในนั้น ระบบเทพอะไรประมาณนั้น เป็นระบบที่มีทั้ง MVC มีทั้งการเปิด REST endpoints ไว้ มีอะไรใหม่ๆ ก็นำมาเพิ่มในระบบนี้ ถ้าเขียนให้เห็นภาพง่ายๆ ก็คือโค้ดทั้งหมดของเราอยู่ในโฟลเดอร์เดียวกัน GitHub repository เดียวกัน ตัว application เรารันอยู่ process เดียวกัน เวลาที่เราจะ scale ระบบออกไป เราก็เพิ่มเครื่อง แล้วก็ใช้โค้ดที่เหมือนกันเป๊ะๆ กับเครื่อง server ใหม่ ดู Figure 4 ประกอบ
ปัญหา?
- เวลาที่มีอะไรใหม่ๆ เราก็เพิ่มเข้าไปในระบบเดียว ทำให้ code base ของเรายิ่งใหญ่มาก ถ้าไม่มีการจัดการดีๆ มันก็จะสลับซับซ้อนซ่อนเงื่อนงำอะไรต่างๆ เป็นแน่ อาจจะทำให้ไม่มีใครกล้าเข้าไปแตะโค้ดนี้เลยทีเดียว โดยเฉพาะคนใหม่ๆ ที่เพิ่งเข้ามาทำงาน
- พวกตัวช่วยเขียนโค้ดอย่าง IDE อาจจะโหลดโค้ดทั้งหมดขึ้นมาไม่ไหว
- เวลา compile และเวลา build จะเริ่มนานขึ้นเรื่อยๆ
- เนื่องจากมันรันอยู่ใน process เดียวกัน ทำให้เราไม่สามารถ scale เฉพาะส่วนได้
- ถ้ามีบางอย่างที่ซด memory เราก็จะต้องปรับเครื่อง server ของเราให้สามารถรองรับให้ได้ และงานจะเข้าเวลาที่เรา scale out ออกไป แล้วต้องใช้เครื่อง server ที่แรงเท่าๆ กับเครื่องที่เราใช้อยู่ เงินจะค่อยๆ หมดไป คำถามที่ตามมาคือเราทำ cost-effective จริงหรือเปล่า?
- ถ้าระบบใหญ่มากๆ อาจจะเกิดกรณีที่เราแบ่ง tier ของการทำงานออกเป็นส่วนของ user interface (UI) ส่วนของ service และส่วนของ database ทีนี้เวลาที่เราต้องการแก้ UI เราก็ต้องประสานงานกับส่วนของ service และส่วนของ service ก็ต้องประสานงานกับส่วนของ database ทีนี้โดยธรรมชาติแล้วแต่ละทีมก็จะทำส่วนของตัวเองให้ดีอยู่แล้ว ส่งผลให้ทุกส่วนอาจจะเริ่มยิ่งใหญ่ ผูกติดกันแน่นมากขึ้น และนำไปสู่โค้ดที่ไม่สามารถดูแลรักษาได้ในอนาคต
สถาปัตยกรรม Microservices
Microservices ถูกออกแบบมาเพื่อแก้ปัญหาเหล่านั้น! โดยที่ service ต่างๆ ที่ถูกเอาใส่ระบบที่เป็น monolithic จะถูกแยกออกมาเป็น service ของตัวเอง แล้วเวลาที่ deploy เราก็ deploy แยกกัน ดู Figure 5 ประกอบ
จะเห็นว่าแต่ละ microservices จะสามารถแยกการทำงาน แยกส่วน business ออกจากกันได้อย่างชัดเจน ดูๆ แล้วเหมือนกับ Service-Oriented Architecture (SOA) เลยเนอะ ซึ่งทั้ง 2 สถาปัตยกรรมนั้นมีส่วนที่เหมือนกัน
- ทั้งคู่จัดการโค้ดแยกกันในแต่ละ service
- ทั้งคู่กำหนดขอบเขตการทำงาน และเส้นแบ่งที่แยกออกจากกันชัดเจน
สิ่งที่แตกต่างกันก็คือ SOA นั้นมีแนวคิดมาจากการที่ระบบ monolithic เปิด API (ที่ส่วนใหญ่เป็น SOAP-based) กับคนอื่นๆ ซึ่งจะขึ้นอยู่กับ middleware เป็นหลัก ส่วน middleware ที่เรียกว่า Enterprise Service Bus (ESB) นั่นเอง ซึ่งจะมีส่วนของ logic ต่างๆ ในการทำ message routing แต่ microservices นั้นจะใช้เป็นการส่งข้อมูล แต่จะไม่มี logic อะไรทั้งนั้นในการส่ง ทำให้ microservices นั้นยุ่งยากน้อยกว่า SOA มาก
ข้อได้เปรียบอีกอย่างของ microservices ก็คือว่า แต่ละ service สามารถ scale ด้วยตัวเองมันเองได้ แต่ละ service สามารถพัฒนาด้วยเทคโนโลยีไหนก็ได้ (ทั้งนี้ก็ขึ้นอยู่กับความเหมาะสมของทีมผู้พัฒนาด้วยนะครับ) เช่นถ้าเราทำ image processing เราก็สร้าง service ที่เขียนด้วย C++ ถ้าทำ service ที่มี CRUD ดี ก็อาจจะใช้ Ruby on Rails ทีนี้เวลาที่เราอยากจะเพิ่มความสามารถให้ระบบของเรา เราก็แก้แค่บางส่วน และ deploy แค่บางส่วนได้ ทำให้การ deploy โค้ดของเราเร็วขึ้นมาก มาก มาก!
แต่ในความดีงามของมัน ก็มีข้อเสียอยู่นะ เราจำเป็นต้องตระหนักถึงในข้อเสียเหล่านี้ด้วยครับ
- ยากที่จะจำได้ว่าเครื่อง server ไหนบ้าง รัน service ไหนอยู่ ยากต่อการจัดการ
- เวลาที่ scale out สถาปัตยกรรมแบบ microservices จะสามารถ scale ได้รวดเร็วกว่า จำนวนเครื่อง server ของเราก็จะเพิ่มขึ้นเร็วกว่าแบบ monolithic เยอะ
Containers มาช่วยชีวิต
เทคโนโลยี Linux containers โดยเฉพาะ Docker มาช่วยลดปัญหาที่เกิดขึ้นจากการที่เรานำ microservices มาใช้ เนื่องจากเราสามารถสร้าง containers มารันบนเครื่อง server เดียวกันโดย โดยแต่ละ container จะมี environment ละ library แยกออกจากกันอย่างเด็ดขาด เราจะไม่เจอปัญหาเรื่องภาษาที่ใช้ หรือเรื่อง dependency ต่างๆ อีกต่อไป (ในการรันบนเครื่อง server เดียวกัน)
ความที่ containers มันมีความสามารถ portability ทำให้การ deploy ของเข้าไปในระบบที่เป็น microservices ง่ายเหมือนปอกกล้วย เราแค่หยุด container ตัวเดิม และสร้างตัวใหม่ที่มีโค้ดใหม่ขึ้นไปแทน โดยที่ container ตัวอื่นไม่กระทบอะไรเลย
ตัว containers นั้นยังสามารถช่วยให้เราจัดการ resource บนเครื่อง server ได้อย่างมีประสิทธิภาพอีกด้วย ถ้า server เรายังมี resource เหลือ เราก็สามารถเพิ่ม service เข้าไปอีกได้ ซึ่งการจัดการ containers พวกนี้ถ้าทำ manual ก็จะยากอยู่ดี เราไม่ควรทำแบบนั้น ไหนๆ เทคโนโลยีก็มาแล้ว แนะนำให้ใช้ Amazon EC2 Container Service (Amazon ECS) หรือไม่ก็ Docker Swarm ครับ 😉 จะทำให้ชีวิตการจัดการ containers ของเราสบายขึ้น
ใครสนใจว่าเค้าใช้ Amazon ECS สร้าง cluster ขึ้นมาอย่างไร ลองตามไปอ่านบทความต้นฉบับกันนะครับ จะมีลิ้งค์ไปยังบทความต่อไปของเค้าอยู่ อ่านเพลินดี