Deployment Options (ตัวเลือกในการ Deploy)

เมื่อพูดถึงแนวทางและเครื่องมือที่เราสามารถใช้สำหรับ microservice workload ของเรา เรามีตัวเลือก มากมาย แต่เราควรพิจารณาตัวเลือกเหล่านี้ในแง่ของหลักการที่ผมเพิ่งกล่าวถึงไป เราต้องการให้ microservices ของเรารันในลักษณะที่แยกส่วน และตามอุดมคติแล้วควรถูก deploy ในวิธีที่หลีกเลี่ยง downtime เราต้องการให้เครื่องมือที่เราเลือกช่วยให้เรารับเอาวัฒนธรรม automation มาใช้ กำหนด infrastructure และ application configuration ของเราในโค้ด และตามอุดมคติแล้วยังจัดการ desired state ให้เราด้วย

มาสรุปตัวเลือกการ deploy ต่าง ๆ กันสั้น ๆ ก่อนที่จะดูว่ามันตอบโจทย์แนวคิดเหล่านี้ได้ดีแค่ไหน:

Physical machine

Microservice instance ถูก deploy ลงบนเครื่อง physical โดยตรง โดยไม่มี virtualization

Virtual machine

Microservice instance ถูก deploy ลงบน virtual machine

Container

Microservice instance รันเป็น container แยกต่างหากบนเครื่อง virtual หรือ physical container runtime นั้นอาจถูกจัดการโดยเครื่องมือ container orchestration อย่าง Kubernetes

Application container

Microservice instance รันอยู่ภายใน application container ที่จัดการ application instance อื่น ๆ โดยทั่วไปบน runtime เดียวกัน

Platform as a Service (PaaS)

Platform ที่ abstract มากขึ้นถูกใช้เพื่อ deploy microservice instance มักจะซ่อนแนวคิดทั้งหมดของ server เบื้องล่างที่ใช้รัน microservices ของคุณ ตัวอย่างเช่น Heroku, Google App Engine และ AWS Beanstalk

Function as a Service (FaaS)

Microservice instance ถูก deploy เป็น function หนึ่งตัวหรือมากกว่า ซึ่งถูกรันและจัดการโดย platform เบื้องล่างอย่าง AWS Lambda หรือ Azure Functions อาจกล่าวได้ว่า FaaS เป็น PaaS ประเภทเฉพาะ แต่มันสมควรได้รับการสำรวจในสิทธิ์ของตัวเอง เนื่องจากความนิยมที่เพิ่มขึ้นเมื่อไม่นานมานี้และคำถามที่มันตั้งขึ้นเกี่ยวกับการ map จาก microservice ไปสู่ deployed artifact

Physical Machines (เครื่อง Physical)

เป็นตัวเลือกที่หายากขึ้นเรื่อย ๆ คุณอาจพบว่าตัวเองกำลัง deploy microservices โดยตรง ลงบนเครื่อง physical โดย "โดยตรง" ผมหมายถึงไม่มีชั้นของ virtualization หรือ containerization ใด ๆ ระหว่างคุณกับ hardware เบื้องล่าง สิ่งนี้พบได้น้อยลงเรื่อย ๆ ด้วยเหตุผลหลายประการ ประการแรก การ deploy โดยตรงลงบน physical hardware สามารถนำไปสู่ utilization ที่ต่ำลงทั่วทั้ง estate ของคุณ ถ้าผมมี microservice instance เดียวรันอยู่บนเครื่อง physical และผมใช้ CPU, memory หรือ I/O เพียงครึ่งเดียวที่ hardware มีให้ resource ที่เหลือก็จะสูญเปล่า ปัญหานี้นำไปสู่การ virtualize computing infrastructure ส่วนใหญ่ ทำให้คุณสามารถให้ virtual machine หลายตัวอยู่ร่วมกันบนเครื่อง physical เดียวกันได้ มันให้ utilization ของ infrastructure ที่สูงขึ้นมาก ซึ่งมีประโยชน์ชัดเจนในแง่ของความคุ้มค่าด้านต้นทุน

ถ้าคุณมีการเข้าถึง physical hardware โดยตรงโดยไม่มีตัวเลือกสำหรับ virtualization ความล่อใจก็คือการอัด microservices หลายตัวลงบนเครื่องเดียวกัน—แน่นอนว่านี่ละเมิดหลักการที่เราพูดถึงเกี่ยวกับการมี isolated execution environment สำหรับ service ของคุณ คุณสามารถใช้เครื่องมืออย่าง Puppet หรือ Chef เพื่อ configure เครื่อง—ช่วย implement infrastructure as code ปัญหาคือถ้าคุณทำงานที่ระดับของเครื่อง physical เดียวเท่านั้น การ implement แนวคิดอย่าง desired state management, zero-downtime deployment และอื่น ๆ ต้องการให้เราทำงานที่ระดับ abstraction ที่สูงขึ้น โดยใช้ management layer บางอย่างข้างบน ระบบประเภทนี้มักถูกใช้ร่วมกับ virtual machine มากกว่า ซึ่งเป็นสิ่งที่เราจะสำรวจเพิ่มเติมในอีกสักครู่

โดยทั่วไป การ deploy microservices ลงบนเครื่อง physical โดยตรงเป็นสิ่งที่ผมแทบไม่เห็นในปัจจุบัน และคุณอาจต้องมีข้อกำหนด (หรือข้อจำกัด) ที่เฉพาะเจาะจงมากในสถานการณ์ของคุณเพื่อสนับสนุนแนวทางนี้เหนือความยืดหยุ่นที่เพิ่มขึ้นซึ่ง virtualization หรือ containerization อาจนำมาให้

Virtual Machines (Virtual Machine)

Virtualization ได้เปลี่ยนโฉม data center โดยช่วยให้เราสามารถแบ่งเครื่อง physical ที่มีอยู่ออกเป็น virtual machine ขนาดเล็กลง Virtualization แบบดั้งเดิมอย่าง VMware หรือที่ใช้โดย cloud provider หลัก ๆ managed virtual machine infrastructure (เช่น บริการ EC2 ของ AWS) ได้ให้ประโยชน์มหาศาลในการเพิ่ม utilization ของ computing infrastructure ในขณะเดียวกันก็ลดภาระในการจัดการ host ลงด้วย

โดยพื้นฐานแล้ว virtualization ช่วยให้คุณแบ่งเครื่องเบื้องล่างออกเป็น "virtual" machine ขนาดเล็กหลายตัวที่ทำงานเหมือนกับ server ปกติสำหรับซอฟต์แวร์ที่รันอยู่ภายใน virtual machine นั้น คุณสามารถกำหนดส่วนของ CPU, memory, I/O และ storage เบื้องล่างให้กับ virtual machine แต่ละตัวได้ ซึ่งในบริบทของเรา ช่วยให้คุณอัด execution environment ที่แยกส่วนจำนวนมากขึ้นสำหรับ microservice instance ของคุณลงบนเครื่อง physical เดียว

Virtual machine แต่ละตัวมี operating system เต็มรูปแบบและชุด resource ที่สามารถใช้โดยซอฟต์แวร์ที่รันอยู่ภายใน VM สิ่งนี้ทำให้แน่ใจได้ว่าคุณมี isolation ที่ดีมากระหว่าง instance เมื่อแต่ละ instance ถูก deploy ลงบน VM แยกต่างหาก Microservice instance แต่ละตัวสามารถ configure operating system ใน VM ตามความต้องการเฉพาะของตัวเองได้อย่างเต็มที่ เรายังคงมีปัญหาอยู่ว่าถ้า hardware เบื้องล่างที่รัน virtual machine เหล่านี้ล้มเหลว เราอาจสูญเสีย microservice instance หลายตัว มีวิธีที่จะช่วยแก้ปัญหานั้นได้ รวมถึงสิ่งอย่าง desired state management ซึ่งเราพูดถึงไปก่อนหน้านี้

Cost of virtualization (ต้นทุนของ Virtualization)

เมื่อคุณอัด virtual machine มากขึ้นเรื่อย ๆ ลงบน hardware เบื้องล่างเดียวกัน คุณจะพบว่าได้ผลตอบแทนที่ลดลงในแง่ของ computing resource ที่มีให้กับ VM เอง ทำไมถึงเป็นเช่นนั้น?

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

ในโลกของ virtualization เรามี overhead ที่คล้ายกับแผ่นกั้นลิ้นชักถุงเท้าของเรา เพื่อทำความเข้าใจว่า overhead นี้มาจากไหน มาดูกันว่า virtualization ส่วนใหญ่ทำงานอย่างไร Figure 8-14 แสดงการเปรียบเทียบ virtualization สองประเภท ทางซ้าย เราเห็นชั้นต่าง ๆ ที่เกี่ยวข้องกับสิ่งที่เรียกว่า type 2 virtualization และทางขวาเราเห็น container-based virtualization ซึ่งเราจะสำรวจเพิ่มเติมในไม่ช้า

