Before Anything Else (ก่อนอื่นใด)
ก่อนที่คุณจะทำอะไรอื่นเพื่อสร้างสถาปัตยกรรม microservice ของคุณ ให้ทำ log aggregation tool ขึ้นมาใช้งานให้ได้ก่อน มองมันเป็น prerequisite สำหรับการสร้างสถาปัตยกรรม microservice คุณจะขอบคุณผมทีหลัง
ทีนี้ ก็เป็นความจริงเช่นกันที่ log aggregation มีข้อจำกัดของมัน และเมื่อเวลาผ่านไป คุณอาจอยากมองหา tooling ที่ซับซ้อนกว่ามาเสริมหรือแม้แต่แทนที่สิ่งที่ log aggregation มอบให้ได้ แต่ถึงอย่างนั้น มันก็ยังเป็นจุดเริ่มต้นที่ยอดเยี่ยม
Common format (รูปแบบร่วม)
ถ้า คุณจะ aggregate log ของคุณ คุณจะต้องสามารถรัน query ข้าม log เหล่านั้นเพื่อดึงข้อมูลที่มีประโยชน์ออกมาได้ เพื่อให้สิ่งนี้ทำงานได้ มันสำคัญที่คุณจะเลือก standard log format ที่สมเหตุสมผล—ไม่งั้น query ของคุณจะยากหรือแทบเป็นไปไม่ได้เลยที่จะเขียน คุณอยากให้ date, time, ชื่อ microservice, log level และอื่น ๆ อยู่ในตำแหน่งที่สม่ำเสมอในแต่ละ log
Log forwarding agent บางตัวให้คุณสามารถ reformat log ก่อนที่จะส่งต่อไปยัง central log store ได้ ส่วนตัวแล้ว ผมจะหลีกเลี่ยงสิ่งนี้เท่าที่ทำได้ ปัญหาคือการ reformat log อาจใช้ computational resource สูงมาก ถึงขนาดที่ผมเคยเห็นปัญหา production จริง ๆ ที่เกิดจาก CPU ถูกดึงไปทำงานนี้ มันดีกว่ามากที่จะเปลี่ยน log ตั้งแต่ตอนที่มันถูกเขียนโดยตัว microservice ของคุณเอง ผมจะเก็บการใช้ log forwarding agent สำหรับ reformat log ไว้เฉพาะที่ที่ผมไม่สามารถเปลี่ยน source log format ได้—เช่น legacy หรือซอฟต์แวร์ของบุคคลที่สาม
ผมคิดว่าในช่วงเวลาตั้งแต่ผมเขียนหนังสือฉบับพิมพ์ครั้งแรก มาตรฐานอุตสาหกรรมร่วมสำหรับ logging น่าจะได้รับความนิยมไปแล้ว แต่ดูเหมือนมันจะยังไม่เกิดขึ้น มี variation มากมายที่ดูเหมือนจะมีอยู่ ซึ่งโดยปกติจะเกี่ยวข้องกับการใช้ standard access log format ที่ web server อย่าง Apache และ nginx รองรับ แล้วขยายมันโดยเพิ่ม column ของข้อมูลเข้าไปอีก สิ่งสำคัญคือภายในสถาปัตยกรรม microservice ของคุณเอง คุณเลือก format ที่คุณจะ standardize ภายในองค์กร
ถ้าคุณใช้ log format ที่ค่อนข้างง่าย คุณก็แค่จะ emit บรรทัดข้อความธรรมดาที่มีข้อมูลเฉพาะอยู่ในตำแหน่งเฉพาะของ log line ใน Example 10-1 เราจะเห็นตัวอย่าง format หนึ่ง
Example 10-1. ตัวอย่าง log บางส่วน
15-02-2020 16:00:58 Order INFO [abc-123] Customer 2112 has placed order 988827
15-02-2020 16:01:01 Payment INFO [abc-123] Payment $20.99 for 988827 by cust 2112
Log aggregation tool จะต้องรู้วิธี parse string นี้เพื่อดึงข้อมูลที่เราอาจอยาก query—timestamp, ชื่อ microservice หรือ log level เป็นต้น ในตัวอย่างนี้ มันทำได้ เพราะข้อมูลเหล่านี้อยู่ในตำแหน่งคงที่ตลอด log ของเรา—date เป็น column แรก time เป็น column ที่สอง เป็นต้น แต่มันจะมีปัญหามากกว่า ถ้าเราอยากหา log line ที่เกี่ยวข้องกับลูกค้ารายหนึ่ง—customer ID อยู่ใน log line ทั้งสองบรรทัด แต่แสดงในตำแหน่งที่ต่างกัน นี่คือจุดที่เราอาจเริ่มคิดถึงการเขียน log line ที่มีโครงสร้างมากขึ้น บางทีอาจใช้ JSON format เพื่อให้เราหาข้อมูลอย่าง customer หรือ order ID ในตำแหน่งที่สม่ำเสมอได้ อีกครั้ง log aggregation tool จะต้องถูก configure ให้ parse และดึงข้อมูลที่ต้องการจาก log สิ่งอื่นที่ควรสังเกตคือ ถ้าคุณ log JSON มันอาจทำให้อ่านโดยตรงด้วยมนุษย์ได้ยากขึ้น หากไม่มี tooling เพิ่มเติมเพื่อ parse ค่าที่ต้องการออกมา—การอ่าน log ใน plain text viewer เฉย ๆ อาจไม่มีประโยชน์มากนัก
Correlating log lines (การ Correlate Log Line)
เมื่อ มี service จำนวนมากที่ทำงานร่วมกันเพื่อมอบความสามารถให้ end-user ใด ๆ การเรียกครั้งแรกเพียงครั้งเดียวอาจนำไปสู่การเรียก service downstream หลายครั้ง ตัวอย่างเช่น ลองพิจารณาตัวอย่างใน MusicCorp ดังที่แสดงใน Figure 10-5 เรากำลังสมัครสมาชิกลูกค้าให้กับ streaming service ใหม่ของเรา ลูกค้าเลือก streaming package ที่ต้องการแล้วกดปุ่ม submit เบื้องหลังฉาก เมื่อปุ่มถูกกดใน UI มันจะไปที่ Gateway ซึ่งอยู่ที่ขอบของระบบเรา จากนั้นมันจะส่งต่อการเรียกไปยัง microservice Streaming Microservice นี้สื่อสารกับ Payment เพื่อรับ payment แรก ใช้ microservice Customer เพื่ออัปเดตว่าลูกค้ารายนี้เปิดใช้งาน streaming แล้ว และส่งอีเมลไปหาลูกค้าโดยใช้ microservice Email ของเรา เพื่อยืนยันว่าพวกเขาสมัครสมาชิกแล้ว
จะเกิดอะไรขึ้นถ้าการเรียกไปยัง microservice Payment ดันสร้าง error แปลก ๆ ขึ้นมา? เราจะพูดถึงการจัดการ failure อย่างละเอียดใน Chapter 12 แต่ลองพิจารณาความยากลำบากในการวินิจฉัยว่าเกิดอะไรขึ้น
ปัญหาคือ microservice ตัวเดียวที่บันทึก error คือ microservice Payment ของเรา ถ้าเราโชคดี เราอาจหาได้ว่า request ไหนก่อปัญหา และเราอาจดู parameter ของการเรียกนั้นได้ด้วยซ้ำ แต่เราไม่สามารถเห็น error นี้ในบริบทที่กว้างขึ้นที่มันเกิดขึ้นได้ ในตัวอย่างนี้โดยเฉพาะ แม้เราจะสมมติว่าแต่ละ interaction สร้าง log line เพียงบรรทัดเดียว เราก็จะมี log line 5 บรรทัดที่มีข้อมูลเกี่ยวกับ flow ของการเรียกนี้ การเห็น log line เหล่านี้ถูกจัดกลุ่มไว้ด้วยกันสามารถมีประโยชน์อย่างมาก
Figure 10-5. ชุดของการเรียกข้าม microservice หลายตัวที่เกี่ยวข้องกับการสมัคร ลูกค้า
วิธีหนึ่งที่มีประโยชน์ที่นี่คือการใช้ correlation ID—สิ่งที่เราพูดถึงครั้งแรกใน Chapter 6 เมื่อพูดถึง saga เมื่อการเรียกครั้งแรกเกิดขึ้น คุณจะสร้าง unique ID ที่จะถูกใช้เพื่อ correlate การเรียกทั้งหมดที่ตามมาที่เกี่ยวข้องกับ request นั้น ใน Figure 10-6 เราสร้าง ID นี้ใน Gateway แล้วมันก็ถูกส่งต่อเป็น parameter ไปกับการเรียกที่ตามมาทั้งหมด
การ log กิจกรรมใด ๆ โดย microservice ที่เกิดจาก incoming call นี้ จะถูกบันทึกพร้อมกับ correlation ID เดียวกัน ซึ่งเราวางไว้ในตำแหน่งที่สม่ำเสมอในแต่ละ log line ดังที่แสดงใน Example 10-2 สิ่งนี้ทำให้ง่ายที่จะดึง log ทั้งหมดที่เกี่ยวข้องกับ correlation ID หนึ่ง ๆ ออกมาได้ในภายหลัง
Example 10-2. การใช้ correlation ID ในตำแหน่งคงที่ของ log line
15-02-2020 16:01:01 Gateway INFO [abc-123] Signup for streaming
15-02-2020 16:01:02 Streaming INFO [abc-123] Cust 773 signs up ...
15-02-2020 16:01:03 Customer INFO [abc-123] Streaming package added ...
15-02-2020 16:01:03 Email INFO [abc-123] Send streaming welcome ...
15-02-2020 16:01:03 Payment ERROR [abc-123] ValidatePayment ...
Figure 10-6. การสร้าง correlation ID สำหรับชุดของการเรียก
แน่นอนว่าคุณจะต้องมั่นใจว่าแต่ละ service รู้ว่าต้องส่งต่อ correlation ID นี้ นี่คือจุดที่คุณต้องมาตรฐานและเข้มงวดมากขึ้นในการบังคับใช้สิ่งนี้ทั่วทั้งระบบของคุณ แต่เมื่อคุณทำสิ่งนี้แล้ว คุณสามารถสร้าง tooling เพื่อติดตาม interaction ทุกประเภทได้จริง Tooling แบบนี้มีประโยชน์ในการตามหา event storm หรือ corner case แปลก ๆ หรือแม้แต่การระบุ transaction ที่มีค่าใช้จ่ายสูงเป็นพิเศษ เพราะคุณสามารถเห็นภาพ cascade ทั้งหมดของการเรียกได้
Correlation ID ใน log เป็นสิ่งที่ดูเหมือนจะไม่ค่อยมีประโยชน์ในตอนแรก แต่เชื่อผมเถอะ—เมื่อเวลาผ่านไป มันจะมีประโยชน์ อย่างเหลือเชื่อ น่าเสียดายที่มันอาจเป็นเรื่องปวดหัวที่จะ retrofit เข้าไปในระบบภายหลัง ด้วยเหตุนี้ ผมขอแนะนำอย่างยิ่งให้คุณ implement correlation ID ใน logging ให้เร็วที่สุดเท่าที่จะทำได้ Log แน่นอนว่าพาคุณไปได้แค่ระดับหนึ่งในเรื่องนี้—ปัญหาบางประเภทแก้ได้ดีกว่าด้วยเครื่องมือ distributed tracing ซึ่งเราจะสำรวจในไม่ช้า แต่ correlation ID ง่าย ๆ ใน logfile สามารถมีประโยชน์อย่างมากในตอนแรก ทำให้คุณสามารถเลื่อนการใช้เครื่องมือ tracing เฉพาะทางออกไปจนกว่าระบบของคุณจะซับซ้อนพอที่จะคุ้มค่า
Tip (เคล็ดลับ)
เมื่อคุณมี log aggregation แล้ว ให้ใส่ correlation ID เข้าไปโดยเร็วที่สุด ทำได้ง่ายตั้งแต่เริ่มต้นแต่ยากที่จะ retrofit ในภายหลัง มันจะช่วยเพิ่มคุณค่าให้ log ของคุณอย่างมาก
Timing (เรื่องของเวลา)
เมื่อ มองดูรายชื่อ log line เราอาจถูกหลอกให้คิดว่าเรากำลังเห็นลำดับเหตุการณ์ที่ถูกต้อง ซึ่งจะช่วยให้เราเข้าใจว่าเกิดอะไรขึ้นและในลำดับไหน ท้ายที่สุด แต่ละบรรทัดของ log ของเรามี date และ time—แล้วทำไมเราจะใช้สิ่งนี้เป็นวิธีระบุลำดับที่สิ่งต่าง ๆ เกิดขึ้นไม่ได้ล่ะ? ในลำดับการเรียกใน Example 10-2 เราเห็น log line จาก Gateway ตามด้วย log จาก Streaming , Customer , Email และจากนั้นก็ microservice Payment เราอาจสรุปได้ว่านี่คือลำดับที่การเรียกเกิดขึ้นจริง น่าเสียดายที่เราไม่สามารถมั่นใจได้เสมอไปว่านั่นเป็นความจริง
Log line ถูกสร้างขึ้นบนเครื่องที่ microservice instance เหล่านี้กำลังรันอยู่ หลังจากถูกเขียนในเครื่องแล้ว ณ จุดหนึ่ง log เหล่านี้ก็จะถูกส่งต่อ นั่นหมายความว่า date stamp ใน log line ถูกสร้างขึ้นบนเครื่องที่ microservice กำลังรันอยู่ น่าเสียดายที่เราไม่สามารถรับประกันได้ว่านาฬิกาบนเครื่องต่าง ๆ นั้น sync กันอยู่ นั่นหมายความว่านาฬิกาบนเครื่องที่ microservice Email กำลังรันอยู่ อาจเร็วกว่านาฬิกาบนเครื่องที่ microservice Payment กำลังรันอยู่สองสามวินาที—นี่อาจทำให้ดูเหมือนว่ามีบางอย่างเกิดขึ้นใน microservice Email ก่อนที่มันจะเกิดขึ้นใน microservice Payment แต่นั่นอาจเป็นเพียงเพราะ clock skew นี้เท่านั้น
ปัญหาของ clock skew ก่อให้เกิดปัญหาต่าง ๆ นานาในระบบ distributed มี protocol ที่พยายามลด clock skew ในระบบ—Network Time Protocol (NTP) เป็นตัวอย่างที่ใช้กันแพร่หลายที่สุด อย่างไรก็ตาม NTP ไม่ได้รับประกันว่าจะทำงานได้เสมอไป และแม้เมื่อมันทำงานได้ สิ่งที่มันทำได้ก็แค่ลด skew ไม่ใช่กำจัดมันออกไปทั้งหมด ถ้าคุณมีลำดับการเรียกที่เกิดขึ้นใกล้กันมาก คุณอาจพบว่าแม้แต่ skew แค่หนึ่งวินาทีข้ามเครื่อง ก็เพียงพอที่จะทำให้ความเข้าใจของคุณต่อลำดับการเรียกเปลี่ยนไปโดยสิ้นเชิง
โดยพื้นฐานแล้ว นี่หมายความว่าเรามีข้อจำกัดสองอย่างเมื่อพูดถึงเวลาใน log เราไม่สามารถได้ข้อมูล timing ที่แม่นยำเต็มที่สำหรับ flow การเรียกโดยรวม และเราก็ไม่สามารถเข้าใจ causality ได้เช่นกัน
ในแง่ของการช่วยแก้ปัญหานี้เพื่อให้เราเข้าใจลำดับที่แท้จริงของสิ่งต่าง ๆ Leslie Lamport 4 ได้เสนอระบบ logical clock ขึ้นมา โดยใช้ counter เพื่อติดตามลำดับของการเรียก คุณสามารถ implement scheme ที่คล้ายกันได้ถ้าคุณต้องการ และมี variation หลายแบบของ scheme นี้อยู่ ส่วนตัวแล้ว ถ้าผมต้องการข้อมูลลำดับการเรียกที่แม่นยำมากขึ้น และผมก็อยากได้ timing ที่แม่นยำมากขึ้นด้วย ผมจะโน้มเอียงไปทางการใช้เครื่องมือ distributed tracing มากกว่า ซึ่งจะแก้ปัญหาทั้งสองอย่างให้ผมได้ เราจะดู distributed tracing อย่างละเอียดมากขึ้นในบทนี้ต่อไป
Implementations (การ Implement)
พื้นที่ ไม่กี่แห่งในอุตสาหกรรมของเราที่มีการแข่งขันสูงเท่ากับ log aggregation และมีโซลูชันหลากหลายอยู่ในพื้นที่นี้
Toolchain open source ยอดนิยมสำหรับ log aggregation คือการใช้ log forwarding agent อย่าง Fluentd เพื่อส่ง log ไปยัง Elasticsearch โดยใช้ Kibana เป็นวิธี slice และ dice stream ของ log ที่ได้ ความท้าทายที่ใหญ่ที่สุดของ stack นี้มักจะเป็น overhead ในการจัดการ Elasticsearch เอง แต่ก็อาจเป็นปัญหาน้อยลง ถ้าคุณต้องรัน Elasticsearch เพื่อจุดประสงค์อื่นอยู่แล้ว หรือถ้าคุณใช้ managed provider ผมขอเตือนเพิ่มเติมอีกสองเรื่องเกี่ยวกับการใช้ toolchain นี้ อย่างแรก มีความพยายามด้าน marketing มากมายที่ทำให้ Elasticsearch ถูกมองว่าเป็นฐานข้อมูล ส่วนตัวแล้ว เรื่องนี้ทำให้ผมรู้สึกไม่สบายใจมาตลอด การเอาสิ่งที่เคยถูกโฆษณาว่าเป็น search index มา rebrand เป็นฐานข้อมูล อาจก่อปัญหาอย่างมาก เรามักตั้งสมมติฐานโดยปริยายว่าฐานข้อมูลทำงานและมีพฤติกรรมอย่างไร และเราปฏิบัติต่อมันตามนั้น โดยมองว่ามันเป็น source of truth สำหรับข้อมูลสำคัญ แต่โดยการออกแบบแล้ว search index ไม่ใช่ source of truth มันเป็นเพียง projection ของ source of truth เท่านั้น Elasticsearch เคยประสบปัญหาในอดีตที่ทำให้ผมต้องหยุดคิด 5 แม้ผมจะมั่นใจว่าปัญหาหลายอย่างเหล่านี้ได้รับการแก้ไขแล้ว การอ่านเรื่องปัญหาเหล่านี้ของผมเองก็ทำให้ผมระมัดระวังเกี่ยวกับการใช้ Elasticsearch ในบางสถานการณ์ และแน่นอนเมื่อพิจารณามันในฐานะฐานข้อมูล การมี search index ที่บางครั้งอาจสูญเสียข้อมูลไปบ้างไม่ใช่ปัญหา ถ้าคุณสามารถ re-index ได้อยู่แล้ว แต่การปฏิบัติต่อมันเหมือนฐานข้อมูลเป็นอีกเรื่องหนึ่งไปเลย ถ้าผมใช้ stack นี้และไม่สามารถเสี่ยงที่จะสูญเสียข้อมูล log ได้ ผมจะต้องมั่นใจว่าผมสามารถ re-index log ต้นฉบับได้ ถ้ามีอะไรผิดพลาด
ข้อกังวลชุดที่สองไม่ได้เกี่ยวกับแง่มุมทางเทคนิคของ Elasticsearch และ Kibana มากนัก แต่เกี่ยวกับพฤติกรรมของ Elastic บริษัทที่อยู่เบื้องหลังโปรเจกต์เหล่านี้มากกว่า เมื่อไม่นานมานี้ Elastic ตัดสินใจเปลี่ยน license สำหรับ source code ของทั้ง Elasticsearch database หลักและ Kibana จาก open source license ที่ใช้และยอมรับกันอย่างแพร่หลาย (Apache 2.0) เป็น non–open source Server Side Public License (SSPL) 6 แรงผลักดัน สำหรับการเปลี่ยน license ครั้งนี้ ดูเหมือนจะมาจากความรู้สึกไม่พอใจของ Elastic ที่องค์กรอย่าง AWS สร้าง commercial offering ที่ประสบความสำเร็จจากเทคโนโลยีนี้ ซึ่งแย่งส่วนแบ่งจาก commercial offering ของ Elastic เอง นอกจากความกังวลที่ว่า SSPL อาจ "viral" ในธรรมชาติ (คล้ายกับ GNU General Public License) การตัดสินใจนี้ทำให้หลายคนโกรธเคือง มีคนมากกว่าพันคนที่ contribute code ให้ Elasticsearch โดยคาดหวังว่าพวกเขากำลังบริจาคให้กับผลิตภัณฑ์ open source มีความย้อนแย้งเพิ่มเติมคือ Elasticsearch เอง และดังนั้นก็รวมถึงบริษัท Elastic ส่วนใหญ่ ถูกสร้างขึ้นบนเทคโนโลยีจากโปรเจกต์ open source Lucene ในขณะที่เขียนหนังสือเล่มนี้ AWS ได้ให้คำมั่นตามที่คาดเดาได้ ว่าจะสร้างและดูแล open source fork ของทั้ง Elasticsearch และ Kibana ภายใต้ open source Apache 2.0 license เดิมที่เคยใช้
ในหลาย ๆ ทาง Kibana เป็นความพยายามที่น่าชื่นชมในการสร้างทางเลือก open source ให้กับตัวเลือกเชิงพาณิชย์ราคาแพงอย่าง Splunk Splunk ดูเหมือนจะดีมากเท่าที่มันเป็น แต่ลูกค้า Splunk ทุกคนที่ผมเคยคุยด้วยก็บอกผมด้วยว่ามันแพงจนตาแตกได้ ทั้งในแง่ค่า license และค่า hardware ลูกค้าหลายรายก็เห็นคุณค่าของมัน อย่างไรก็ตาม ก็มีตัวเลือกเชิงพาณิชย์มากมายให้เลือก ส่วนตัวแล้วผมเป็นแฟนตัวยงของ Humio หลายคนชอบใช้ Datadog สำหรับ log aggregation และคุณก็มีโซลูชัน out-of-the-box พื้นฐานแต่ใช้งานได้ สำหรับ log aggregation จาก public cloud provider บางราย เช่น CloudWatch สำหรับ AWS หรือ Application Insights สำหรับ Azure
ความจริงคือคุณมีตัวเลือกมากมายมหาศาลในพื้นที่นี้ ตั้งแต่ open source ไปจนถึง commercial และตั้งแต่ self-hosted ไปจนถึง fully hosted ถ้าคุณอยากสร้างสถาปัตยกรรม microservice นี่ไม่ใช่หนึ่งในสิ่งที่คุณควรพบว่ายากที่จะจัดการให้เรียบร้อย
Shortcomings (ข้อบกพร่อง)
Log เป็นวิธีที่ยอดเยี่ยมและง่ายในการดึงข้อมูลออกมาจากระบบที่กำลังรันอยู่ของคุณอย่างรวดเร็ว ผมยังคงเชื่อมั่นว่าสำหรับสถาปัตยกรรม microservice ในระยะเริ่มต้น มีที่ไม่กี่แห่งที่จะให้ผลตอบแทนมากกว่า log เมื่อพูดถึงการปรับปรุง visibility ของแอปพลิเคชันของคุณใน production มันจะกลายเป็นเส้นเลือดใหญ่สำหรับการรวบรวมข้อมูลและการวินิจฉัย แต่นั่นก็ไม่ได้แปลว่าคุณไม่จำเป็นต้องตระหนักถึง challenge สำคัญบางอย่างที่อาจเกิดขึ้นกับ log
อย่างแรก อย่างที่เราได้พูดถึงไปแล้ว เนื่องจาก clock skew log เหล่านั้นจึงไม่สามารถพึ่งพาได้เสมอไปในการช่วยให้คุณเข้าใจลำดับที่การเรียกเกิดขึ้น Clock skew ระหว่างเครื่องนี้ยังหมายความว่า timing ที่แม่นยำของลำดับการเรียกจะเป็นปัญหาด้วย ซึ่งอาจจำกัดประโยชน์ของ log ในการตามหา latency bottleneck
ปัญหาหลักของ log คือ เมื่อคุณมี microservice และการเรียกมากขึ้น คุณจะสร้างข้อมูลออกมาเป็นจำนวนมาก มากมายมหาศาล นี่อาจส่งผลให้ต้นทุนสูงขึ้นในแง่ของการต้องการ hardware มากขึ้น และยังอาจเพิ่มค่าธรรมเนียมที่คุณจ่ายให้ service provider ของคุณด้วย (บาง provider คิดเงินตามการใช้งาน) และขึ้นอยู่กับว่า log aggregation toolchain ของคุณถูกสร้างขึ้นมาอย่างไร สิ่งนี้อาจส่งผลให้เกิด scaling challenge ด้วย โซลูชัน log aggregation บางตัวพยายามสร้าง index เมื่อได้รับ log data เพื่อทำให้ query เร็วขึ้น ปัญหาคือการดูแล index นั้นใช้ computational resource สูงมาก—และยิ่งคุณรับ log มากเท่าไหร่ และ index ยิ่งใหญ่ขึ้นเท่าไหร่ ปัญหานี้ก็ยิ่งรุนแรงขึ้นเท่านั้น สิ่งนี้นำไปสู่ความจำเป็นที่จะต้อง log อย่างเจาะจงมากขึ้นเพื่อลดปัญหานี้ ซึ่งในทางกลับกันก็สร้างงานเพิ่มขึ้น และเสี่ยงที่คุณอาจเลื่อนการ log ข้อมูลที่จริง ๆ แล้วมีค่าออกไป ผมเคยคุยกับทีมที่ดูแล Elasticsearch cluster สำหรับ developer tooling แบบ SaaS พวกเขาพบว่า Elasticsearch cluster ที่ใหญ่ที่สุดที่พวกเขาจะรันได้อย่างสบายใจ สามารถรองรับ logging ได้แค่หกสัปดาห์สำหรับผลิตภัณฑ์หนึ่งของพวกเขาเท่านั้น ทำให้ทีมต้องคอยย้ายข้อมูลไปมาตลอดเพื่อให้จัดการได้ ส่วนหนึ่งของเหตุผลที่ผมชอบ Humio คือนี่เป็นสิ่งที่นักพัฒนาของมันสร้างมาเพื่อรองรับโดยเฉพาะ—แทนที่จะดูแล index พวกเขาโฟกัสที่การ ingest ข้อมูลอย่างมีประสิทธิภาพและ scale ได้ พร้อมโซลูชันที่ฉลาดบางอย่างเพื่อพยายามลด query time ลง
แม้คุณจะมีโซลูชันที่เก็บปริมาณ log ที่คุณต้องการได้ log เหล่านี้ก็อาจมีข้อมูลที่มีค่าและ sensitive อยู่มาก นั่นหมายความว่าคุณอาจต้องจำกัดการเข้าถึง log (ซึ่งอาจทำให้ความพยายามของคุณในการมี collective ownership ของ microservice ใน production ซับซ้อนขึ้นไปอีก) และ log อาจกลายเป็นเป้าหมายของฝ่ายที่ประสงค์ร้าย คุณจึงอาจต้องพิจารณาไม่ log ข้อมูลบางประเภท (อย่างที่เราจะพูดถึงใน "Be frugal" ถ้า คุณไม่เก็บ ข้อมูล มันก็ไม่สามารถถูกขโมยได้) เพื่อลดผลกระทบจากการเข้าถึงโดยฝ่ายที่ไม่ได้รับอนุญาต
Metrics Aggregation
เช่นเดียว กับความท้าทายในการดู log สำหรับ host ต่าง ๆ เราต้องหาวิธีที่ดีกว่าในการรวบรวมและดูข้อมูลเกี่ยวกับระบบของเรา มันอาจยากที่จะรู้ว่า "ดี" หน้าตาเป็นอย่างไร เมื่อเรามอง metric สำหรับระบบที่ซับซ้อนมากขึ้น เว็บไซต์ของเราเห็น HTTP error code 4XX เกือบ 50 ครั้งต่อวินาที นั่นแย่ไหม? CPU load บน catalog service เพิ่มขึ้น 20% ตั้งแต่มื้อเที่ยง มีอะไรผิดพลาดหรือเปล่า? เคล็ดลับในการรู้ว่าเมื่อไหร่ควรตื่นตระหนกและเมื่อไหร่ควรผ่อนคลาย คือการรวบรวม metric เกี่ยวกับพฤติกรรมของระบบคุณในช่วงเวลาที่นานพอ จนเห็น pattern ที่ชัดเจนปรากฏขึ้น
ในสภาพแวดล้อมที่ซับซ้อนกว่านี้ เราจะ provision instance ใหม่ของ microservice ของเราค่อนข้างบ่อย ดังนั้นเราจึงอยากให้ระบบที่เราเลือกทำให้การเก็บ metric จาก host ใหม่ทำได้ง่ายมาก เราจะอยาก aggregate metric สำหรับทั้งระบบ—เช่น average CPU load—แต่เราก็อยาก aggregate metric นั้นสำหรับทุก instance ของ service หนึ่ง ๆ หรือแม้แต่สำหรับ instance เดียวของ service นั้นด้วย นั่นหมายความว่าเราจะต้องสามารถ associate metadata กับ metric เพื่อให้เรา infer โครงสร้างนี้ได้
ประโยชน์สำคัญอีกอย่างของการเข้าใจ trend ของคุณคือเมื่อพูดถึง capacity planning เรากำลังใกล้ถึงขีดจำกัดหรือยัง? อีกนานแค่ไหนก่อนที่เราจะต้องการ host เพิ่ม? ในอดีตเมื่อเราซื้อ host แบบ physical นี่มักเป็นงานประจำปี ในยุคใหม่ของ on-demand computing ที่ vendor infrastructure as service (IaaS) มอบให้ เราสามารถ scale up หรือ down ได้ภายในไม่กี่นาที ถ้าไม่ใช่ไม่กี่วินาที นั่นหมายความว่าถ้าเราเข้าใจ usage pattern ของเรา เราก็สามารถมั่นใจได้ว่าเรามี infrastructure เพียงพอที่จะรองรับความต้องการของเรา ยิ่งเราฉลาดในการติดตาม trend และรู้ว่าจะทำอะไรกับมันมากเท่าไหร่ ระบบของเราก็จะยิ่ง cost effective และตอบสนองได้ดีมากขึ้นเท่านั้น
เนื่องด้วยธรรมชาติของข้อมูลประเภทนี้ เราอาจอยากเก็บและรายงาน metric เหล่านี้ที่ resolution ต่างกัน ตัวอย่างเช่น ผมอาจอยากได้ CPU sample สำหรับ server ของผมที่ resolution หนึ่ง sample ทุก 10 วินาที ในช่วง 30 นาทีที่ผ่านมา เพื่อให้ตอบสนองได้ดีขึ้นต่อสถานการณ์ที่กำลังเกิดขึ้นในขณะนั้น ในทางกลับกัน CPU sample จาก server ของผมจากเดือนที่แล้ว อาจจำเป็นแค่สำหรับการวิเคราะห์ trend ทั่วไปเท่านั้น ดังนั้นผมอาจพอใจกับการคำนวณ average CPU sample เป็นรายชั่วโมง สิ่งนี้มักถูกทำในแพลตฟอร์ม metric มาตรฐาน เพื่อช่วยลด query time และลดการเก็บข้อมูลด้วย สำหรับสิ่งง่าย ๆ อย่าง CPU rate นี่อาจโอเค แต่กระบวนการ aggregate ข้อมูลเก่านั้นก็ทำให้เราสูญเสียข้อมูลไปเช่นกัน ปัญหาของความจำเป็นที่ข้อมูลนี้ต้องถูก aggregate คือคุณมักจะต้องตัดสินใจล่วงหน้าว่าจะ aggregate อะไร—คุณต้องเดาล่วงหน้าว่าข้อมูลไหนโอเคที่จะสูญเสียไป
เครื่องมือ metric มาตรฐานอาจดีเยี่ยมสำหรับการเข้าใจ trend หรือ failure mode ง่าย ๆ มันสำคัญมากด้วยซ้ำ แต่บ่อยครั้งมันก็ไม่ได้ช่วยให้ระบบของเรา observable มากขึ้น เพราะมันจำกัดประเภทของคำถามที่เราอยากถาม สิ่งต่าง ๆ จะเริ่มน่าสนใจเมื่อเราเปลี่ยนจากข้อมูลง่าย ๆ อย่าง response time, CPU หรือ disk space use ไปสู่การคิดกว้างขึ้นเกี่ยวกับประเภทของข้อมูลที่เราอยากจับ
Low versus high cardinality (Cardinality ต่ำเทียบกับสูง)
เครื่องมือ หลายตัว โดยเฉพาะตัวใหม่ ๆ ถูกสร้างขึ้นมาเพื่อรองรับการจัดเก็บและดึงข้อมูลที่มี cardinality สูง มีหลายวิธีที่จะอธิบาย cardinality แต่คุณอาจคิดว่ามันคือจำนวน field ที่สามารถ query ได้ง่ายในข้อมูลจุดหนึ่ง ๆ ยิ่งเรามี field ที่อยากจะ query กับข้อมูลของเรามากเท่าไหร่ cardinality ที่เราต้องรองรับก็ยิ่งสูงขึ้นเท่านั้น โดยพื้นฐานแล้ว สิ่งนี้จะยิ่งเป็นปัญหามากขึ้นกับ time-series database ด้วยเหตุผลที่ผมจะไม่ขยายความตรงนี้ แต่มันเกี่ยวข้องกับวิธีที่ระบบเหล่านี้ถูกสร้างขึ้นมา
ตัวอย่างเช่น ผมอาจอยากจับและ query ชื่อของ microservice, customer ID, request ID, build number ของซอฟต์แวร์ และ product ID ตลอดเวลา จากนั้นผมตัดสินใจจับข้อมูลเกี่ยวกับเครื่องด้วย ณ จุดนั้น—OS, system architecture, cloud provider และอื่น ๆ ผมอาจต้องจับข้อมูลทั้งหมดนั้นสำหรับข้อมูลแต่ละจุดที่ผมเก็บ เมื่อผมเพิ่มจำนวนสิ่งที่ผมอาจอยาก query cardinality ก็เพิ่มขึ้น และระบบที่ไม่ได้ถูกสร้างมาเพื่อ use case นี้จะยิ่งมีปัญหามากขึ้น อย่างที่ Charity Majors 7 ผู้ก่อตั้ง Honeycomb อธิบายไว้:
มันสรุปได้โดยพื้นฐานแล้วว่าคือ metric Metric คือจุดข้อมูล ตัวเลขเดียวที่มีชื่อและ tag ที่ระบุตัวตนบางอย่าง บริบททั้งหมดที่คุณได้มา ต้องถูกยัดลงใน tag เหล่านั้น แต่ write explosion ของการเขียน tag ทั้งหมดนั้นแพงมาก เพราะวิธีที่ metric ถูกเก็บบน disk การเก็บ metric นั้นถูกมาก แต่การเก็บ tag นั้นแพง และการเก็บ tag จำนวนมากต่อ metric จะทำให้ storage engine ของคุณหยุดทำงานอย่างรวดเร็ว
พูดในทางปฏิบัติ ระบบที่ถูกสร้างมาโดยคำนึงถึง cardinality ต่ำ จะประสบปัญหาอย่างมากถ้าคุณพยายามใส่ข้อมูล cardinality สูงเข้าไป ระบบอย่าง Prometheus ตัวอย่างเช่น ถูกสร้างมาเพื่อเก็บข้อมูลที่ค่อนข้างง่าย เช่น CPU rate สำหรับเครื่องหนึ่ง ๆ ในหลาย ๆ ทาง เราสามารถมอง Prometheus และเครื่องมือคล้ายกันว่าเป็น implementation ที่ยอดเยี่ยมของ metric storage และ querying แบบดั้งเดิม แต่การขาดความสามารถในการรองรับข้อมูล cardinality สูง อาจเป็นปัจจัยจำกัด นักพัฒนา Prometheus เปิดเผยเรื่องข้อจำกัดนี้อย่างตรงไปตรงมา :
จำไว้ว่าทุก combination ที่ unique ของ key-value label pair จะแทน time series ใหม่ ซึ่งสามารถเพิ่มปริมาณข้อมูลที่ถูกเก็บได้อย่างมาก อย่าใช้ label เพื่อเก็บ dimension ที่มี cardinality สูง (label value ที่ต่างกันจำนวนมาก) เช่น user ID, email address หรือชุดค่าที่ไม่มีขอบเขตอื่น ๆ
ระบบที่สามารถรองรับ cardinality สูงได้ จะสามารถให้คุณถามคำถามหลากหลายกับระบบของคุณได้มากขึ้น—บ่อยครั้งเป็นคำถามที่คุณไม่รู้ตัวว่าต้องถามมาก่อน นี่อาจเป็น concept ที่เข้าใจยากสักหน่อย โดยเฉพาะถ้าคุณเคยจัดการระบบ monolithic แบบ single-process ของคุณได้ค่อนข้างดีด้วยเครื่องมือแบบ "conventional" มากกว่า แม้แต่คนที่มีระบบใหญ่กว่าก็ยังต้องทนใช้ระบบ cardinality ต่ำ บ่อยครั้งเพราะพวกเขาไม่มีทางเลือก แต่เมื่อระบบของคุณซับซ้อนขึ้น คุณจะต้องปรับปรุงคุณภาพของ output ที่ระบบของคุณให้มา เพื่อช่วยปรับปรุง observability ของมัน นั่นหมายถึงการเก็บข้อมูลมากขึ้น และมี tooling ที่ให้คุณ slice และ dice ข้อมูลนี้ได้
Implementations (การ Implement)
ตั้งแต่ หนังสือฉบับพิมพ์ครั้งแรก Prometheus ได้กลายเป็นเครื่องมือ open source ยอดนิยมสำหรับการรวบรวมและ aggregate metric และในกรณีที่ผมอาจเคยแนะนำการใช้ Graphite (ซึ่งเคยได้รับคำแนะนำของผมในฉบับพิมพ์ครั้งแรก) Prometheus ก็อาจเป็นทางเลือกทดแทนที่สมเหตุสมผล พื้นที่เชิงพาณิชย์ก็ขยายตัวขึ้นมากในด้านนี้เช่นกัน ทั้ง vendor ใหม่และเก่าต่างสร้างหรือปรับโซลูชันที่มีอยู่ใหม่ เพื่อเจาะกลุ่มผู้ใช้ microservice
จำ ไว้ด้วยว่าผมมีข้อกังวลเรื่อง cardinality ต่ำเทียบกับสูง ระบบที่ถูกสร้างมาเพื่อจัดการข้อมูล cardinality ต่ำ จะยากมากที่จะ retrofit ให้รองรับการจัดเก็บและประมวลผล cardinality สูง ถ้าคุณกำลังมองหาระบบที่สามารถจัดเก็บและจัดการข้อมูล cardinality สูงได้ ซึ่งเปิดโอกาสให้สังเกต (และตั้งคำถามกับ) พฤติกรรมของระบบคุณได้ซับซ้อนมากขึ้น ผมขอแนะนำอย่างยิ่งให้ลองดู Honeycomb หรือ Lightstep แม้เครื่องมือเหล่านี้มักถูกมองว่าเป็นโซลูชันสำหรับ distributed tracing (ซึ่งเราจะสำรวจเพิ่มเติมในภายหลัง) พวกมันก็มีความสามารถสูงในการจัดเก็บ กรอง และ query ข้อมูล cardinality สูงเช่นกัน
Monitoring and Observability Systems Are Production Systems (ระบบ Monitoring และ Observability ก็คือ Production System)
ด้วย ชุดเครื่องมือที่กำลังเติบโตขึ้นเพื่อช่วยเราจัดการสถาปัตยกรรม microservice ของเรา เราต้องจำไว้ว่าเครื่องมือเหล่านี้เองก็คือ production system เช่นกัน Log aggregation platform, distributed tracing tool, alerting system—ทั้งหมดล้วนเป็นแอปพลิเคชัน mission-critical ที่สำคัญพอ ๆ กับซอฟต์แวร์ของเราเอง ถ้าไม่มากกว่าด้วยซ้ำ ความละเอียดรอบคอบระดับเดียวกันนี้ต้องถูกนำมาใช้ในการดูแลเครื่องมือ production monitoring ของเรา เช่นเดียวกับที่เราใช้กับซอฟต์แวร์ที่เราเขียนและดูแล
เราควรตระหนักด้วยว่าเครื่องมือเหล่านี้อาจกลายเป็นช่องทางโจมตีจากฝ่ายภายนอกได้ ณ เวลาที่เขียนหนังสือเล่มนี้ รัฐบาลสหรัฐฯ และองค์กรอื่น ๆ ทั่วโลกกำลังรับมือกับการค้นพบการถูกเจาะระบบ (breach) ใน network management software จาก SolarWinds แม้ธรรมชาติที่แท้จริงของการเจาะระบบนี้ยังอยู่ระหว่างการสืบสวน แต่เชื่อกันว่านี่คือสิ่งที่เรียกว่า supply chain attack เมื่อถูกติดตั้งบน site ของลูกค้าแล้ว (และ SolarWinds ถูกใช้โดย 425 บริษัทใน US Fortune 500) ซอฟต์แวร์นี้ทำให้ฝ่ายที่ประสงค์ร้ายสามารถเข้าถึง network ของลูกค้าจากภายนอกได้ รวมถึง network ของ US Treasury ด้วย
Distributed Tracing
จนถึงตอนนี้ ผมพูดถึงการเก็บข้อมูลแยกกันเป็นหลัก ใช่ เรากำลัง aggregate ข้อมูลนั้น แต่การเข้าใจบริบทที่กว้างขึ้นซึ่งข้อมูลนี้ถูกจับมาก็สำคัญเช่นกัน โดยพื้นฐานแล้ว สถาปัตยกรรม microservice คือชุดของ process ที่ทำงานร่วมกันเพื่อทำงานบางอย่าง—เราสำรวจวิธีต่าง ๆ ที่เราสามารถประสาน (coordinate) กิจกรรมเหล่านี้ไปแล้วใน Chapter 6 ดังนั้นมันจึงสมเหตุสมผล เมื่อเราอยากเข้าใจว่าระบบของเรากำลังทำงานอย่างไรจริง ๆ ใน production environment ที่เราจะสามารถเห็นความสัมพันธ์ระหว่าง microservice ของเราได้ นี่ช่วยให้เราเข้าใจได้ดีขึ้นว่าระบบของเรากำลังทำงานอย่างไร ประเมินผลกระทบของปัญหา หรือเก่งขึ้นในการหาว่าจริง ๆ แล้วอะไรที่ไม่ได้ทำงานตามที่เราคาดหวัง
เมื่อระบบของเราซับซ้อนมากขึ้น มันจึงสำคัญขึ้นที่จะมีวิธีเห็น trace เหล่านี้ผ่านระบบของเรา เราต้องสามารถดึงข้อมูลที่กระจัดกระจายนี้มารวมกัน เพื่อให้เราเห็นภาพรวมของชุดการเรียกที่ correlate กัน อย่างที่เราเห็นไปแล้ว การทำอะไรง่าย ๆ อย่างการใส่ correlation ID ลงใน logfile ของเราเป็นจุดเริ่มต้นที่ดี แต่มันเป็นโซลูชันที่ค่อนข้างไม่ซับซ้อนนัก โดยเฉพาะเมื่อในที่สุดเราจะต้องสร้าง tooling แบบ custom ของเราเองเพื่อช่วย visualize, slice และ dice ข้อมูลนี้ นี่คือจุดที่ distributed tracing เข้ามา
How it works (มันทำงานอย่างไร)
แม้ รายละเอียด implementation ที่แท้จริงจะแตกต่างกัน แต่โดยกว้าง ๆ แล้ว เครื่องมือ distributed tracing ทั้งหมดทำงานในลักษณะคล้ายกัน กิจกรรมในเครื่องภายใน thread หนึ่ง ๆ จะถูกจับไว้ใน span span แต่ละอันจะถูก correlate กันโดยใช้ unique identifier บางอย่าง จากนั้น span เหล่านี้จะถูกส่งไปยัง central collector ซึ่งสามารถสร้าง span ที่เกี่ยวข้องกันเหล่านี้ให้เป็น trace เดียวได้ ใน Figure 10-7 เราเห็นภาพจาก Honeycomb ที่แสดง trace ข้ามสถาปัตยกรรม microservice
Figure 10-7. Distributed trace ที่แสดงใน Honeycomb ซึ่งให้คุณระบุได้ว่าเวลาถูกใช้ไปที่ไหน สำหรับ operation ที่อาจข้าม microservice หลายตัว
Span เหล่านี้ให้คุณเก็บข้อมูลได้หลากหลาย ข้อมูลที่คุณเก็บได้จริงจะขึ้นอยู่กับ protocol ที่คุณใช้ แต่ในกรณีของ OpenTracing API แต่ละ span จะมี start และ end time, ชุดของ log ที่เกี่ยวข้องกับ span และชุดของ key-value pair ที่กำหนดเองได้ เพื่อช่วยในการ query ในภายหลัง (สิ่งเหล่านี้อาจถูกใช้เพื่อส่งข้อมูลอย่าง customer ID, order ID, hostname, build number และอื่น ๆ)
การเก็บข้อมูลให้เพียงพอเพื่อ trace การเรียกในระบบของเรา สามารถส่งผลกระทบโดยตรงต่อตัวระบบเองได้ นี่ส่งผลให้เกิดความจำเป็นของ sampling รูปแบบหนึ่ง ซึ่งข้อมูลบางส่วนจะถูกตัดออกจากการเก็บ trace ของเราโดยตั้งใจ เพื่อให้แน่ใจว่าระบบยังคงทำงานได้ ความท้าทายคือการทำให้แน่ใจว่าข้อมูลที่ถูกทิ้งไปนั้นถูกต้อง และเรายังคงเก็บ sample เพียงพอที่จะ extrapolate การสังเกตได้อย่างถูกต้อง
กลยุทธ์ sampling อาจเรียบง่ายมาก ระบบ Dapper ของ Google ซึ่งเป็นแรงบันดาลใจให้เครื่องมือ distributed tracing หลายตัวที่ตามมาภายหลัง ใช้ random sampling ที่ aggressive มาก บาง percentage ของการเรียกถูก sample และก็แค่นั้น Jaeger ตัวอย่างเช่น จะจับแค่ 1 ใน 1,000 การเรียกในการตั้งค่าเริ่มต้นของมัน แนวคิดตรงนี้คือการจับข้อมูลให้เพียงพอที่จะเข้าใจว่าระบบของเรากำลังทำอะไรอยู่ แต่ไม่จับข้อมูลมากเกินไปจนตัวระบบเองรับไม่ไหว เครื่องมืออย่าง Honeycomb และ Lightstep สามารถให้ dynamic sampling ที่ nuance กว่านี้ได้ มากกว่า random-based sampling ง่าย ๆ แบบนี้ ตัวอย่างของ dynamic sampling อาจเป็นกรณีที่คุณอยากได้ sample มากขึ้นสำหรับ event บางประเภท—ตัวอย่างเช่น คุณอาจอยาก sample อะไรก็ตามที่สร้าง error แต่พอใจกับการ sample แค่ 1 ใน 100 operation ที่สำเร็จ ถ้ามันคล้าย ๆ กันหมด
Implementing distributing tracing (การ Implement Distributed Tracing)
การทำให้ distributed tracing ใช้งานได้กับระบบของคุณ ต้องการหลายสิ่ง อย่างแรก คุณต้องจับข้อมูล span ภายใน microservice ของคุณ ถ้าคุณใช้ standard API อย่าง OpenTracing หรือ OpenTelemetry API ที่ใหม่กว่า คุณอาจพบว่า third-party library และ framework บางตัวมาพร้อมกับการรองรับ API เหล่านี้ในตัว และจะส่งข้อมูลที่มีประโยชน์ให้อยู่แล้ว (ตัวอย่างเช่น การจับข้อมูลเกี่ยวกับ HTTP call โดยอัตโนมัติ) แต่ถึงแม้พวกมันจะทำได้ คุณก็มักจะยังอยาก instrument โค้ดของคุณเอง เพื่อให้ข้อมูลที่มีประโยชน์เกี่ยวกับสิ่งที่ microservice ของคุณกำลังทำอยู่ ณ จุดใดจุดหนึ่งของเวลา
ต่อไป คุณจะต้องมีวิธีบางอย่างในการส่งข้อมูล span นี้ไปยัง collector ของคุณ—มันอาจเป็นการที่คุณส่งข้อมูลนี้โดยตรงจาก microservice instance ของคุณไปยัง central collector แต่โดยทั่วไปแล้วจะพบได้บ่อยกว่าที่จะใช้ local forwarding agent ดังนั้นเช่นเดียวกับ log aggregation คุณรัน agent ในเครื่อง local ต่อ microservice instance ของคุณ ซึ่งจะส่งข้อมูล span เป็นระยะไปยัง central collector การใช้ local agent โดยทั่วไปจะรองรับความสามารถที่ซับซ้อนขึ้นได้ เช่นการเปลี่ยน sampling หรือเพิ่ม tag เพิ่มเติม และยังสามารถ buffer ข้อมูลที่ถูกส่งได้อย่างมีประสิทธิภาพมากขึ้นด้วย
สุดท้าย แน่นอนว่าคุณต้องมี collector ที่สามารถรับข้อมูลนี้และทำความเข้าใจมันทั้งหมดได้
ในพื้นที่ open source Jaeger ได้กลายมาเป็นตัวเลือกยอดนิยมสำหรับ distributed tracing สำหรับ commercial tooling ผมจะเริ่มด้วยการดู Lightstep และ Honeycomb ที่กล่าวถึงไปแล้ว แต่ผมขอกระตุ้นให้คุณเลือกอะไรบางอย่างที่มุ่งมั่นสนับสนุน OpenTelemetry API OpenTelemetry เป็น open API specification ที่ทำให้ง่ายขึ้นมากที่โค้ดอย่าง database driver หรือ web framework จะมาพร้อมกับการรองรับ tracing ในตัวตั้งแต่แรก และมันยังทำให้ portability ข้าม vendor ต่าง ๆ บนฝั่ง collection ง่ายขึ้นด้วย โดยอิงจากงานที่ทำไปแล้วจาก OpenTracing และ OpenConsensus API ก่อนหน้า ตอนนี้ API นี้ได้รับการสนับสนุนอย่างกว้างขวางจากอุตสาหกรรมแล้ว
Are We Doing OK? (เรากำลังทำได้ดีหรือเปล่า?)
เรา พูดถึงสิ่งที่คุณอาจทำในฐานะ operator ของระบบไปมากแล้ว—mindset ที่คุณต้องมี ข้อมูลที่คุณอาจต้องรวบรวม แต่คุณจะรู้ได้อย่างไรว่าคุณกำลังทำมากเกินไป—หรือน้อยเกินไป? คุณจะรู้ได้อย่างไรว่าคุณกำลังทำได้ดีพอ หรือระบบของคุณกำลังทำงานได้ดีพอ?
แนวคิดแบบ binary ที่ระบบเป็น "up" หรือ "down" เริ่มมีความหมายน้อยลงเรื่อย ๆ เมื่อระบบซับซ้อนขึ้น กับระบบ monolithic แบบ single-process มันง่ายกว่าที่จะมอง system health เป็นเรื่องขาวดำ แต่แล้วระบบ distributed ล่ะ? ถ้า instance หนึ่งของ microservice ไม่สามารถเข้าถึงได้ นั่นเป็นปัญหาไหม? microservice นั้น "healthy" ไหม ถ้ามันเข้าถึงได้? แล้วสถานการณ์ที่ microservice Returns ของเรายังใช้งานได้ แต่ครึ่งหนึ่งของ functionality ที่มันมอบให้ต้องพึ่งพา microservice Inventory ที่เป็น downstream ซึ่งกำลังประสบปัญหาอยู่ล่ะ? นั่นหมายความว่าเราควรถือว่า microservice Returns healthy หรือไม่ healthy?
เมื่อสิ่งต่าง ๆ ซับซ้อนขึ้น มันจึงสำคัญขึ้นเรื่อย ๆ ที่จะถอยกลับมาหนึ่งก้าวและคิดจากมุมมองที่ต่างออกไป ลองนึกถึงรังผึ้งดู คุณอาจมองผึ้งตัวหนึ่งแล้วสรุปว่ามันไม่มีความสุข บางทีมันอาจสูญเสียปีกข้างหนึ่งไปและบินไม่ได้อีกต่อไป นี่แน่นอนว่าเป็นปัญหาสำหรับผึ้งตัวนั้น แต่คุณจะขยายจากตรงนั้นไปเป็นข้อสังเกตเกี่ยวกับสุขภาพของทั้งรังได้ไหม? ไม่ได้—คุณจะต้องมองสุขภาพของรังในแบบองค์รวมมากกว่านี้ ผึ้งตัวหนึ่งป่วยไม่ได้แปลว่าทั้งรังป่วย
เราสามารถพยายามหาว่า service หนึ่ง healthy หรือไม่ ด้วยการตัดสินใจ เช่น ว่า CPU level ที่ดีคือเท่าไหร่ หรือ response time ที่ยอมรับได้คืออะไร ถ้าระบบ monitoring ของเราตรวจพบว่าค่าจริงตกอยู่นอกระดับที่ปลอดภัยนี้ เราก็สามารถ trigger alert ได้ อย่างไรก็ตาม ในหลาย ๆ ทาง ค่าเหล่านี้ห่างออกไปหนึ่งขั้นจากสิ่งที่เราต้องการติดตามจริง ๆ นั่นคือ ระบบกำลังทำงานอยู่หรือเปล่า? ยิ่ง interaction ระหว่าง service ซับซ้อนมากเท่าไหร่ เราก็ยิ่งห่างไกลจากการตอบคำถามนั้นได้จริง ๆ ด้วยการดู metric เดียวแบบแยกส่วนมากเท่านั้น
ดังนั้นเราสามารถรวบรวมข้อมูลได้มากมาย แต่ด้วยตัวมันเองแล้วมันไม่ได้ช่วยตอบคำถามว่าระบบกำลังทำงานอย่างถูกต้องหรือไม่ สำหรับเรื่องนั้น เราต้องเริ่มคิดมากขึ้นในแง่ของการนิยามว่า acceptable behavior หน้าตาเป็นอย่างไร งานจำนวนมากได้ถูกทำในพื้นที่ของ site reliability engineering (SRE) ซึ่งโฟกัสอยู่ที่ว่าเราจะทำให้ระบบของเรา reliable ได้อย่างไร ในขณะที่ยังอนุญาตให้มีการเปลี่ยนแปลงได้ จากพื้นที่นี้ เรามี concept ที่มีประโยชน์สองสามอย่างให้สำรวจ
เตรียมตัวไว้—เรากำลังจะเข้าสู่เมือง acronym แล้ว
Service-level agreement (ข้อตกลงระดับ Service)
Service-level agreement (SLA) คือข้อตกลงที่บรรลุระหว่างคนที่สร้างระบบและคนที่ใช้ระบบ มันอธิบายไม่เพียงแค่ว่าผู้ใช้คาดหวังอะไรได้บ้าง แต่ยังอธิบายด้วยว่าจะเกิดอะไรขึ้นถ้าระบบไม่ถึงระดับ acceptable behavior นี้ SLA มักจะอยู่ในระดับ "bare minimum" มาก ถึงขนาดที่ว่าถ้าระบบเพิ่งจะบรรลุเป้าหมายของมันพอดี ผู้ใช้ปลายทางก็ยังคงไม่พอใจอยู่ดี ยกตัวอย่างเช่น AWS มี SLA สำหรับ compute service ของมัน มันระบุชัดเจนว่าไม่มีการรับประกัน uptime ที่มีผลจริงสำหรับ EC2 instance เดียว (managed virtual machine 8 ) AWS ระบุว่ามันพยายามอย่างดีที่สุดเพื่อให้ได้ 90% uptime สำหรับ instance หนึ่ง ๆ แต่ถ้าทำไม่ได้ มันก็แค่ไม่คิดเงินคุณสำหรับชั่วโมงที่ instance นั้นใช้งานไม่ได้ ทีนี้ ถ้า EC2 instance ของคุณล้มเหลวไม่ถึง 90% availability ในชั่วโมงหนึ่ง ๆ ตลอดเวลา จนทำให้ระบบไม่เสถียรอย่างมีนัยสำคัญ คุณอาจไม่ถูกคิดเงิน แต่คุณก็คงไม่ได้มีความสุขนัก จากประสบการณ์ของผม AWS ในทางปฏิบัติทำได้เกินกว่าที่ SLA ระบุไว้มาก อย่างที่มักเป็นกรณีกับ SLA ทั่วไป
Service-level objectives (วัตถุประสงค์ระดับ Service)
การ mapping SLA ลงมาสู่ทีมนั้นเป็นปัญหา โดยเฉพาะถ้า SLA ค่อนข้างกว้างและ cross-cutting ในระดับทีม เราพูดถึง service-level objectives (SLO) แทน SLO นิยามสิ่งที่ทีมรับปากที่จะมอบให้ การบรรลุ SLO ของทุกทีมทั่วทั้งองค์กร จะทำให้ (และมักจะเกินกว่า) ข้อกำหนดของ SLA ขององค์กรได้รับการตอบสนอง ตัวอย่าง SLO อาจรวมถึงสิ่งอย่าง uptime ที่คาดหวัง หรือ response time ที่ยอมรับได้สำหรับ operation หนึ่ง ๆ
มันง่ายเกินไปที่จะคิดว่า SLO เป็นแค่สิ่งที่ทีมต้องทำเพื่อให้องค์กรบรรลุ SLA ของมัน ใช่ ถ้าทั้งองค์กรบรรลุ SLO ทั้งหมดของมัน เราจะสันนิษฐานได้ว่า SLA ทั้งหมดก็ถูกบรรลุด้วยเช่นกัน แต่ SLO สามารถพูดถึงเป้าหมายอื่นที่ไม่ได้ระบุไว้ใน SLA ได้—หรือมันอาจเป็น aspirational มันอาจ inward facing (พยายามทำการเปลี่ยนแปลงภายในบางอย่าง) SLO มักสะท้อนสิ่งที่ตัวทีมเองอยากบรรลุ ซึ่งอาจไม่มีความเกี่ยวข้องกับ SLA เลยก็ได้
Service-level indicators (ตัวชี้วัดระดับ Service)
เพื่อ ตัดสินว่าเรากำลังบรรลุ SLO ของเราหรือไม่ เราต้องรวบรวมข้อมูลจริง นี่คือสิ่งที่ service-level indicators (SLI) ของเราคือ SLI เป็นตัววัดของบางสิ่งที่ซอฟต์แวร์ของเราทำ มันอาจเป็น response time จาก process หนึ่ง การลงทะเบียนของลูกค้า error ที่ถูกแจ้งให้ลูกค้าเห็น หรือ order ที่ถูกวางไว้ก็ได้ เราต้องรวบรวมและแสดง SLI เหล่านี้เพื่อให้แน่ใจว่าเรากำลังบรรลุ SLO ของเรา
Error budgets (Error Budget)
เมื่อ เราลองสิ่งใหม่ ๆ เราก็ฉีด instability ที่อาจเกิดขึ้นเข้าไปในระบบของเรามากขึ้น ดังนั้น ความต้องการที่จะรักษา (หรือปรับปรุง) system stability อาจทำให้การเปลี่ยนแปลงถูกกีดกันได้ Error budget เป็นความพยายามที่จะหลีกเลี่ยงปัญหานี้ โดยทำให้ชัดเจนว่า error เท่าไหร่ถึงจะยอมรับได้ในระบบ
ถ้าคุณตัดสินใจเรื่อง SLO ไปแล้ว การหา error budget ของคุณก็ควรจะชัดเจนพอสมควร ตัวอย่างเช่น คุณอาจบอกว่า microservice ของคุณต้อง available 99.9% ของเวลาต่อไตรมาส แบบ 24/7 นั่นหมายความว่าคุณได้รับอนุญาตให้ down ได้จริง ๆ 2 ชั่วโมง 11 นาทีต่อไตรมาส ในแง่ของ SLO นั้น นี่คือ error budget ของคุณ
Error budget ช่วยให้คุณเข้าใจได้ชัดเจนว่าคุณกำลังบรรลุ (หรือไม่บรรลุ) SLO ได้ดีแค่ไหน ทำให้คุณตัดสินใจได้ดีขึ้นว่าจะรับความเสี่ยงอะไรบ้าง ถ้าคุณยัง under error budget ของไตรมาสอยู่มาก บางทีคุณอาจโอเคที่จะ rollout microservice ที่เขียนด้วยภาษาโปรแกรมใหม่ ถ้าคุณเกิน error budget ไปแล้ว บางทีคุณอาจเลื่อน rollout นั้นออกไป แล้วโฟกัสเวลาของทีมมากขึ้นกับการปรับปรุงความน่าเชื่อถือของระบบของคุณแทน
Error budget เป็นเรื่องของการให้พื้นที่หายใจแก่ทีมในการลองสิ่งใหม่ ๆ พอ ๆ กับเรื่องอื่นเลย
Alerting
ใน บางโอกาส (หวังว่าจะน้อยครั้ง แต่คงมากกว่าที่เราอยากให้เป็น) จะมีบางอย่างเกิดขึ้นในระบบของเราที่ต้องการให้ human operator ได้รับแจ้งเพื่อดำเนินการ Microservice หนึ่งอาจใช้งานไม่ได้อย่างไม่คาดคิด เราอาจเห็น error มากกว่าที่เราคาดไว้ หรือบางทีทั้งระบบอาจใช้งานไม่ได้เลยสำหรับผู้ใช้ของเรา ในสถานการณ์แบบนี้ เราต้องการให้คนรับรู้ว่าเกิดอะไรขึ้น เพื่อที่พวกเขาจะได้พยายามแก้ไขมัน
ปัญหาคือด้วยสถาปัตยกรรม microservice จำนวนการเรียกที่มากขึ้น จำนวน process ที่มากขึ้น และ infrastructure เบื้องล่างที่ซับซ้อนขึ้น จะมีสิ่งต่าง ๆ ผิดพลาดอยู่บ่อยครั้ง ความท้าทายในสภาพแวดล้อม microservice คือการหาว่าปัญหาประเภทไหนที่ควรทำให้คนได้รับแจ้ง และควรบอกพวกเขาอย่างไร
Some problems are worse than others (บางปัญหาแย่กว่าปัญหาอื่น)
เมื่อมีอะไรผิดพลาด เราอยากรู้เกี่ยวกับมัน หรือเปล่า? ปัญหาทั้งหมดเหมือนกันไหม? เมื่อแหล่งของปัญหาเพิ่มขึ้น มันจะยิ่งสำคัญขึ้นที่จะสามารถจัดลำดับความสำคัญของ issue เหล่านี้ เพื่อตัดสินใจว่า human operator ควรเข้ามาเกี่ยวข้องหรือไม่ และอย่างไร บ่อยครั้งคำถามที่ใหญ่ที่สุดที่ผมพบว่าตัวเองถามเมื่อพูดถึง alerting คือ "ปัญหานี้ควรทำให้ใครสักคนถูกปลุกตอนตีสามไหม?"
ผมเคยเห็นตัวอย่างของแนวคิดนี้ตอนที่ใช้เวลาอยู่ที่ Google campus เมื่อหลายปีก่อน ในบริเวณต้อนรับของอาคารหนึ่งใน Mountain View มี rack เครื่องเก่าอยู่ ตั้งไว้เป็นเหมือนนิทรรศการ ผมสังเกตเห็นสองสามอย่าง อย่างแรก server เหล่านี้ไม่ได้อยู่ใน enclosure มันเป็นแค่ motherboard เปล่า ๆ ที่เสียบเข้าไปใน rack สิ่งที่ผมสังเกตเห็นหลักคือ hard drive ถูกติดด้วย Velcro ผมถามคนของ Google คนหนึ่งว่าทำไม "อ๋อ" เขาบอก "hard drive เสียบ่อยมาก เราไม่อยากขันน็อตมันไว้ เราแค่ดึงมันออก โยนทิ้งถังขยะ แล้วแปะ Velcro อันใหม่เข้าไป"
ระบบที่ Google สร้างขึ้นตั้งสมมติฐานว่า hard drive จะเสีย มันปรับ design ของ server เหล่านี้ให้การเปลี่ยน hard drive ทำได้ง่ายที่สุดเท่าที่จะทำได้ เนื่องจากระบบถูกสร้างมาให้ทนต่อความล้มเหลวของ hard drive แม้มันจะสำคัญที่ drive จะถูกเปลี่ยนในที่สุด แต่โอกาสที่ hard drive เสียตัวเดียวจะไม่ก่อให้เกิดปัญหาสำคัญใด ๆ ที่ผู้ใช้จะเห็นได้ ด้วย server หลายพันตัวใน data center ของ Google มันจะเป็นงานประจำวันของใครสักคนที่แค่เดินไปตาม row ของ rack แล้วเปลี่ยน hard drive ไปเรื่อย ๆ แน่นอนว่า drive failure เป็นปัญหา แต่เป็นปัญหาที่จัดการได้แบบ routine Hard drive failure ถูกมองว่าเป็นเรื่องปกติ—ไม่คุ้มที่จะโทรหาใครนอกเวลางาน แต่บางทีก็เป็นแค่สิ่งที่พวกเขาต้องได้รับแจ้งระหว่างวันทำงานปกติ
เมื่อแหล่งของปัญหาที่อาจเกิดขึ้นเพิ่มขึ้น คุณจะต้องเก่งขึ้นในการจัดลำดับความสำคัญว่าอะไรก่อให้เกิด alert ประเภทไหน ไม่งั้นคุณอาจพบว่าตัวเองกำลังดิ้นรนที่จะแยกแยะระหว่างเรื่องเล็กน้อยกับเรื่องเร่งด่วน
Alert fatigue (ความล้าจาก Alert)
บ่อยครั้ง alert ที่มากเกินไปสามารถก่อปัญหาสำคัญได้ ในปี 1979 เกิดเหตุการณ์ partial meltdown ของ reactor ที่โรงไฟฟ้านิวเคลียร์ Three Mile Island ในสหรัฐฯ การสืบสวนเหตุการณ์นี้เน้นให้เห็นข้อเท็จจริงว่า operator ของโรงงานถูก alert ที่พวกเขาเห็นรุมล้อมจนไม่สามารถระบุได้ว่าต้องดำเนินการอะไร มี alert หนึ่งที่บ่งบอกปัญหาพื้นฐานที่ต้องได้รับการแก้ไข แต่นี่ไม่ใช่สิ่งที่ชัดเจนสำหรับ operator เพราะมี alert อื่น ๆ อีกมากมายที่ดังพร้อมกัน ระหว่างการไต่สวนสาธารณะเกี่ยวกับเหตุการณ์นี้ operator คนหนึ่งชื่อ Craig Faust จำได้ว่า "ผมอยากจะโยนแผง alarm ทิ้งไปเลย มันไม่ได้ให้ข้อมูลที่มีประโยชน์อะไรกับเราเลย" รายงานเกี่ยวกับเหตุการณ์นี้สรุปว่าห้องควบคุม "ไม่เพียงพออย่างมากในการจัดการกับอุบัติเหตุ" 9
เมื่อไม่นานมานี้ เราเห็นปัญหาของ alert ที่มากเกินไปในบริบทของเหตุการณ์ชุดหนึ่งกับเครื่องบิน 737 Max ซึ่งรวมถึงอุบัติเหตุเครื่องบินตกสองครั้งแยกกันที่คร่าชีวิตคนไป 346 คนรวมกัน รายงานเบื้องต้น 10 โดย US National Transportation Safety Board (NTSB) ให้ความสนใจกับ alert ที่สับสนซึ่งถูก trigger ในสภาพการณ์จริง และถูกพิจารณาว่าเป็นปัจจัยที่มีส่วนก่อให้เกิดอุบัติเหตุ จากรายงาน:
งานวิจัยด้าน human factors พบว่า สำหรับสภาพที่ไม่ปกติ เช่นสภาพที่เกี่ยวข้องกับความล้มเหลวของระบบพร้อม alert หลายตัว ซึ่ง flight crew อาจต้องดำเนินการหลายอย่าง การให้นักบินเข้าใจว่าการดำเนินการใดต้องมาก่อนเป็นความจำเป็นที่สำคัญยิ่ง นี่เป็นความจริงโดยเฉพาะในกรณีของฟังก์ชันที่ถูก implement ข้าม airplane system หลายระบบ เพราะความล้มเหลวในระบบหนึ่ง ภายในสถาปัตยกรรมระบบที่ integrate สูง สามารถแสดง alert และ indication หลายตัวให้ flight crew เห็น เมื่อแต่ละระบบที่เชื่อมต่อกันบันทึกความล้มเหลวนั้น...ดังนั้น จึงสำคัญที่ system interaction และ flight deck interface จะถูกออกแบบมาเพื่อช่วยชี้นำนักบินไปสู่การดำเนินการที่มีความสำคัญสูงสุด
ดังนั้นตรงนี้เรากำลังพูดถึงการควบคุม reactor นิวเคลียร์และการบินเครื่องบิน ผมเดาว่าหลายคนตอนนี้คงสงสัยว่ามันเกี่ยวอะไรกับระบบที่คุณกำลังสร้าง ทีนี้ มันเป็นไปได้ (ถ้าไม่ใช่ว่าน่าจะเป็นไปได้มาก) ว่าคุณไม่ได้กำลังสร้างระบบที่ safety-critical แบบนี้ แต่มีอะไรมากมายที่เราเรียนรู้ได้จากตัวอย่างเหล่านี้ ทั้งสองกรณีเกี่ยวข้องกับระบบที่ซับซ้อนสูงและเชื่อมโยงกัน ซึ่งปัญหาในพื้นที่หนึ่งสามารถก่อปัญหาในอีกพื้นที่หนึ่งได้ และเมื่อเราสร้าง alert มากเกินไป หรือเราไม่ให้ operator สามารถจัดลำดับความสำคัญว่าควรโฟกัส alert ไหน ภัยพิบัติก็อาจตามมาได้ การล้น operator ด้วย alert สามารถก่อปัญหาที่แท้จริงได้ จากรายงานอีกครั้ง:
นอกจากนี้ งานวิจัยเกี่ยวกับการตอบสนองของนักบินต่อสถานการณ์ผิดปกติหลายอย่างพร้อมกัน รวมถึงข้อมูลจากอุบัติเหตุ บ่งชี้ว่า alert ที่แข่งขันกันหลายตัวอาจเกินกว่าทรัพยากรทางจิตใจที่มีอยู่ และทำให้ focus ความสนใจแคบลง นำไปสู่การตอบสนองที่ล่าช้าหรือจัดลำดับความสำคัญไม่เพียงพอ
ดังนั้นลองคิดสองครั้งก่อนที่จะแค่โยน alert เพิ่มขึ้นให้ operator—คุณอาจไม่ได้สิ่งที่คุณต้องการ
Alarm Versus Alert (Alarm เทียบกับ Alert)
เมื่อ มองให้กว้างขึ้นเข้าไปในหัวข้อของ alerting ผมพบงานวิจัยและ practice ที่มีประโยชน์อย่างเหลือเชื่อจากหลายบริบท ซึ่งหลายอันไม่ได้พูดถึง alerting ในระบบ IT โดยเฉพาะ คำว่า alarm มักพบเจอได้บ่อยเมื่อดูหัวข้อนี้ในงาน engineering และนอกเหนือจากนั้น ในขณะที่เรามักใช้คำว่า alert บ่อยกว่าใน IT ผมคุยกับคนสองสามคนที่เห็นความแตกต่างระหว่างสองคำนี้ แต่แปลกที่ความแตกต่างที่แต่ละคนแบ่งระหว่างสองคำนี้ดูไม่สอดคล้องกัน จากข้อเท็จจริงที่ว่าคนส่วนใหญ่ดูเหมือนจะมองคำว่า alert และ alarm แทบจะเหมือนกัน และจากข้อเท็จจริงที่ว่าเมื่อมีการแบ่งความแตกต่างระหว่างทั้งสอง มันก็ไม่สอดคล้องกัน ผมจึงตัดสินใจใช้คำว่า alert เป็นมาตรฐานสำหรับหนังสือเล่มนี้
Toward better alerting (สู่ Alerting ที่ดีขึ้น)
ดังนั้นเราอยากหลีกเลี่ยงการมี alert มากเกินไป รวมถึง alert ที่ไม่มีประโยชน์ด้วย เรามีแนวทางอะไรบ้างที่จะช่วยเราสร้าง alert ที่ดีขึ้น?
Steven Shorrock ขยายความเรื่องนี้ในบทความของเขา "Alarm Design: From Nuclear Power to WebOps" 11 ซึ่งเป็นบทความที่อ่านดีมากและเป็นจุดเริ่มต้นที่ดีสำหรับการอ่านเพิ่มเติมในพื้นที่นี้ จากบทความ:
จุดประสงค์ของ [alert] คือการชี้นำความสนใจของผู้ใช้ไปยังแง่มุมสำคัญของการดำเนินงานหรืออุปกรณ์ที่ต้องการความสนใจอย่างทันท่วงที
โดยอิง จากงานนอกเหนือวงการพัฒนาซอฟต์แวร์ เรามีชุดกฎเกณฑ์ที่มีประโยชน์จาก ของทุกที่ Engineering Equipment and Materials Users Association (EEMUA) ซึ่งได้คิดค้นคำอธิบายที่ดีเยี่ยมว่า alert ที่ดีคืออะไร เท่าที่ผมเคยเห็นมา:
Relevant
ทำให้แน่ใจว่า alert มีคุณค่า
Unique
ทำให้แน่ใจว่า alert ไม่ได้ซ้ำกับอันอื่น
Timely
เราต้องได้รับ alert เร็วพอที่จะใช้ประโยชน์จากมันได้
Prioritized
ให้ข้อมูลแก่ operator เพียงพอที่จะตัดสินใจว่า alert ควรถูกจัดการตามลำดับไหน
Understandable
ข้อมูลใน alert ต้องชัดเจนและอ่านเข้าใจได้
Diagnostic
ต้องชัดเจนว่าอะไรผิดปกติ
Advisory
ช่วยให้ operator เข้าใจว่าต้องดำเนินการอะไรบ้าง
Focusing
ดึงความสนใจไปยัง issue ที่สำคัญที่สุด
เมื่อมองย้อนกลับไปในอาชีพของผมถึงช่วงเวลาที่ผมทำงานด้าน production support มันน่าหดหู่ที่จะคิดว่า alert ที่ผมต้องจัดการนั้นทำตามกฎเหล่านี้น้อยแค่ไหน
บ่อยครั้งเหลือเกิน น่าเสียดายที่คนที่ให้ข้อมูลกับระบบ alerting ของเรา และคนที่รับ alert ของเราจริง ๆ เป็นคนละคนกัน จาก Shorrock อีกครั้ง:
การเข้าใจธรรมชาติของการจัดการ alarm และประเด็นการออกแบบที่เกี่ยวข้อง สามารถช่วยคุณ—ผู้เชี่ยวชาญในงานของคุณ—ให้เป็นผู้ใช้ที่มีข้อมูลมากขึ้น ช่วยนำไปสู่ระบบ alarm ที่ดีที่สุดในการสนับสนุนงานของคุณ
เทคนิคหนึ่งที่ช่วยลดจำนวน alert ที่แย่งชิงความสนใจของเราได้ คือการเปลี่ยนวิธีที่เราคิดว่า issue ไหนที่จำเป็นต้องนำมาสู่ความสนใจของ operator ตั้งแต่แรก มาสำรวจหัวข้อนี้กันต่อไป
Semantic Monitoring
ด้วย semantic monitoring เรากำลังนิยาม model ว่า semantics ที่ยอมรับได้ของระบบของเราคืออะไร คุณสมบัติอะไรบ้างที่ระบบต้องมี เพื่อให้เราคิดว่ามันกำลังทำงานอยู่ภายในเกณฑ์ที่ยอมรับได้? ในระดับหนึ่ง semantic monitoring ต้องการการเปลี่ยนพฤติกรรมของเรา แทนที่จะมองหาการมีอยู่ของ error เราต้องถามคำถามหนึ่งอย่างต่อเนื่อง: ระบบกำลังทำงานตามที่เราคาดหวังหรือเปล่า? ถ้ามันทำงานถูกต้อง สิ่งนี้จะช่วยให้เราจัดลำดับความสำคัญได้ดีขึ้นในการจัดการกับ error ที่เราเห็นอยู่
สิ่งต่อไปที่ต้องหาคือจะนิยาม model สำหรับระบบที่ทำงานถูกต้องได้อย่างไร คุณสามารถทำแบบ formal มาก ๆ กับแนวทางนี้ได้ (ในความหมายตรงตัว บางองค์กรใช้ formal method สำหรับสิ่งนี้) แต่การทำ value statement ง่าย ๆ ก็สามารถพาคุณไปได้ไกล ตัวอย่างเช่น ในกรณีของ MusicCorp อะไรต้องเป็นจริงเพื่อให้เราพอใจว่าระบบกำลังทำงานอย่างถูกต้อง? บางที เราอาจบอกว่า:
-
ลูกค้าใหม่สามารถลงทะเบียนเข้าร่วมได้
-
เรากำลังขายสินค้ามูลค่าอย่างน้อย $20,000 ต่อชั่วโมงในช่วง peak time ของเรา
-
เรากำลังจัดส่ง order ในอัตราที่เป็นปกติ
ถ้า statement ทั้งสามนี้พิสูจน์ได้ว่าถูกต้อง โดยกว้าง ๆ แล้วเรารู้สึกว่าระบบกำลังทำงานได้ดีพอ ย้อนกลับมาที่การพูดคุยก่อนหน้าของเราเรื่อง SLA และ SLO model ของเราสำหรับความถูกต้องเชิง semantic จะถูกคาดหวังให้เกินกว่าข้อผูกพันของเราใน SLA อย่างมาก และเราจะคาดหวังให้มี SLO ที่เป็นรูปธรรมที่ให้เรา track ตาม model นี้ได้ พูดอีกอย่างคือ การพูด statement เหล่านี้เกี่ยวกับวิธีที่เราคาดหวังให้ซอฟต์แวร์ของเราทำงาน จะช่วยเราได้มากในการระบุ SLO
หนึ่งใน challenge ที่ใหญ่ที่สุดคือการได้ข้อตกลงว่า model นี้คืออะไร อย่างที่คุณเห็น เราไม่ได้พูดถึงเรื่อง low level อย่าง "disk usage ไม่ควรเกิน 95%" เรากำลังพูด statement ระดับสูงกว่าเกี่ยวกับระบบของเรา ในฐานะ operator ของ ระบบ หรือคนที่เขียนและทดสอบ microservice คุณอาจไม่ได้อยู่ในตำแหน่งที่จะตัดสินใจว่า value statement เหล่านี้ควรเป็นอย่างไร ในองค์กรที่ product-driven delivery นี่คือจุดที่ product owner ควรเข้ามา—แต่มันอาจเป็นหน้าที่ของคุณในฐานะ operator ที่จะทำให้แน่ใจว่าการพูดคุยกับ product owner เกิดขึ้นจริง
เมื่อคุณตัดสินใจแล้วว่า model ของคุณคืออะไร ต่อไปก็คือการหาว่าพฤติกรรมปัจจุบันของระบบตรงกับ model นี้หรือไม่ โดยกว้าง ๆ เรามีสองวิธีหลักในการทำสิ่งนี้—real user monitoring และ synthetic transaction เราจะดู synthetic transaction ในอีกสักครู่ เพราะมันอยู่ภายใต้ร่มของ testing in production แต่มาดู real user monitoring กันก่อน
Real user monitoring (การ Monitor ผู้ใช้จริง)
ด้วย real user monitoring เราดูสิ่งที่กำลังเกิดขึ้นจริงใน production system ของเรา แล้วเปรียบเทียบมันกับ semantic model ของเรา ใน MusicCorp เราจะดูว่าลูกค้าลงทะเบียนกี่คน เราส่ง order ไปกี่รายการ และอื่น ๆ
ความท้าทายกับ real user monitoring คือบ่อยครั้งข้อมูลที่เราต้องการไม่พร้อมใช้งานสำหรับเราในเวลาที่เหมาะสม ลองพิจารณาความคาดหวังว่า MusicCorp ควรขายสินค้าอย่างน้อย $20,000 ต่อชั่วโมง ถ้าข้อมูลนี้ถูกล็อกไว้ในฐานข้อมูลที่ไหนสักแห่ง เราอาจไม่สามารถรวบรวมข้อมูลนี้และดำเนินการตามมันได้ นี่คือเหตุผลที่คุณอาจต้องเก่งขึ้นในการเปิดให้เข้าถึงข้อมูลที่คุณเคยมองว่าเป็น metric "ธุรกิจ" ให้กับ production tooling ของคุณ ถ้าคุณสามารถ emit CPU rate ไปยัง metric store ของคุณได้ และ metric store นี้สามารถถูกใช้เพื่อ alert เงื่อนไขนี้ได้ แล้วทำไมคุณจะบันทึกยอดขายและมูลค่าเป็นดอลลาร์ลงใน store เดียวกันนี้ไม่ได้ล่ะ?
หนึ่งในข้อเสียหลักของ real user monitoring คือมัน noisy โดยพื้นฐาน คุณได้รับข้อมูลจำนวนมาก—การคุ้ยหาข้อมูลนั้นเพื่อดูว่ามีปัญหาหรือไม่อาจเป็นเรื่องยาก มันยังคุ้มค่าที่จะตระหนักด้วยว่า real user monitoring บอกคุณว่าเกิดอะไรขึ้นไปแล้ว และผลก็คือคุณอาจไม่พบปัญหาจนกว่ามันจะเกิดขึ้นแล้ว ถ้าลูกค้าลงทะเบียนไม่สำเร็จ นั่นคือลูกค้าที่ไม่พอใจ ด้วย synthetic transaction ซึ่งเป็นอีกรูปแบบหนึ่งของ testing in production ที่เราจะดูในอีกสักครู่ เรามีโอกาสที่จะไม่เพียงลด noise ลง แต่ยังจับปัญหาได้ก่อนที่ผู้ใช้ของเราจะรับรู้มันด้วย
Testing in Production
การไม่ testing ใน prod ก็เหมือนไม่ซ้อมกับวง orchestra เต็มวง เพียงเพราะ solo ของคุณฟังดูดีตอนซ้อมที่บ้าน 12
Charity Majors
อย่าง ที่เราได้พูดถึงหลายครั้งตลอดทั้งเล่ม ตั้งแต่การพูดคุยเรื่อง concept อย่าง canary deployment ใน "Canary Release" ไปจนถึงการมองดู balancing act เกี่ยวกับ pre- และ post-production testing การทำ testing in production บางรูปแบบสามารถเป็นกิจกรรมที่มีประโยชน์อย่างเหลือเชื่อ—และปลอดภัย เราได้ดู testing in production หลายประเภทในหนังสือเล่มนี้ และยังมีรูปแบบอื่นอีกมากมาย ดังนั้นผมคิดว่ามันน่าจะมีประโยชน์ที่จะสรุปประเภทต่าง ๆ ของ testing in production ที่เราได้ดูไปแล้ว และแชร์ตัวอย่างอื่น ๆ ของ testing in production ที่ใช้กันทั่วไปด้วย มันน่าแปลกใจที่หลายคนที่กลัว concept ของ testing in production นั้น จริง ๆ แล้วกำลังทำมันอยู่โดยไม่รู้ตัวด้วยซ้ำ
รูปแบบทั้งหมดของ testing in production ล้วนเป็นรูปแบบหนึ่งของกิจกรรม "monitoring" ในแง่หนึ่ง เรากำลังทำ testing in production เหล่านี้เพื่อให้แน่ใจว่า production system ของเรากำลังทำงานตามที่เราคาดหวัง และ testing in production หลายรูปแบบสามารถมีประสิทธิภาพอย่างเหลือเชื่อในการจับปัญหาก่อนที่ผู้ใช้ของเราจะสังเกตเห็นด้วยซ้ำ
Synthetic transactions (Synthetic Transaction)
ด้วย synthetic transaction เราฉีดพฤติกรรมผู้ใช้ปลอมเข้าไปใน production system ของเรา พฤติกรรมผู้ใช้ปลอมนี้มี input ที่รู้ค่าและ output ที่คาดหวังไว้ สำหรับ MusicCorp ตัวอย่างเช่น เราสามารถสร้างลูกค้าใหม่ขึ้นมาเทียม ๆ แล้วตรวจสอบว่าลูกค้านั้นถูกสร้างสำเร็จหรือไม่ Transaction เหล่านี้จะถูกยิงเป็นประจำ ทำให้เรามีโอกาสจับปัญหาให้เร็วที่สุดเท่าที่จะทำได้
ผมทำแบบนี้ครั้งแรกย้อนไปในปี 2005 ผมเป็นส่วนหนึ่งของทีม Thoughtworks เล็ก ๆ ที่กำลังสร้างระบบให้ investment bank แห่งหนึ่ง ตลอดวันทำการซื้อขาย มี event จำนวนมากเข้ามาที่แสดงถึงการเปลี่ยนแปลงใน market งานของเราคือตอบสนองต่อการเปลี่ยนแปลงเหล่านี้และดูผลกระทบต่อ portfolio ของธนาคาร เราทำงานภายใต้ deadline ที่ค่อนข้างตึง เป้าหมายที่ยากคือทำการคำนวณทั้งหมดของเราให้เสร็จภายในไม่ถึง 10 วินาทีหลัง event มาถึง ตัวระบบเองประกอบด้วย discrete service ประมาณห้าตัว ซึ่งอย่างน้อยหนึ่งตัวรันอยู่บน computing grid ที่ ในบรรดาสิ่งอื่น ๆ กำลังเก็บเกี่ยว CPU cycle ที่ไม่ได้ใช้จาก desktop host ประมาณ 250 เครื่องใน disaster recovery center ของธนาคาร
จำนวน moving part ในระบบนี้หมายความว่ามี noise จำนวนมากถูกสร้างขึ้นจาก lower-level metric หลายตัวที่เรากำลังเก็บอยู่ เราก็ไม่ได้มีข้อดีของการ scale แบบค่อยเป็นค่อยไป หรือให้ระบบรันไปสักสองสามเดือนเพื่อเข้าใจว่า "ดี" หน้าตาเป็นอย่างไรในแง่ของ low-level metric อย่าง CPU rate หรือ response time แนวทางของเราคือการสร้าง fake event เพื่อ price ส่วนหนึ่งของ portfolio ที่ยังไม่ถูก book เข้าไปในระบบ downstream ทุก ๆ หนึ่งนาทีหรือประมาณนั้น เราใช้เครื่องมือชื่อ Nagios เพื่อรัน command-line job ที่ใส่ fake event เข้าไปใน queue ตัวใดตัวหนึ่งของเรา ระบบของเราเก็บมันขึ้นมาและรันการคำนวณต่าง ๆ เหมือน job อื่น ๆ ยกเว้นว่าผลลัพธ์จะไปปรากฏใน book "junk" ซึ่งถูกใช้สำหรับ testing เท่านั้น ถ้า repricing ไม่ถูกเห็นภายในเวลาที่กำหนด Nagios จะรายงานสิ่งนี้เป็น issue
ในทางปฏิบัติ ผมพบว่าการใช้ synthetic transaction เพื่อทำ semantic monitoring แบบนี้ เป็นตัวชี้วัดปัญหาในระบบที่ดีกว่าการ alert บน lower-level metric มาก มันไม่ได้แทนที่ความจำเป็นของรายละเอียด lower-level นะ—เรายังคงต้องการข้อมูลนั้นเมื่อเราต้องหาว่า ทำไม synthetic transaction ถึงล้มเหลว
Implementing synthetic transactions (การ Implement Synthetic Transaction)
ในอดีต การ implement synthetic transaction เป็นงานที่ค่อนข้างน่ากลัว แต่โลกได้เปลี่ยนไปแล้ว และวิธีการ implement มันก็อยู่แค่ปลายนิ้วของเรา! คุณกำลังรัน test สำหรับระบบของคุณอยู่ใช่ไหม? ถ้าไม่ ให้ไปอ่าน Chapter 9 แล้วค่อยกลับมา เสร็จแล้วใช่ไหม? ดี!
ถ้าเรามองที่ test ที่เรามีที่ทดสอบ service หนึ่ง ๆ แบบ end to end หรือแม้แต่ทั้งระบบของเราแบบ end to end เราก็มีสิ่งที่เราต้องการเป็นส่วนใหญ่แล้วเพื่อ implement semantic monitoring ระบบของเราเปิด hook ที่จำเป็นสำหรับการ launch test และตรวจสอบผลลัพธ์อยู่แล้ว แล้วทำไมไม่แค่รัน subset ของ test เหล่านี้อย่างต่อเนื่อง เพื่อเป็นวิธี monitor ระบบของเราล่ะ?
แน่นอนว่ามีบางอย่างที่เราต้องทำ อย่างแรก เราต้องระวังเรื่อง data requirement ของ test ของเรา เราอาจต้องหาวิธีให้ test ของเราปรับตัวเข้ากับ live data ที่ต่างกัน ถ้ามันเปลี่ยนไปตามเวลา หรือไม่ก็ตั้ง source ของข้อมูลอีกแบบหนึ่ง ตัวอย่างเช่น เราอาจมีชุด fake user ที่เราใช้ใน production พร้อมชุดข้อมูลที่รู้ค่าแล้ว
เช่นเดียวกัน เราต้องมั่นใจว่าเราจะไม่บังเอิญ trigger side effect ที่ไม่คาดคิด เพื่อนคนหนึ่งเคยเล่าเรื่องให้ผมฟังเกี่ยวกับบริษัท ecommerce ที่บังเอิญรัน test ของมันกับระบบสั่งซื้อ production จริง มันไม่รู้ตัวถึงความผิดพลาดนี้จนกระทั่งเครื่องซักผ้าจำนวนมากมาส่งถึงสำนักงานใหญ่
A/B testing
ด้วย A/B test คุณ deploy functionality เดียวกันสองเวอร์ชันที่ต่างกัน โดยผู้ใช้จะเห็น functionality "A" หรือ "B" อย่างใดอย่างหนึ่ง คุณจึงสามารถดูได้ว่า functionality เวอร์ชันไหนทำงานได้ดีกว่ากัน สิ่งนี้มักถูกใช้เมื่อพยายามตัดสินใจระหว่างสองแนวทางที่ต่างกันว่าควรทำบางอย่างอย่างไร—ตัวอย่างเช่น คุณอาจลอง customer registration form สองแบบที่ต่างกัน เพื่อดูว่าแบบไหนมีประสิทธิภาพมากกว่าในการดึงดูดการสมัครสมาชิก
Canary release
ผู้ใช้ ส่วนเล็ก ๆ ของฐานผู้ใช้ของคุณจะได้เห็น functionality เวอร์ชันใหม่ ถ้า functionality ใหม่นี้ทำงานได้ดี คุณสามารถเพิ่มสัดส่วนของฐานผู้ใช้ของคุณที่เห็น functionality ใหม่ได้ จนถึงจุดที่ functionality เวอร์ชันใหม่ถูกใช้โดยผู้ใช้ทั้งหมด ในทางกลับกัน ถ้า functionality ใหม่ไม่ทำงานตามที่ตั้งใจไว้ คุณก็กระทบแค่ผู้ใช้ส่วนเล็ก ๆ และสามารถ revert การเปลี่ยนแปลง หรือพยายามแก้ปัญหาที่คุณระบุได้
Parallel run
ด้วย parallel run คุณรัน implementation ที่เทียบเท่ากันสองแบบของ functionality เดียวกันควบคู่กันไป Request ของผู้ใช้ใด ๆ จะถูก route ไปยังทั้งสองเวอร์ชัน และผลลัพธ์ของมันสามารถถูกเปรียบเทียบกันได้ ดังนั้นแทนที่จะนำผู้ใช้ไปที่เวอร์ชันเก่าหรือใหม่ อย่างใน canary release เรารันทั้งสองเวอร์ชัน—แต่ผู้ใช้เห็นแค่เวอร์ชันเดียว สิ่งนี้ทำให้สามารถเปรียบเทียบสองเวอร์ชันได้อย่างเต็มที่ ซึ่งมีประโยชน์อย่างมากเมื่อเราอยากเข้าใจแง่มุมอย่าง load characteristic ของ implementation ใหม่ของ functionality สำคัญได้ดีขึ้น
Smoke tests
ใช้ หลังจากซอฟต์แวร์ถูก deploy ลง production แล้วแต่ก่อนที่จะถูก release smoke test จะถูกรันกับซอฟต์แวร์เพื่อให้แน่ใจว่ามันทำงานได้อย่างเหมาะสม test เหล่านี้มักจะ automated เต็มรูปแบบ และอาจมีตั้งแต่กิจกรรมง่าย ๆ อย่างการตรวจสอบว่า microservice หนึ่ง ๆ up และกำลังรันอยู่ ไปจนถึงการรัน synthetic transaction เต็มรูปแบบเลยก็ได้
Synthetic transactions
Interaction ของผู้ใช้ปลอมแบบเต็มรูปแบบถูกฉีดเข้าไปในระบบ มันมักใกล้เคียงกับ end-to-end test ที่คุณอาจเขียนมาก
Chaos engineering
หัวข้อ ที่เราจะพูดถึงเพิ่มเติมใน Chapter 12 chaos engineering สามารถเกี่ยวข้องกับการฉีด fault เข้าไปใน production system เพื่อให้แน่ใจว่ามันสามารถจัดการกับ issue ที่คาดไว้เหล่านี้ได้ ตัวอย่างที่รู้จักกันดีที่สุดของเทคนิคนี้คงเป็น Chaos Monkey ของ Netflix ซึ่งสามารถปิด virtual machine ใน production ได้ โดยคาดหวังว่าระบบจะแข็งแกร่งพอที่การปิดเหล่านี้จะไม่รบกวน functionality ของ end-user