Type 2 virtualization เป็นประเภทที่ implement โดย AWS, VMware, vSphere, Xen และ KVM (Type 1 virtualization หมายถึงเทคโนโลยีที่ VM รันโดยตรงบน hardware ไม่ใช่บน operating system อื่น) บน physical infrastructure ของเรามี host operating system บน OS นี้ เรารันสิ่งที่เรียกว่า hypervisor ซึ่งมีหน้าที่หลักสองอย่าง อย่างแรก มันแมป resource อย่าง CPU และ memory จาก virtual host ไปยัง physical host อย่างที่สอง มันทำหน้าที่เป็น control layer ช่วยให้เราสามารถควบคุม virtual machine ได้เอง

bms2 0814

Figure 8-14. การเปรียบเทียบ type 2 virtualization มาตรฐานกับ container แบบ lightweight

ภายใน VM เราจะได้สิ่งที่ดูเหมือน host ที่แตกต่างกันโดยสิ้นเชิง พวกมันสามารถรัน operating system ของตัวเองพร้อม kernel ของตัวเองได้ พวกมันสามารถถือได้ว่าเป็นเครื่องที่ถูกปิดผนึกอย่างแทบสมบูรณ์ แยกออกจาก physical host เบื้องล่างและ virtual machine อื่น ๆ โดย hypervisor

ปัญหาของ type 2 virtualization คือ hypervisor ตรงนี้ต้องกัน resource ไว้เพื่อทำงานของมันเอง สิ่งนี้ดึงเอา CPU, I/O และ memory ที่อาจถูกใช้ที่อื่นไป ยิ่ง hypervisor จัดการ host มากขึ้น มันก็ยิ่งต้องการ resource มากขึ้น ณ จุดหนึ่ง overhead นี้จะกลายเป็นข้อจำกัดในการแบ่ง physical infrastructure ของคุณให้ละเอียดขึ้นไปอีก ในทางปฏิบัติ นี่หมายความว่ามักมีผลตอบแทนที่ลดลงในการแบ่งกล่อง physical ให้เป็นชิ้นส่วนเล็กลงเรื่อย ๆ เนื่องจาก resource ที่มากขึ้นตามสัดส่วนไปกับ overhead ของ hypervisor

Good for microservices? (เหมาะกับ Microservices ไหม?)

กลับมาที่หลักการของเรา virtual machine ทำได้ดีมากในแง่ของ isolation แต่ก็มีต้นทุน ความง่ายในการ automation ของมันอาจแตกต่างกันไปขึ้นอยู่กับเทคโนโลยีที่ใช้อย่างแน่นอน—managed VM บน Google Cloud, Azure หรือ AWS ตัวอย่างเช่น ล้วนง่ายต่อการ automate ผ่าน API ที่รองรับดีและ ecosystem ของเครื่องมือที่สร้างขึ้นบน API เหล่านี้ นอกจากนี้ platform เหล่านี้ยังมีแนวคิดอย่าง autoscaling group ช่วย implement desired state management การ implement zero-downtime deployment จะต้องใช้งานมากขึ้น แต่ถ้า VM platform ที่คุณใช้ให้ API ที่ดี building block ก็มีอยู่แล้ว ปัญหาคือหลายคนใช้ managed VM ที่จัดหาโดย virtualization platform แบบดั้งเดิมอย่างที่จัดหาโดย VMware ซึ่งแม้ในทางทฤษฎีจะรองรับ automation ได้ แต่ก็มักไม่ถูกใช้ในบริบทนี้ แทนที่จะเป็นแบบนั้น platform เหล่านี้มักอยู่ภายใต้การควบคุมส่วนกลางของทีม operations เฉพาะ และความสามารถในการ automate โดยตรงกับพวกมันก็อาจถูกจำกัดเป็นผล

แม้ container จะพิสูจน์ว่าได้รับความนิยมมากขึ้นโดยทั่วไปสำหรับ microservice workload แต่หลายองค์กรก็ใช้ virtual machine ในการรันระบบ microservice ขนาดใหญ่ได้อย่างมีประสิทธิภาพ Netflix หนึ่งในตัวอย่างที่โดดเด่นของ microservices ได้สร้าง microservices ส่วนใหญ่ของตนบนพื้นฐาน managed virtual machine ของ AWS ผ่าน EC2 ถ้าคุณต้องการระดับ isolation ที่เข้มงวดกว่าที่พวกมันสามารถให้ได้ หรือคุณไม่มีความสามารถในการ containerize แอปพลิเคชันของคุณ VM ก็อาจเป็นตัวเลือกที่ดี

Containers (Container)

นับตั้งแต่หนังสือฉบับพิมพ์ครั้งแรก container ได้กลายเป็นแนวคิดที่ครองพื้นที่ในการ deploy ซอฟต์แวร์ฝั่ง server และสำหรับหลาย ๆ คน มันเป็นตัวเลือกโดยพฤตินัยสำหรับการ package และรันสถาปัตยกรรม microservice แนวคิด container ที่ทำให้เป็นที่นิยมโดย Docker และผูกกับ container orchestration platform ที่รองรับอย่าง Kubernetes ได้กลายเป็นตัวเลือกอันดับแรกของหลายคนสำหรับการรันสถาปัตยกรรม microservice ในระดับใหญ่

ก่อนที่เราจะพูดถึงว่าทำไมสิ่งนี้ถึงเกิดขึ้น และความสัมพันธ์ระหว่าง container, Kubernetes และ Docker เราควรสำรวจก่อนว่า container คืออะไรกันแน่ และดูโดยเฉพาะว่ามันแตกต่างจาก virtual machine อย่างไร

Isolated, differently (แยกส่วน แบบต่างออกไป)

Container เกิดขึ้นครั้งแรกบน operating system แบบ UNIX-style และเป็นเวลาหลายปีที่มันเป็นทางเลือกที่ใช้งานได้จริงเฉพาะบน operating system เหล่านั้น เช่น Linux แม้ Windows container จะเป็นสิ่งที่มีอยู่จริงมาก แต่ก็เป็น Linux operating system ที่ container ส่งผลกระทบมากที่สุดจนถึงตอนนี้

บน Linux process ถูกรันโดยผู้ใช้ที่กำหนด และมีความสามารถบางอย่างขึ้นอยู่กับการตั้งค่า permission Process สามารถ spawn process อื่นได้ ตัวอย่างเช่น ถ้าผมเปิด process ใน terminal process นั้นมักจะถือว่าเป็น child ของ terminal process หน้าที่ของ Linux kernel คือการดูแล tree ของ process นี้ ทำให้แน่ใจว่ามีเพียงผู้ใช้ที่ได้รับอนุญาตเท่านั้นที่สามารถเข้าถึง process ได้ นอกจากนี้ Linux kernel ยังสามารถกำหนด resource ให้กับ process ต่าง ๆ เหล่านี้ได้—ทั้งหมดนี้เป็นส่วนหนึ่งของการสร้าง multiuser operating system ที่ใช้งานได้จริง ที่ซึ่งคุณไม่ต้องการให้กิจกรรมของผู้ใช้คนหนึ่งทำลายระบบส่วนที่เหลือ

Container ที่รันบนเครื่องเดียวกันใช้ kernel เบื้องล่างร่วมกัน (แม้จะมีข้อยกเว้นสำหรับกฎนี้ที่เราจะสำรวจในไม่ช้า) แทนที่จะจัดการ process โดยตรง คุณสามารถคิดว่า container เป็น abstraction เหนือ subtree ของ process tree ทั้งระบบ โดยที่ kernel ทำงานหนักทั้งหมด Container เหล่านี้สามารถมี resource ทาง physical ถูกกำหนดให้ ซึ่งเป็นสิ่งที่ kernel จัดการให้เรา แนวทางทั่วไปนี้มีอยู่ในหลายรูปแบบ เช่น Solaris Zones และ OpenVZ แต่ด้วย LXC ที่แนวคิดนี้เข้าสู่กระแสหลักของ Linux operating system แนวคิดของ Linux container ก้าวหน้าขึ้นไปอีกเมื่อ Docker ให้ระดับ abstraction ที่สูงขึ้นเหนือ container โดยเริ่มแรกใช้ LXC เบื้องหลัง แล้วจึงแทนที่มันไปทั้งหมด

ถ้าเราดู stack diagram สำหรับ host ที่รัน container ใน Figure 8-14 เราจะเห็นความแตกต่างบางอย่างเมื่อเทียบกับ type 2 virtualization ประการแรก เราไม่ต้องการ hypervisor ประการที่สอง container ดูเหมือนจะไม่มี kernel—นั่นเป็นเพราะมันใช้ kernel ของเครื่องเบื้องล่าง ใน Figure 8-15 เราจะเห็นสิ่งนี้ชัดเจนขึ้น container สามารถรัน operating system ของตัวเองได้ แต่ operating system นั้นใช้ shared kernel บางส่วน—มันอยู่ใน kernel นี้เองที่ process tree ของแต่ละ container อาศัยอยู่ นี่หมายความว่า host operating system ของเราสามารถรัน Ubuntu ได้ และ container ของเรารัน CentOS ได้ ตราบใดที่ทั้งคู่สามารถรันเป็นส่วนหนึ่งของ kernel เบื้องล่างเดียวกันได้

Each container has its own app and operating system, but makes use of part of the same underlying kernel

Figure 8-15. โดยปกติแล้ว container บนเครื่องเดียวกันจะ share kernel เดียวกัน

ด้วย container เราไม่ได้ประโยชน์แค่จาก resource ที่ประหยัดได้จากการไม่ต้องใช้ hypervisor เท่านั้น เรายังได้ประโยชน์ในแง่ของ feedback อีกด้วย Linux container นั้น เร็ว กว่ามากในการ provision เมื่อเทียบกับ virtual machine เต็มรูปแบบ ไม่ใช่เรื่องแปลกที่ VM จะใช้เวลาหลายนาทีในการเริ่มต้น—แต่ด้วย Linux container การเริ่มต้นสามารถใช้เวลาเพียงไม่กี่วินาที คุณยังมีการควบคุมที่ละเอียดกว่ามากเหนือ container เองในแง่ของการกำหนด resource ให้กับพวกมัน ซึ่งทำให้ปรับแต่งการตั้งค่าเพื่อให้ได้ประโยชน์สูงสุดจาก hardware เบื้องล่างได้ง่ายขึ้นมาก

เนื่องจากลักษณะที่เบากว่าของ container เราสามารถมีมันรันอยู่บน hardware เดียวกันได้มากกว่าที่จะเป็นไปได้ด้วย VM โดยการ deploy หนึ่ง service ต่อหนึ่ง container ดังใน Figure 8-16 เราได้ระดับของ isolation จาก container อื่น ๆ (แม้จะไม่สมบูรณ์แบบ) และสามารถทำได้อย่างคุ้มค่ากว่ามากเมื่อเทียบกับการที่เราต้องการรันแต่ละ service ใน VM ของตัวเอง กลับมาที่การเปรียบเทียบลิ้นชักถุงเท้าของเราก่อนหน้านี้ ด้วย container แผ่นกั้นในลิ้นชักถุงเท้าจะบางกว่ามากเมื่อเทียบกับ VM ซึ่งหมายความว่าสัดส่วนของลิ้นชักถุงเท้าที่ถูกใช้เก็บถุงเท้าจริง ๆ จะมากขึ้น

bms2 0816

Figure 8-16. การรัน service แยกกันใน container

Container สามารถใช้งานร่วมกับ virtualization เต็มรูปแบบได้ดีเช่นกัน อันที่จริง นี่เป็นเรื่องปกติ ผมเคยเห็นมากกว่าหนึ่งโปรเจกต์ที่จัดหา AWS EC2 instance ขนาดใหญ่และรัน container หลายตัวบนมันเพื่อให้ได้สิ่งที่ดีที่สุดจากทั้งสองโลก: compute platform แบบ on-demand ephemeral ในรูปแบบของ EC2 ผสมกับ container ที่ยืดหยุ่นและรวดเร็วมากที่รันอยู่ข้างบน

Not perfect (ไม่สมบูรณ์แบบ)

Linux container ก็ไม่ได้ปราศจากปัญหา ลองนึกภาพว่าผมมี microservices จำนวนมากรันอยู่ใน container ของตัวเองบน host โลกภายนอกจะเห็นมันได้อย่างไร? คุณต้องมีวิธีบางอย่างในการ route จากโลกภายนอกไปยัง container เบื้องล่าง ซึ่งเป็นสิ่งที่ hypervisor ส่วนใหญ่ทำให้คุณด้วย virtualization ปกติ ด้วยเทคโนโลยีก่อนหน้าอย่าง LXC นี่เป็นสิ่งที่คุณต้องจัดการเอง—นี่คือพื้นที่หนึ่งที่แนวทางของ Docker ต่อ container ได้ช่วยไว้อย่างมาก

อีกจุดหนึ่งที่ต้องจำไว้คือ container เหล่านี้สามารถถือได้ว่าแยกส่วนจากมุมมองของ resource—ผมสามารถจัดสรรชุด CPU, memory และอื่น ๆ ที่ถูกกันไว้ให้กับแต่ละ container ได้—แต่นี่ไม่จำเป็นต้องเป็นระดับ isolation เดียวกับที่คุณได้จาก virtual machine หรือจากการมีเครื่อง physical แยกกัน ในช่วงแรก มีวิธีที่ได้รับการบันทึกและเป็นที่รู้จักหลายวิธีที่ process จาก container หนึ่งสามารถทะลุออกไปและมีปฏิสัมพันธ์กับ container อื่นหรือ host เบื้องล่างได้

มีการทำงานจำนวนมหาศาลที่ทุ่มเทเพื่อแก้ไขปัญหาเหล่านี้ และระบบ container orchestration และ container runtime เบื้องล่างได้ทำงานได้ดีในการตรวจสอบวิธีรัน container workload ให้ดีขึ้น เพื่อให้ isolation นี้ดีขึ้น แต่คุณจะต้องพิจารณาอย่างรอบคอบถึงประเภทของ workload ที่คุณต้องการรัน คำแนะนำของผมเองตรงนี้คือโดยทั่วไปแล้ว คุณควรมอง container ว่าเป็นวิธีที่ดีเยี่ยมในการแยกส่วนการทำงานของซอฟต์แวร์ที่เชื่อถือได้ ถ้าคุณกำลังรันโค้ดที่เขียนโดยคนอื่นและกังวลเกี่ยวกับฝ่ายที่มุ่งร้ายพยายามข้าม container-level isolation คุณจะต้องทำการตรวจสอบเชิงลึกด้วยตัวเองเกี่ยวกับสถานะปัจจุบันของศาสตร์ในการจัดการสถานการณ์เช่นนั้น—บางส่วนที่เราจะพูดถึงในอีกสักครู่

Windows containers (Windows Container)

ในอดีต ผู้ใช้ Windows มักมองด้วยความอิจฉาไปยังเพื่อนร่วมงานที่ใช้ Linux เพราะ container เป็นสิ่งที่ถูกปฏิเสธจาก Windows operating system ในช่วงไม่กี่ปีที่ผ่านมา อย่างไรก็ตาม สิ่งนี้ได้เปลี่ยนไป โดย container กลายเป็นแนวคิดที่ได้รับการรองรับเต็มรูปแบบแล้ว ความล่าช้าที่แท้จริงเกี่ยวข้องกับ Windows operating system และ kernel เบื้องล่างที่ต้องรองรับความสามารถแบบเดียวกับที่มีอยู่ในโลกของ Linux เพื่อทำให้ container ทำงานได้ มันเป็นการเปิดตัว Windows Server 2016 ที่ทำให้หลายสิ่งหลายอย่างเปลี่ยนไป และนับตั้งแต่นั้น Windows container ก็พัฒนาต่อเนื่องมาเรื่อย ๆ

หนึ่งในอุปสรรคเริ่มต้นในการรับเอา Windows container มาใช้คือขนาดของ Windows operating system เอง จำไว้ว่าคุณต้องรัน operating system ภายในแต่ละ container ดังนั้นเมื่อ download container image คุณก็กำลัง download operating system ไปด้วย แต่ Windows นั้น ใหญ่ —ใหญ่มากจนทำให้ container หนักมาก ไม่ใช่แค่ในแง่ขนาดของ image เท่านั้น แต่ยังรวมถึง resource ที่จำเป็นในการรันมันด้วย

Microsoft ตอบสนองต่อสิ่งนี้ด้วยการสร้าง operating system แบบตัดทอนที่เรียกว่า Windows Nano Server แนวคิดคือ Nano Server ควรมี OS ที่มี footprint เล็ก และสามารถรันสิ่งอย่าง microservice instance ได้ นอกจากนี้ Microsoft ยังรองรับ Windows Server Core ที่ใหญ่กว่า ซึ่งมีไว้เพื่อรองรับการรัน legacy Windows application เป็น container ปัญหาคือสิ่งเหล่านี้ยังมีขนาดค่อนข้างใหญ่เมื่อเทียบกับสิ่งเทียบเท่าใน Linux—Nano Server เวอร์ชันแรก ๆ ยังคงมีขนาดกว่า 1 GB เมื่อเทียบกับ Linux operating system แบบ footprint เล็กอย่าง Alpine ที่มีขนาดเพียงไม่กี่เมกะไบต์

แม้ Microsoft จะพยายามลดขนาดของ Nano Server อย่างต่อเนื่อง แต่ความแตกต่างของขนาดนี้ก็ยังคงมีอยู่ ในทางปฏิบัติ อย่างไรก็ตาม เนื่องจากวิธีที่ layer ทั่วไปข้าม container image สามารถถูก cache ได้ นี่อาจไม่ใช่ปัญหาใหญ่นัก

สิ่งที่น่าสนใจเป็นพิเศษในโลกของ Windows container คือข้อเท็จจริงที่ว่าพวกมันรองรับระดับ isolation ที่แตกต่างกัน Windows container มาตรฐานใช้ process isolation คล้ายกับใน Linux ด้วย process isolation แต่ละ container รันอยู่ในส่วนของ kernel เบื้องล่างเดียวกัน ซึ่งจัดการ isolation ระหว่าง container ด้วย Windows container คุณยังมีตัวเลือกในการให้ isolation มากขึ้นโดยการรัน container ภายใน Hyper-V VM ของตัวเอง สิ่งนี้ให้บางอย่างที่ใกล้เคียงกับระดับ isolation ของ virtualization เต็มรูปแบบ แต่สิ่งที่ดีคือคุณสามารถเลือกระหว่าง Hyper-V หรือ process isolation ได้เมื่อคุณเปิด container—image ไม่จำเป็นต้องเปลี่ยน

ความยืดหยุ่นในการรัน image ในประเภท isolation ที่แตกต่างกันสามารถมีประโยชน์ได้ ในบางสถานการณ์ threat model ของคุณอาจกำหนดให้คุณต้องการ isolation ที่แข็งแกร่งกว่าระหว่าง process ที่กำลังรันอยู่มากกว่า process-level isolation ธรรมดา ตัวอย่างเช่น คุณอาจกำลังรันโค้ดของบุคคลที่สามที่ "ไม่น่าเชื่อถือ" ควบคู่ไปกับ process ของคุณเอง ในสถานการณ์เช่นนั้น การสามารถรัน container workload เหล่านั้นเป็น Hyper-V container นั้นมีประโยชน์มาก ควรสังเกตด้วยว่า Hyper-V isolation มีแนวโน้มที่จะมี ผลกระทบ ในแง่ของเวลาในการเปิดตัวและต้นทุน runtime ที่ใกล้เคียงกับ virtualization ปกติมากกว่า

Blurred Lines (เส้นแบ่งที่เลือนราง)

มีแนวโน้มที่เพิ่มขึ้นของคนที่มองหา solution ที่ให้ isolation ที่แข็งแกร่งกว่าซึ่ง VM มอบให้ ในขณะที่มีลักษณะเบาของ container ตัวอย่างรวมถึง Hyper-V container ของ Microsoft ซึ่งรองรับ kernel แยกกัน และ Firecracker ซึ่งถูกเรียกอย่างชวนสับสนว่าเป็น kernel-based VM Firecracker ได้พิสูจน์ตัวเองว่าเป็นที่นิยมในฐานะรายละเอียดการ implement ของ service offering อย่าง AWS Lambda ที่ซึ่งมีความจำเป็นต้องแยก workload จากลูกค้าต่าง ๆ อย่างเต็มที่ ในขณะที่ยังพยายามรักษาเวลาในการเปิดตัวให้ต่ำและลด operational footprint ของ workload

Docker

Container ถูกใช้ในขอบเขตจำกัดก่อนที่การเกิดขึ้นของ Docker จะผลักดันแนวคิดนี้เข้าสู่กระแสหลัก Docker toolchain จัดการงานส่วนใหญ่รอบ ๆ container Docker จัดการการ provision container จัดการปัญหาเรื่อง networking บางอย่างให้คุณ และยังมีแนวคิด registry ของตัวเองที่ช่วยให้คุณเก็บ Docker application ได้ ก่อน Docker เราไม่มีแนวคิดของ "image" สำหรับ container—ด้านนี้ ควบคู่ไปกับชุดเครื่องมือที่ดีกว่ามากสำหรับการทำงานกับ container ช่วยให้ container ใช้งานง่ายขึ้นมาก

Abstraction ของ Docker image มีประโยชน์กับเรา เพราะรายละเอียดว่า microservice ของเราถูก implement อย่างไรถูกซ่อนไว้ เรามี build สำหรับ microservice ของเราสร้าง Docker image เป็น build artifact และเก็บ image ไว้ใน Docker registry แล้วก็เสร็จ เมื่อคุณเปิด instance ของ Docker image คุณจะมีชุดเครื่องมือทั่วไปสำหรับจัดการ instance นั้น ไม่ว่าเทคโนโลยีเบื้องล่างจะเป็นอะไร—microservices ที่เขียนด้วย Go, Python, NodeJS หรืออะไรก็ตามสามารถถูกจัดการเหมือนกันได้ทั้งหมด

Docker ยังสามารถช่วยลดข้อเสียบางอย่างของการรัน service จำนวนมากในเครื่องสำหรับ dev และ test ได้ ก่อนหน้านี้ ผมอาจใช้เครื่องมืออย่าง Vagrant ที่ช่วยให้ผม host VM อิสระหลายตัวบนเครื่อง development ของผม สิ่งนี้จะช่วยให้ผมมี VM แบบ production-like ที่รัน service instance ของผมในเครื่อง นี่เป็นแนวทางที่ค่อนข้างหนักหน่วง และผมจะถูกจำกัดจำนวน VM ที่สามารถรันได้ ด้วย Docker การรัน Docker โดยตรงบนเครื่อง developer ของผมนั้นง่ายมาก โดยอาจใช้ Docker Desktop ตอนนี้ผมสามารถ build Docker image สำหรับ microservice instance ของผมได้ หรือดึง prebuilt image ลงมา แล้วรันมันในเครื่อง Docker image เหล่านี้สามารถ (และควร) เหมือนกันทุกประการกับ container image ที่ผมจะรันจริง ๆ ใน production ในที่สุด

เมื่อ Docker เกิดขึ้นครั้งแรก ขอบเขตของมันจำกัดอยู่ที่การจัดการ container บนเครื่องเดียว สิ่งนี้มีประโยชน์จำกัด—ถ้าคุณต้องการจัดการ container ข้ามหลายเครื่องล่ะ? นี่เป็นสิ่งจำเป็นถ้าคุณต้องการรักษาสุขภาพของระบบ ถ้าคุณมีเครื่องตายไป หรือถ้าคุณแค่ต้องการรัน container เพียงพอที่จะรับโหลดของระบบ Docker ได้ออกผลิตภัณฑ์ที่แตกต่างกันโดยสิ้นเชิงสองตัวของตัวเองเพื่อแก้ปัญหานี้ เรียกอย่างชวนสับสนว่า "Docker Swarm" และ "Docker Swarm Mode"—ใครบอกว่าการตั้งชื่อสิ่งของเป็นเรื่องยากอีกล่ะ? แต่อย่างไรก็ตาม เมื่อพูดถึงการจัดการ container จำนวนมากข้ามหลายเครื่อง Kubernetes คือราชาในพื้นที่นี้ แม้คุณจะยังใช้ Docker toolchain สำหรับการ build และจัดการ container แต่ละตัวก็ตาม

Fitness for microservices (ความเหมาะสมกับ Microservices)

Container ในฐานะแนวคิดทำงานได้ดีอย่างน่าทึ่งสำหรับ microservices และ Docker ทำให้ container ใช้งานได้จริงมากขึ้นอย่างมีนัยสำคัญในฐานะแนวคิด เราได้ isolation ของเราแต่ในต้นทุนที่จัดการได้ เรายังซ่อนเทคโนโลยีเบื้องล่าง ช่วยให้เราผสม tech stack ที่แตกต่างกันได้ อย่างไรก็ตาม เมื่อพูดถึงการ implement แนวคิดอย่าง desired state management เราจะต้องการบางอย่างเช่น Kubernetes เพื่อจัดการให้เรา

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

Application Containers (Application Container)

ถ้าคุณคุ้นเคยกับการ deploy แอปพลิเคชัน .NET ผ่าน IIS หรือแอปพลิเคชัน Java เข้าไปยังสิ่งอย่าง Weblogic หรือ Tomcat คุณจะคุ้นเคยดีกับโมเดลที่ service หรือแอปพลิเคชันที่แยกจากกันหลายตัวอยู่ภายใน application container เดียว ซึ่งอยู่บน host เดียว ดังที่เราเห็นใน Figure 8-17 แนวคิดคือ application container ที่ service ของคุณอาศัยอยู่ให้ประโยชน์ในแง่ของการจัดการที่ดีขึ้น เช่น clustering support สำหรับจัดกลุ่ม instance หลายตัวเข้าด้วยกัน เครื่องมือ monitoring และอื่น ๆ

Deploying multiple microservices into a single application server

Figure 8-17. Microservice หลายตัวต่อหนึ่ง application container

การตั้งค่านี้ยังสามารถให้ประโยชน์ในแง่ของการลด overhead ของ language runtime ลองพิจารณาการรัน Java service ห้าตัวใน Java servlet container เดียว ผมมี overhead ของ JVM เพียงตัวเดียว เทียบกับการรัน JVM อิสระห้าตัวบน host เดียวกันเมื่อใช้ container ถึงอย่างนั้น ผมยังคงรู้สึกว่า application container เหล่านี้มีข้อเสียมากพอที่คุณควรท้าทายตัวเองว่ามันจำเป็นจริง ๆ หรือไม่

ข้อเสียแรกคือมันย่อมจำกัดตัวเลือกเทคโนโลยี คุณต้องซื้อ tech stack หนึ่งมาใช้ สิ่งนี้สามารถจำกัดไม่เพียงแค่ตัวเลือกเทคโนโลยีสำหรับ implementation ของ service เองเท่านั้น แต่ยังรวมถึงตัวเลือกที่คุณมีในแง่ของ automation และการจัดการระบบของคุณด้วย ดังที่เราจะพูดถึงในไม่ช้า หนึ่งในวิธีที่เราสามารถจัดการ overhead ของการจัดการหลาย host คือด้วย automation ดังนั้นการจำกัดตัวเลือกของเราในการแก้ปัญหานี้อาจสร้างความเสียหายเป็นสองเท่า

ผมยังตั้งคำถามถึงคุณค่าของ feature บางอย่างที่ application container เหล่านี้มีให้ หลายตัวโฆษณาความสามารถในการจัดการ cluster เพื่อรองรับ shared in-memory session state ซึ่งเป็นสิ่งที่เราต้องการหลีกเลี่ยงอย่างแน่นอนอยู่แล้ว เนื่องจากความท้าทายที่มันสร้างขึ้นเมื่อ scale service ของเรา และความสามารถ monitoring ที่มันมีให้ก็จะไม่เพียงพอเมื่อเราพิจารณาถึงประเภทของ monitoring แบบเชื่อมโยงกันที่เราต้องการทำในโลกของ microservices ดังที่เราจะเห็นใน Chapter 10 หลายตัวยังมีเวลาเปิดตัวที่ช้ามาก ส่งผลกระทบต่อ feedback cycle ของนักพัฒนา

ยังมีปัญหาชุดอื่น ๆ อีกด้วย การพยายามจัดการ life-cycle ของแอปพลิเคชันอย่างเหมาะสมบนแพลตฟอร์มอย่าง JVM อาจมีปัญหาและซับซ้อนกว่าการรีสตาร์ท JVM ธรรมดา การวิเคราะห์การใช้ resource และ thread ก็ซับซ้อนกว่ามากเช่นกัน เนื่องจากคุณมีแอปพลิเคชันหลายตัว share process เดียวกัน และจำไว้ว่า แม้คุณจะได้คุณค่าจาก technology-specific container พวกมันก็ไม่ได้ฟรี นอกเหนือจากข้อเท็จจริงที่ว่าหลายตัวเป็นเชิงพาณิชย์และมีผลกระทบด้านต้นทุน พวกมันยังเพิ่ม resource overhead ในตัวมันเองด้วย

ท้ายที่สุด แนวทางนี้เป็นความพยายามอีกครั้งที่จะ optimize สำหรับความขาดแคลนของ resource ที่อาจไม่คงอยู่อีกต่อไปแล้ว ไม่ว่าคุณจะตัดสินใจมี service หลายตัวต่อ host เป็น deployment model หรือไม่ ผมขอแนะนำอย่างยิ่งให้พิจารณา self-contained deployable microservices เป็น artifact โดยแต่ละ microservice instance รันเป็น process ที่แยกส่วนของตัวเอง

โดยพื้นฐานแล้ว การขาด isolation ที่โมเดลนี้ให้เป็นหนึ่งในเหตุผลหลักที่ทำให้โมเดลนี้หายากขึ้นเรื่อย ๆ สำหรับผู้ที่รับเอาสถาปัตยกรรม microservice มาใช้

Platform as a Service (PaaS)

เมื่อใช้ Platform as a Service (PaaS) คุณกำลังทำงานที่ระดับ abstraction ที่สูงกว่า host เดียว บาง platform เหล่านี้พึ่งพาการรับเอา technology-specific artifact เช่น Java WAR file หรือ Ruby gem และ provision และรันมันให้คุณโดยอัตโนมัติ บาง platform เหล่านี้จะพยายามจัดการ scale ระบบขึ้นลงให้คุณโดยไม่รู้ตัว ตัวอื่น ๆ จะให้คุณควบคุมได้บ้างว่า service ของคุณจะรันบนกี่ node แต่พวกมันจัดการที่เหลือ

เหมือนกับตอนที่ผมเขียนฉบับพิมพ์ครั้งแรก PaaS solution ที่ดีที่สุดและขัดเกลาที่สุดส่วนใหญ่เป็นแบบ hosted Heroku ตั้งมาตรฐานในการมอบ interface ที่เป็นมิตรกับนักพัฒนา และอาจกล่าวได้ว่ายังคงเป็นมาตรฐานทองคำสำหรับ PaaS แม้จะมีการเติบโตของ featureset ที่จำกัดในช่วงไม่กี่ปีที่ผ่านมา Platform อย่าง Heroku ไม่ได้แค่รัน application instance ของคุณเท่านั้น พวกมันยังให้ความสามารถอย่างการรัน database instance ให้คุณด้วย—ซึ่งเป็นสิ่งที่ทำเองได้ยากมาก

เมื่อ PaaS solution ทำงานได้ดี มันทำงานได้ดีมากจริง ๆ อย่างไรก็ตาม เมื่อมันไม่ทำงานได้ดีสำหรับคุณ คุณมักไม่มีการควบคุมมากนักในแง่ของการเข้าไปแก้ไขสิ่งต่าง ๆ นี่เป็นส่วนหนึ่งของ trade-off ที่คุณต้องแลก ผมจะบอกว่าจากประสบการณ์ของผม ยิ่ง PaaS solution พยายามฉลาดมากเท่าไร มันก็ยิ่งผิดพลาดมากขึ้นเท่านั้น ผมเคยใช้ PaaS มากกว่าหนึ่งตัวที่พยายาม autoscale ตามการใช้งานแอปพลิเคชัน แต่ทำได้ไม่ดี heuristic ที่ขับเคลื่อนความฉลาดเหล่านี้มักถูกปรับแต่งสำหรับแอปพลิเคชันโดยเฉลี่ยมากกว่า use case เฉพาะของคุณ ยิ่งแอปพลิเคชันของคุณไม่เป็นมาตรฐานมากเท่าไร ก็ยิ่งมีแนวโน้มที่มันจะไม่เข้ากันดีกับ PaaS มากขึ้นเท่านั้น

เนื่องจาก PaaS solution ที่ดีจัดการสิ่งต่าง ๆ ให้คุณมากมาย พวกมันจึงเป็นวิธีที่ยอดเยี่ยมในการจัดการ overhead ที่เพิ่มขึ้นที่เราได้จากการมีชิ้นส่วนที่เคลื่อนไหวมากขึ้น ถึงอย่างนั้น ผมยังไม่แน่ใจว่าเรามีโมเดลที่ถูกต้องทั้งหมดในพื้นที่นี้แล้วหรือยัง และตัวเลือก self-hosted ที่จำกัดหมายความว่าแนวทางนี้อาจไม่เหมาะกับคุณ ตอนที่ผมเขียนฉบับพิมพ์ครั้งแรก ผมหวังว่าเราจะเห็นการเติบโตมากขึ้นในพื้นที่นี้ แต่มันไม่ได้เกิดขึ้นในแบบที่ผมคาดไว้ แทนที่จะเป็นแบบนั้น ผมคิดว่าการเติบโตของผลิตภัณฑ์ serverless ที่นำเสนอโดย public cloud provider เป็นหลักได้เริ่มเติมเต็มความต้องการนี้ แทนที่จะนำเสนอ platform แบบ black-box สำหรับ host แอปพลิเคชัน พวกมันกลับให้ turnkey managed solution สำหรับสิ่งอย่าง message broker, ฐานข้อมูล, storage และอื่น ๆ ที่ช่วยให้เราผสมและจับคู่ชิ้นส่วนที่เราชอบเพื่อสร้างสิ่งที่เราต้องการ นี่คือฉากหลังที่ Function as a Service ซึ่งเป็น serverless product ประเภทเฉพาะ ได้รับความนิยมมากขึ้นเรื่อย ๆ

การประเมินความเหมาะสมของข้อเสนอ PaaS สำหรับ microservices เป็นเรื่องยาก เพราะมันมาในหลากหลายรูปแบบและขนาด Heroku ดูแตกต่างจาก Netlify มาก ตัวอย่างเช่น แต่ทั้งคู่อาจเหมาะกับคุณในฐานะ deployment platform สำหรับ microservices ของคุณ ขึ้นอยู่กับลักษณะของแอปพลิเคชันของคุณ

Function as a Service (FaaS)

ในช่วงไม่กี่ปีที่ผ่านมา เทคโนโลยีเดียวที่เข้าใกล้ Kubernetes ในแง่ของการสร้าง hype (อย่างน้อยในบริบทของ microservices) คือ serverless Serverless แท้จริงแล้วเป็นคำร่มที่ครอบคลุมเทคโนโลยีหลากหลายแบบ ที่ซึ่งจากมุมมองของคนที่ใช้มัน คอมพิวเตอร์เบื้องล่างไม่สำคัญ รายละเอียดของการจัดการและ configure เครื่องถูกดึงออกไปจากคุณ ใน คำพูดของ Ken Fromm (ผู้ที่ผมทราบมาว่าเป็นคนบัญญัติคำว่า serverless ขึ้นมา):

คำว่า "serverless" ไม่ได้หมายความว่า server ไม่มีส่วนเกี่ยวข้องอีกต่อไป มันแค่หมายความว่านักพัฒนาไม่ต้องคิดถึงมันมากนักอีกต่อไป Computing resource ถูกใช้เป็น service โดยไม่ต้องจัดการเกี่ยวกับ physical capacity หรือขีดจำกัด ผู้ให้บริการรับผิดชอบมากขึ้นเรื่อย ๆ ในการจัดการ server, data store และ infrastructure resource อื่น ๆ นักพัฒนาสามารถตั้งค่า open source solution ของตัวเองได้ แต่นั่นหมายความว่าพวกเขาต้องจัดการ server และ queue และโหลดเอง

Ken Fromm, "Why the Future of Software and Apps Is Serverless"

Function as a Service หรือ FaaS ได้กลายเป็นส่วนสำคัญมากของ serverless จนสำหรับหลายคนทั้งสองคำนี้ใช้แทนกันได้ นี่เป็นเรื่องที่น่าเสียดาย เพราะมันมองข้ามความสำคัญของ serverless product อื่น ๆ อย่างฐานข้อมูล, queue, storage solution และอื่น ๆ ถึงกระนั้น มันก็สะท้อนให้เห็นถึงความตื่นเต้นที่ FaaS สร้างขึ้นจนครองการสนทนาไปทั้งหมด

มันคือผลิตภัณฑ์ Lambda ของ AWS ที่เปิดตัวในปี 2014 ที่จุดประกายความตื่นเต้นเกี่ยวกับ FaaS ในระดับหนึ่ง แนวคิดนี้เรียบง่ายอย่างน่ายินดี คุณ deploy โค้ดบางส่วน ("function") โค้ดนั้นอยู่เฉย ๆ จนกว่าจะมีบางอย่างเกิดขึ้นเพื่อ trigger โค้ดนั้น คุณเป็นผู้ตัดสินใจว่า trigger นั้นคืออะไร—มันอาจเป็นไฟล์ที่มาถึงตำแหน่งหนึ่ง รายการที่ปรากฏใน message queue การเรียกที่เข้ามาผ่าน HTTP หรืออย่างอื่น

เมื่อ function ของคุณถูก trigger มันจะรัน และเมื่อมันเสร็จสิ้น มันจะปิดตัวลง Platform เบื้องล่างจัดการการเปิดและปิด function เหล่านี้ตามความต้องการ และจะจัดการการรัน function ของคุณพร้อมกันหลายชุด เพื่อให้คุณสามารถมีสำเนาหลายชุดรันพร้อมกันได้เมื่อเหมาะสม

ประโยชน์ตรงนี้มีมากมาย โค้ดที่ไม่ได้กำลังรันอยู่ไม่ทำให้คุณเสียเงิน—คุณจ่ายเฉพาะสิ่งที่คุณใช้เท่านั้น สิ่งนี้ทำให้ FaaS เป็นตัวเลือกที่ดีเยี่ยมสำหรับสถานการณ์ที่คุณมีโหลดต่ำหรือคาดเดาไม่ได้ Platform เบื้องล่างจัดการการเปิดและปิด function ให้คุณ มอบระดับ high availability และความทนทานโดยนัยให้คุณโดยไม่ต้องทำงานใด ๆ โดยพื้นฐานแล้ว การใช้ FaaS platform เช่นเดียวกับข้อเสนอ serverless อื่น ๆ ช่วยให้คุณลด operational overhead ที่คุณต้องกังวลลงอย่างมาก

Limitations (ข้อจำกัด)

ภายใต้พื้นผิว การ implement FaaS ทั้งหมดที่ผมทราบล้วนใช้ container technology บางอย่าง สิ่งนี้ถูกซ่อนจากคุณ—โดยปกติคุณไม่ต้องกังวลเกี่ยวกับการ build container ที่จะถูกรัน คุณแค่ให้โค้ดในรูปแบบ package บางอย่างเท่านั้น อย่างไรก็ตาม นี่หมายความว่าคุณขาดระดับการควบคุมว่าจะรันอะไรได้บ้างอย่างแน่ชัด เป็นผลให้คุณต้องการให้ FaaS provider รองรับภาษาที่คุณเลือก Azure Functions ทำได้ดีที่สุดตรงนี้ในบรรดา cloud vendor หลัก ๆ รองรับ runtime ที่หลากหลายมาก ในขณะที่ Google Cloud's own Cloud Functions รองรับภาษาน้อยมากเมื่อเทียบกัน (ในขณะที่เขียนนี้ Google รองรับเพียง Go, Node บางเวอร์ชัน และ Python) ควรสังเกตว่า AWS ตอนนี้อนุญาตให้คุณกำหนด custom runtime ของคุณเองสำหรับ function ของคุณ ในทางทฤษฎีทำให้คุณสามารถ implement การรองรับภาษาที่ไม่ได้มีให้ตั้งแต่แรกได้ แม้สิ่งนี้จะกลายเป็น operational overhead อีกชิ้นหนึ่งที่คุณต้องดูแลก็ตาม

การขาดการควบคุมเหนือ runtime เบื้องล่างนี้ยังขยายไปถึงการขาดการควบคุมเหนือ resource ที่มอบให้กับแต่ละ function invocation ทั้ง Google Cloud, Azure และ AWS คุณสามารถควบคุมได้เพียง memory ที่มอบให้กับแต่ละ function เท่านั้น สิ่งนี้ดูเหมือนจะบ่งบอกว่า CPU และ I/O จำนวนหนึ่งถูกมอบให้กับ function runtime ของคุณ แต่คุณไม่สามารถควบคุมด้านเหล่านั้นได้โดยตรง สิ่งนี้อาจหมายความว่าคุณต้องมอบ memory ให้กับ function มากขึ้นแม้มันจะไม่ต้องการ เพียงเพื่อให้ได้ CPU ที่คุณต้องการ ท้ายที่สุด ถ้าคุณรู้สึกว่าคุณต้องทำ fine tuning จำนวนมากเกี่ยวกับ resource ที่มีให้กับ function ของคุณ ผมรู้สึกว่าอย่างน้อยในขั้นตอนนี้ FaaS อาจไม่ใช่ตัวเลือกที่ดีสำหรับคุณ

ข้อจำกัดอีกอย่างที่ควรทราบคือ function invocation อาจมีขีดจำกัดในแง่ของระยะเวลาที่มันสามารถรันได้ Google Cloud function ตัวอย่างเช่น ในปัจจุบันถูกจำกัดที่ 9 นาทีของการทำงาน ในขณะที่ AWS Lambda function สามารถรันได้นานถึง 15 นาที Azure function สามารถรันได้ตลอดไปถ้าคุณต้องการ (ขึ้นอยู่กับประเภทของแผนที่คุณใช้) ส่วนตัวผมคิดว่าถ้าคุณมี function ที่รันเป็นระยะเวลานาน นี่น่าจะบ่งชี้ถึงประเภทของปัญหาที่ function ไม่เหมาะสม

สุดท้าย function invocation ส่วนใหญ่ถือว่าไม่มี state โดยแนวคิด นี่หมายความว่า function ไม่สามารถเข้าถึง state ที่ทิ้งไว้โดย invocation ก่อนหน้าได้ เว้นแต่ state นั้นจะถูกเก็บไว้ที่อื่น (ตัวอย่างเช่น ในฐานข้อมูล) สิ่งนี้ทำให้ยากที่จะมี function หลายตัวเชื่อมโยงกัน—ลองพิจารณา function หนึ่งที่ orchestrate ชุดการเรียกไปยัง downstream function อื่น ๆ ข้อยกเว้นที่น่าสังเกตคือ Azure Durable Functions ซึ่งแก้ปัญหานี้ในวิธีที่น่าสนใจมาก Durable Functions รองรับความสามารถในการ suspend state ของ function หนึ่ง และให้มันเริ่มต้นใหม่จากจุดที่ invocation หยุดไว้—ทั้งหมดนี้ถูกจัดการอย่างโปร่งใสผ่านการใช้ reactive extension นี่เป็น solution ที่ผมคิดว่าเป็นมิตรกับนักพัฒนามากกว่า Step Functions ของ AWS เองอย่างมาก ซึ่งเชื่อม function หลายตัวเข้าด้วยกันโดยใช้ configuration แบบ JSON-based

WebAssembly (Wasm)

Wasm เป็นมาตรฐานอย่างเป็นทางการที่แต่เดิมถูกกำหนดขึ้นเพื่อให้นักพัฒนามีวิธีรันโปรแกรมแบบ sandboxed ที่เขียนด้วยภาษาโปรแกรมหลากหลายบน client browser การกำหนดทั้ง packaging format และ runtime environment เป้าหมายของ Wasm คือการให้โค้ดใด ๆ สามารถรันในลักษณะที่ปลอดภัยและมีประสิทธิภาพบนอุปกรณ์ client สิ่งนี้ช่วยให้แอปพลิเคชันฝั่ง client ที่ซับซ้อนมากขึ้นสามารถถูกสร้างขึ้นได้เมื่อใช้ web browser ปกติ ในฐานะตัวอย่างที่เป็นรูปธรรม eBay ใช้ Wasm เพื่อส่งมอบซอฟต์แวร์สแกนบาร์โค้ด ซึ่งแกนหลักเขียนด้วย C++ และก่อนหน้านี้มีให้ใช้เฉพาะแอปพลิเคชัน Android หรือ iOS แบบ native เท่านั้น สู่เว็บ 2

WebAssembly System Interface (WASI) ถูกกำหนดขึ้นเป็นวิธีให้ Wasm ย้ายออกจาก browser และทำงานได้ทุกที่ที่มี WASI implementation ที่รองรับ ตัวอย่างของสิ่งนี้คือความสามารถในการรัน Wasm บน content delivery network อย่าง Fastly หรือ Cloudflare

เนื่องจากลักษณะที่เบาและแนวคิด sandboxing ที่แข็งแกร่งที่ถูกสร้างเข้าไปในข้อกำหนดหลักของมัน Wasm มีศักยภาพที่จะท้าทายการใช้ container ในฐานะ deployment format หลักสำหรับแอปพลิเคชันฝั่ง server ในระยะสั้น สิ่งที่ยับยั้งมันน่าจะเป็น platform ฝั่ง server ที่มีให้รัน Wasm แม้ในทางทฤษฎีคุณสามารถรัน Wasm บน Kubernetes ได้ ตัวอย่างเช่น คุณจะจบลงด้วยการฝัง Wasm ไว้ภายใน container ซึ่งอาจกล่าวได้ว่าไม่ค่อยมีประโยชน์นัก เพราะคุณกำลังรัน deployment ที่เบากว่าอยู่ภายใน container ที่หนักกว่า (โดยเปรียบเทียบ)

Deployment platform ฝั่ง server ที่รองรับ WASI แบบ native น่าจะจำเป็นเพื่อให้ได้ประโยชน์สูงสุดจากศักยภาพของ Wasm ในทางทฤษฎีอย่างน้อย scheduler อย่าง Nomad น่าจะเหมาะสมกว่าในการรองรับ Wasm เนื่องจากมันรองรับ pluggable driver model เวลาจะเป็นเครื่องพิสูจน์!

Challenges (ความท้าทาย)

นอกเหนือจากข้อจำกัดที่เราเพิ่งพูดถึงไป ยังมีความท้าทายอื่น ๆ ที่คุณอาจพบเจอเมื่อใช้ FaaS

ประการแรก มันสำคัญที่จะพูดถึงข้อกังวลที่มักถูกยกขึ้นมาเกี่ยวกับ FaaS นั่นคือแนวคิดของ spin-up time โดยแนวคิด function ไม่ได้กำลังรันอยู่เลยเว้นแต่จะมีความจำเป็น สิ่งนี้หมายความว่ามันต้องถูกเปิดตัวเพื่อรองรับ request ที่เข้ามา สำหรับบาง runtime ตอนนี้ ต้องใช้เวลานานในการเปิดตัว runtime เวอร์ชันใหม่—มักถูกเรียกว่าเวลา "cold start" JVM และ .NET runtime ประสบปัญหานี้อย่างมาก ดังนั้นเวลา cold start สำหรับ function ที่ใช้ runtime เหล่านี้อาจมีนัยสำคัญ

ในความเป็นจริง อย่างไรก็ตาม runtime เหล่านี้แทบไม่ค่อย cold start เลย บน AWS อย่างน้อย runtime จะถูกเก็บให้ "warm" เพื่อให้ request ที่เข้ามาถูกให้บริการโดย instance ที่ถูกเปิดและรันอยู่แล้ว สิ่งนี้เกิดขึ้นในระดับที่มันยากที่จะประเมินผลกระทบของ "cold start" ในปัจจุบันเนื่องจากการปรับแต่งที่ทำโดย FaaS provider เบื้องหลัง ถึงกระนั้น ถ้านี่เป็นข้อกังวล การยึดติดกับภาษาที่ runtime มีเวลาเปิดตัวที่รวดเร็ว (Go, Python, Node และ Ruby เป็นตัวอย่างที่นึกถึง) สามารถหลีกเลี่ยงปัญหานี้ได้อย่างมีประสิทธิภาพ

สุดท้าย ด้าน dynamic scaling ของ function สามารถกลายเป็นปัญหาได้จริง ๆ Function ถูกเปิดตัวเมื่อถูก trigger platform ทั้งหมดที่ผมเคยใช้มีขีดจำกัดตายตัวเกี่ยวกับจำนวน concurrent function invocation สูงสุด ซึ่งเป็นสิ่งที่คุณอาจต้องจดจำอย่างระมัดระวัง ผมเคยคุยกับมากกว่าหนึ่งทีมที่มีปัญหาเรื่อง function scale ขึ้นและครอบงำส่วนอื่น ๆ ของ infrastructure ที่ไม่มีคุณสมบัติการ scale แบบเดียวกัน Steve Faulkner จาก Bustle ได้แชร์ ตัวอย่างหนึ่งแบบนี้ ที่ function ที่ scale ขึ้นทำให้ Redis infrastructure ของ Bustle รับโหลดเกิน ก่อให้เกิดปัญหาใน production ถ้าส่วนหนึ่งของระบบของคุณสามารถ scale แบบไดนามิกได้ แต่ส่วนอื่น ๆ ของระบบของคุณไม่สามารถทำได้ คุณอาจพบว่าความไม่สอดคล้องกันนี้สามารถสร้างปัญหาปวดหัวอย่างมาก

Mapping to microservices (การ Map ไปสู่ Microservices)

จนถึงตอนนี้ในการอภิปรายเรื่องตัวเลือกการ deploy ต่าง ๆ ของเรา การ map จาก microservice instance ไปสู่กลไกการ deploy นั้นค่อนข้างตรงไปตรงมา Microservice instance เดียวสามารถถูก deploy ลงบน virtual machine, package เป็น container เดียว หรือแม้แต่วางลงบน application container อย่าง Tomcat หรือ IIS ด้วย FaaS สิ่งต่าง ๆ จะสับสนมากขึ้นเล็กน้อย

Function per microservice (Function ต่อหนึ่ง Microservice)

ตอนนี้เห็นได้ชัดว่า microservice instance เดียวสามารถถูก deploy เป็น function เดียวได้ ดังที่แสดงใน Figure 8-18 นี่น่าจะเป็นจุดเริ่มต้นที่สมเหตุสมผล นี่ยังคงแนวคิดของ microservice instance ในฐานะหน่วยของการ deploy ไว้ ซึ่งเป็นโมเดลที่เราได้สำรวจมากที่สุดจนถึงตอนนี้

bms2 0818

Figure 8-18. Service Expenses ของเราถูก implement เป็น function เดียว

เมื่อถูกเรียก FaaS platform จะ trigger entry point เดียวใน function ที่ deploy ของคุณ สิ่งนี้หมายความว่าถ้าคุณจะมี function deployment เดียวสำหรับ service ทั้งหมดของคุณ คุณจะต้องมีวิธีบางอย่างในการ dispatch จาก entry point นั้นไปยังส่วนต่าง ๆ ของ functionality ใน microservice ของคุณ ถ้าคุณกำลัง implement service Expenses เป็น REST-based microservice คุณอาจมี resource ต่าง ๆ ที่ถูก expose เช่น /receipt , /claim หรือ /report ด้วยโมเดลนี้ request สำหรับ resource เหล่านี้จะเข้ามาผ่าน entry point เดียวกันนี้ ดังนั้นคุณจะต้อง direct call ที่เข้ามาไปยังชิ้นส่วนของ functionality ที่เหมาะสมตาม inbound request path

Function per aggregate (Function ต่อหนึ่ง Aggregate)

แล้วเราจะแบ่ง microservice instance ออกเป็น function ขนาดเล็กลงได้อย่างไร? ถ้าคุณใช้ domain-driven design คุณอาจได้ model aggregate ของคุณไว้อย่างชัดเจนแล้ว (คือกลุ่มของ object ที่ถูกจัดการเป็น entity เดียว มักอ้างอิงถึงแนวคิดในโลกจริง) ถ้า microservice instance ของคุณจัดการหลาย aggregate โมเดลหนึ่งที่ผมคิดว่าสมเหตุสมผลคือแยก function ออกมาสำหรับแต่ละ aggregate ดังที่แสดงใน Figure 8-19 สิ่งนี้ทำให้แน่ใจว่า logic ทั้งหมดสำหรับ aggregate เดียวถูกบรรจุอยู่ภายใน function เอง ทำให้ง่ายขึ้นในการทำให้แน่ใจว่ามี implementation ที่สอดคล้องกันสำหรับการจัดการ life-cycle ของ aggregate

bms2 0819

Figure 8-19. Service Expenses ถูก deploy เป็น function หลายตัว โดยแต่ละตัวจัดการ aggregate ที่แตกต่างกัน

ด้วยโมเดลนี้ microservice instance ของเราไม่ได้ map ไปยังหน่วยของการ deploy เดียวอีกต่อไป แทนที่จะเป็นแบบนั้น microservice ของเรากลายเป็นแนวคิดเชิง logical มากขึ้นที่ประกอบด้วย function ที่แตกต่างกันหลายตัวซึ่งในทางทฤษฎีสามารถถูก deploy ได้อย่างอิสระจากกัน

มีข้อควรระวังบางอย่างตรงนี้ ประการแรก ผมขอแนะนำอย่างยิ่งให้คุณรักษา external interface ที่หยาบกว่าไว้ สำหรับ upstream consumer พวกเขายังคงคุยกับ service Expenses อยู่—พวกเขาไม่รู้ตัวว่า request ถูก map ไปยัง aggregate ที่มีขอบเขตเล็กกว่า สิ่งนี้ทำให้แน่ใจว่าถ้าคุณเปลี่ยนใจและต้องการรวมสิ่งต่าง ๆ กลับเข้าด้วยกันหรือแม้แต่ restructure aggregate model คุณจะไม่กระทบ upstream consumer

ประเด็นที่สองเกี่ยวข้องกับข้อมูล aggregate เหล่านี้ควรใช้ shared database ต่อไปหรือไม่? ในประเด็นนี้ ผมค่อนข้างผ่อนคลาย โดยสมมติว่าทีมเดียวกันจัดการ function ทั้งหมดนี้ และในเชิงแนวคิดมันยังคงเป็น "service" เดียว ผมจะโอเคกับการที่พวกมันยังคงใช้ฐานข้อมูลเดียวกัน ดังที่ Figure 8-20 แสดงให้เห็น

อย่างไรก็ตาม เมื่อเวลาผ่านไป ถ้าความต้องการของแต่ละ aggregate function แตกต่างกันมากขึ้น ผมมีแนวโน้มที่จะแยกการใช้ข้อมูลของพวกมันออกจากกัน ดังที่เห็นใน Figure 8-21 โดยเฉพาะถ้าคุณเริ่มเห็น coupling ใน data tier ที่ขัดขวางความสามารถของคุณในการเปลี่ยนแปลงมันได้อย่างง่ายดาย ณ จุดนี้ คุณอาจโต้แย้งได้ว่า function เหล่านี้จะกลายเป็น microservices ในสิทธิ์ของตัวเอง—แม้ว่าตามที่ผมเพิ่งอธิบายไป อาจมีคุณค่าในการยังคงนำเสนอพวกมันเป็น microservice เดียวต่อ upstream consumer

bms2 0820

Figure 8-20. Function ที่แตกต่างกันใช้ฐานข้อมูลเดียวกัน เนื่องจากทั้งหมดเป็นส่วนหนึ่งของ microservice เดียวกันในเชิง logical และถูกจัดการโดยทีมเดียวกัน

bms2 0821

Figure 8-21. แต่ละ function ใช้ฐานข้อมูลของตัวเอง

การ map จาก microservice เดียวไปสู่หน่วยที่ deploy ได้ขนาดเล็กหลายหน่วยนี้บิดเบือนคำจำกัดความก่อนหน้าของเราเกี่ยวกับ microservice ไปบ้าง โดยปกติแล้วเราคิดว่า microservice คือหน่วยที่ deploy ได้อย่างอิสระ—ตอนนี้ microservice หนึ่งตัวประกอบด้วยหน่วยที่ deploy ได้อย่างอิสระ หลายหน่วยที่แตกต่างกัน โดยแนวคิด ในตัวอย่างนี้ microservice เคลื่อนไปสู่การเป็นแนวคิดเชิง logical มากกว่าเชิง physical

Get even more fine-grained (แบ่งให้ละเอียดยิ่งขึ้นไปอีก)

ถ้าคุณต้องการแบ่งให้เล็กลงไปอีก มีความล่อใจที่จะแบ่ง function ต่อ aggregate ของคุณออกเป็นชิ้นส่วนเล็กลง ผมระมัดระวังมากกว่านี้ นอกเหนือจากการระเบิดจำนวน function ที่สิ่งนี้น่าจะสร้างขึ้น มันยังละเมิดหนึ่งในหลักการหลักของ aggregate—นั่นคือเราต้องการปฏิบัติต่อมันเป็นหน่วยเดียว เพื่อให้แน่ใจว่าเราสามารถจัดการความสมบูรณ์ของ aggregate เองได้ดีขึ้น

ผมเคยพิจารณาแนวคิดในการทำให้แต่ละ state transition ของ aggregate เป็น function ของตัวเอง แต่ผมถอยกลับจากแนวคิดนี้เนื่องจากปัญหาที่เกี่ยวข้องกับความไม่สอดคล้องกัน เมื่อคุณมีสิ่งที่ deploy ได้อย่างอิสระต่างกัน แต่ละตัวจัดการส่วนที่แตกต่างกันของ state transition โดยรวม การทำให้แน่ใจว่าสิ่งต่าง ๆ ถูกทำอย่างเหมาะสมจะยากขึ้นมาก มันทำให้เราเข้าสู่พื้นที่ของ saga ซึ่งเราพูดถึงใน Chapter 6 เมื่อ implement business process ที่ซับซ้อน แนวคิดอย่าง saga สำคัญ และงานนั้นสมเหตุสมผล ผมยังคงพยายามหาคุณค่าในการเพิ่มความซับซ้อนนี้ที่ระดับการจัดการ aggregate เดียวที่สามารถจัดการได้ง่าย ๆ ด้วย function เดียว

The way forward (หนทางข้างหน้า)

ผมยังคงเชื่อว่าอนาคตสำหรับนักพัฒนาส่วนใหญ่คือการใช้ platform ที่ซ่อนรายละเอียดเบื้องล่างส่วนใหญ่จากพวกเขา หลายปีที่ผ่านมา Heroku เป็นสิ่งที่ใกล้เคียงที่สุดที่ผมสามารถชี้ให้เห็นได้ในแง่ของสิ่งที่หาจุดสมดุลได้ถูกต้อง แต่ตอนนี้เรามี FaaS และ ecosystem ที่กว้างขึ้นของข้อเสนอ serverless แบบ turnkey ที่วางเส้นทางที่แตกต่างออกไป

ยังมีปัญหาที่ต้องได้รับการแก้ไขกับ FaaS แต่ผมรู้สึกว่า แม้ข้อเสนอชุดปัจจุบันยังต้องเปลี่ยนแปลงเพื่อแก้ไขปัญหาเหล่านี้ นี่คือประเภทของ platform ที่นักพัฒนาส่วนใหญ่จะจบลงด้วยการใช้ ไม่ใช่ทุกแอปพลิเคชันที่จะเข้ากับ FaaS ecosystem ได้อย่างพอดีเมื่อพิจารณาข้อจำกัด แต่สำหรับแอปพลิเคชันที่เข้ากันได้ ผู้คนก็เห็นประโยชน์ที่มีนัยสำคัญอยู่แล้ว ด้วยงานที่มากขึ้นเรื่อย ๆ ที่ทุ่มให้กับข้อเสนอ FaaS ที่รองรับด้วย Kubernetes ผู้ที่ไม่สามารถใช้ FaaS solution ที่จัดหาโดย cloud provider หลัก ๆ โดยตรงได้จะสามารถใช้ประโยชน์จากวิธีการทำงานใหม่นี้ได้มากขึ้นเรื่อย ๆ

ดังนั้น แม้ FaaS อาจไม่ทำงานได้กับทุกอย่าง มันก็เป็นสิ่งที่ผมขอเรียกร้องให้คนสำรวจอย่างแน่นอน และสำหรับลูกค้าของผมที่กำลังพิจารณาย้ายไปสู่ cloud-based Kubernetes solution ผมได้เร่งให้หลายคนสำรวจ FaaS ก่อน เพราะมันอาจให้ทุกอย่างที่พวกเขาต้องการในขณะที่ซ่อนความซับซ้อนที่มีนัยสำคัญและ offload งานจำนวนมาก

ผมเห็นองค์กรมากขึ้นเรื่อย ๆ ที่ใช้ FaaS เป็นส่วนหนึ่งของ solution ที่กว้างขึ้น เลือก FaaS สำหรับ use case เฉพาะที่มันเหมาะสม ตัวอย่างที่ดีคือ BBC ซึ่งใช้ Lambda function เป็นส่วนหนึ่งของ core technology stack ที่ให้บริการเว็บไซต์ BBC News ระบบโดยรวมใช้ผสมกันระหว่าง Lambda และ EC2 instance—โดย EC2 instance มักถูกใช้ในสถานการณ์ที่การเรียก Lambda function จะมีค่าใช้จ่ายสูงเกินไป 3