สร้างระบบ Monitoring Microservices ด้วย Spring Cloud Sleuth, ELK, และ Zipkin
อย่างที่เราทุกคนรู้จักกันดี microservices เป็น architecture ที่ทำการแบ่งย่อย monolithic application ที่มีขนาดใหญ่และมีความซับซ้อน ออกเป็น application ที่มีขนาดเล็กลงและจัดการได้ง่ายยิ่งขึ้น และ application ที่มีขนาดเล็กนี้ สามารถพัฒนาด้วยภาษาใดๆก็ได้และ deploy ได้อย่างอิสระ
จะเห็นได้ว่า microservices มีความยืดหยุ่นสูง แต่ความยืดหยุ่นเหล่านี้ก็มาพร้อมกับความความซับซ้อนที่เพิ่มขึ้นมาด้วยเช่นกัน เนื่องจาก microservices เป็น distributed system โดยธรรมชาติ เพราะฉะนั้นการ debug ปัญหาที่เกิดขึ้นในระบบมีความยุ่งยากเป็นอย่างมาก เนื่องจากเราต้องทำการตามหาแหล่งที่มาของปัญหาใน transactions ที่มีการเรียกหากันระหว่าง services ต่างๆ มากมาย ไม่ว่าจะเป็น physical machines หรือ data stores แบบต่างๆ เราต้องพยายามเชื่อมแต่ละส่วนเข้าด้วยกัน เพื่อที่จะหาสาเหตุที่แท้จริงแล้วเกิดอะไรขึ้นในระบบกันแน่
ฉะนั้นสิ่งที่จำเป็นที่สุดสำหรับ microservices architecture คือ ต้องสามารถเข้าถึง call history รวมทั้งต้องมีความสามารถใน debugging ระหว่าง transactions ที่เรียกระหว่าง services ต่างๆได้อย่างง่าย โชคดีที่ทุกวันนี้มี tools ที่เป็นประโยชน์อยู่จำนวนหนึ่งที่นิยมนำมาใช้ในการทำ debugging system เมื่อสร้าง microservices ด้วย Spring Boot และ Spring Cloud วันนี้เราจะมาทำความรู้จักกับ tools เหล่านั้น พร้อมกับทดลองใช้งาน tools เหล่านั้นด้วยการสร้าง microservices ขึ้นมาด้วยตัวเราเองครับ
ทำความรู้จักกับ Tools แบบคร่าวๆกันก่อน
- SPRING CLOUD SLEUTH
Spring Cloud Sleuth เป็น library ที่ทำการดักจับ requests ที่เข้ามาใน service และทำการสร้าง trace id ให้กับ request นั้นๆถ้า request ที่เข้ามาในระบบยังมี trace id และยังสามารถทำการส่ง trace data ที่มันสร้างขึ้นตอนที่ทำ request ระหว่าง services ต่างๆ ไปยัง Zipkin server ด้วย โดย trace id จะสร้างโดยทำการ random unique number หรือ string พร้อมกับทำการ inject ให้กับ request นั้นๆ เพื่อที่จะทำการส่งต่อระหว่าง service หนึ่งไปยังอีก service เรื่อยๆตลอดทั้ง microservices
- ZIPKIN
Zipkin เป็น distributed tracing system ที่สามารถ visualizing traces ต่างๆที่เกิดภายใน service และระหว่าง services ด้วย ซึ่งจะเป็นตัวช่วยในการแก้ปัญหา latency problems ที่เกิดขึ้นใน microservices architecture โดย Zipkin สามารถจัดการทั้งเก็บและ lookup data ได้ด้วย
- ELK Stack (Logstash + Eslastic Search + Kibana)
ELK Stack เป็น end-to-end logging ที่นิยมใช้กับ microservices architecture โดย ELK Stack มีต้นกำเนิดมาจาก open source project ที่ชื่อว่า Elasticsearch ที่เรารู้จักในดีในนามของ database engines จากนั้นก็มี Kibana ตามมาในภายหลัง ซึ่งทำหน้าที่ในการดู data ใน Elasticsearch หลังจากนั้นก็มี tool ที่ทำหน้าที่ insert data ลง Elasticsearch ชื่อว่า Logstash ตามมาในภายหลัง เมื่อ 3 อย่างทำงานด้วยกัน ก็เลยเกิด tools ที่เราสามารถ searching, analyzing, และ visualizing log data ได้แบบ real-time กันเลยทีเดียวครับ โดยที่ Logstash ทำหน้าที่ในการเก็บ logs data ส่งต่อ logs data และเก็บ logs ลงใน Elasticsearch และใช้ Kibana ซึ่งเป็น ui ที่เราใช้ในการดูและ search logs ที่ Logstash ได้ทำ indexed ไว้
Microservices
บทความนี้เราจะใช้ microservice แบบง่ายๆที่เราเคยสร้างมาแล้วในบทความ สร้าง API Gateway ด้วย Netflix Zuul และ Spring Cloud โดย code base สามารถหาได้จาก github ด้านล่าง และอย่าลืม checkout เป็น branch origin/zuul-service ด้วยนะครับ
หลังจากที่เรา clone project มาแล้ว เราจะทำการ integrate Spring Cloud Sleuth, Zipkin และ ELK Stack ให้กับ microservice ของเราครับ
Spring Cloud Sleuth และ Correlation ID
อย่างที่ทราบกันไปแล้วว่า Spring Cloud Sleuth เป็น library ที่ทำการดัก requests และทำการสร้าง trace id โดยจะทำการ random เป็น unique number หรือ string พร้อมทำการ inject ไปกับ request โดยจะส่งต่อกันไปเรื่อยๆตลอดทั้ง microservices ด้วยการใช้งาน Spring Cloud Sleuth เราจะสามารถ
- สร้างและ inject correlation ID ให้กับ service calls ถ้า correlation ID ยังไม่ได้สร้างขึ้น
- จัดการการส่งต่อ correlation ID ไปยัง service calls ขาออกโดยอัตโนมัติ
- เพิ่ม correlation information ให้กับ Spring MDC logging นั้นหมายความว่า correlation ID จะทำการ log โดยอัตโนมัติเมื่อเราใช้ Spring Boots default log ไม่ว่าจะเป็น SL4J หรือ Logback
- อันนี้เป็น Optionally โดยเราสามารถส่ง tracing information ที่เกิดขึ้นไปยัง Zipkin server หรือ distributed tracing platform อื่นๆได้ด้วย
เอาหละครับ ทำการเพิ่ม Spring Cloud Sleuth dependency ใน pom.xml file ของ Pricing-Service, Product-Service และ Zuul-service กันเลย
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
เพียงแค่เราทำการเพิ่ม Spring Cloud Sleuth dependency เท่านั้น services ของเราก็จะสามารถตรวจสอบทุกๆ request ที่เข้ามาว่ามี tracing information อยู่รึป่าว ถ้ายังไม่มีจะทำการสร้าง tracing information และส่งต่อไปยัง service ต่อๆไป นอกจากนั้น Spring Cloud Sleuth ยังทำการ inject tracing information ให้กับ Spring MDC นั้นหมายความว่า ทุกๆ log statement ที่เกิดขึ้นจะถูกเพิ่ม tracing information เข้าไปโดยอัตโนมัติ และ Spring Cloud Sleuth ยังทำการ inject tracing information ไปกับ HTTP call ขาออกแบบอัตโนมัติด้วยเช่นกัน
ถึงตรงนี้ถ้าทุกๆอย่างทำการ set up อย่างถูกต้อง ถ้าเราทำการเรียก http://localhost:9000/products/price?sku=bnk48 ซึ่งเป็นการทดลองเรียก pricing service จะได้ out put หน้าตาแบบนี้
จะเห็นว่า Spring Cloud Sleuth ได้ทำการเพิ่ม information เพิ่มเข้ามา 4 อย่าง ในแต่ละ log ดังนี้
— Application name ของ service โดย default แล้ว Spring Cloud Sleuth จะใช้ชื่อของ application ที่เรา config ไว้ที่ spring.application.name (application.properties/application.yml)
— Trace ID หรือหลายคนรู้จักกันในนาม correlation ID ซึ่งจะเป็น unique number หรือ string
— Span ID เป็น unique ID ที่จะใช้ในแต่ละ service นั้นๆ โดยใน transaction แต่ละ service จะมี span ID เป็นของตัวเอง
— ในลำดับสุดท้ายเป็น boolean flag ที่จะบอกว่าได้ทำการส่ง trace data ไป Zipkin หรือไม่
ถึงตรงนี้ เราแค่ทำการดู logging data ใน service เดียวเท่านั้น ทีนี้ลองเรียก product-service ผ่าน zuul service ดังนี้
http://localhost:9999/products/search?sku=sku-bnk48 โดย zuul-service ซึ่งทำหน้าที่เป็น api gateway จะทำการ route ไปที่ product-service และ product-service ก็จะเรียกไปที่ pricing-service ด้วยเช่นกัน โดยเราจะได้ out put ดังนี้
ทั้ง product-service และ pricing-service จะได้ trace ID ที่เหมือนกัน คือ 1be252986f2d8f12 แต่จะได้ span id ที่แตกต่างกัน product-service มี span ID คือ 56b154f1e35864e9 ส่วน pricing-service จะมี span ID คือ d5a29f5075836e2a
แค่เราทำการเพิ่ม Spring Cloud Sleuth dependency ใน pom.xml เท่านั้น เราก็สามารถสร้าง correlation ID โดยที่เราไม่ต้องเขียน code แม้แต่นิดเดียวเลยด้วยซ้ำ
Centralized Log Aggregation & Visualization using ELK and Spring Cloud Sleuth
ใน microservices นั้น logging data เป็นเครื่องมือที่สำคัญมากในการ debug ปัญหาต่างๆที่เกิดขึ้นใน microservices เนื่องจากโดยธรรมชาติแล้ว microservices architecture สำหรับ service หนึ่งประเภท จะมี service instances เป็นจำนวนมาก เช่น pricing-service ในระบบ microservices ขนาดใหญ่ อาจจะได้เป็นหลักสิบหลักร้อย instances เพราะฉะนั้น การพยายามที่จะรวบรวม log data จากหลายๆ services เพื่อที่จะแก้ปัญหาต่างๆที่เกิดขึ้นในระบบเป็นเรื่องที่ยุ่งยาก ซับซ้อนและเสียเวลาเป็นจำนวนมากที่ใช้ในการ investigate และแก้ไขปัญหา
ดังนั้นการแก้ไขปัญหาที่ได้รับความนิยมและเป็นแนวทางที่ดีด้วยคือใช้วิธีการ stream logs data แบบ real-time จากทุกๆ service instances ไปที่ centralized aggregation ซึ่งเป็นส่วนที่ logs data สามารถทำ indexed และค้นหาได้อย่างง่าย รูปภาพด้านล่างเป็นการแสดง concept ของ logging architecture โดยแต่ละ services จะเป็นตัว produce log data และจะ log aggregation tool ตัวหนึ่ง ในที่นี้คือ Logstash ทำหน้าที่กรองและส่งต่อไปที่ central data store คือ Elasticsearch ซึ่งจะทำหน้าที่ indexed และเก็บข้อมูลในรูปแบบที่สามารถ search ได้ เพื่อที่จะให้ development หรือ operations teams มา query log เหล่านั้นได้
เราจะทำการ implement ตัว unified logging architecture ด้านบนด้วย Spring Cloud Sleuth และ ELK Stack กัน โดยการ set up ตัว ELK Stack นั้น จะใช้งาน docker ในการ set up ซึ่งก็มี docker image หลายตัวที่เราสามารถใช้ในการ set up ตัว ELK Stack โดยในบทความนี้ ผมจะใช้ sebp/elk Docker image มาช่วยในการ setup ELK Stack สำหรับใครที่อยากศึกษาเพิ่มเติมเกี่ยวกับ sebp/elk สามารถดูเพิ่มได้ได้จาก document สำหรับการ set up ตัว ELK Stack เราต้องทำตาม step ดังนี้
- สร้าง Dockerfile และ Logstash configuration ดังนี้
สร้าง Dockerfile
FROM sebp/elk
ADD ./02-beats-input.conf /etc/logstash/conf.d/02-beats-input.conf
ADD ./30-output.conf /etc/logstash/conf.d/30-output.conf
สร้าง 30-output.conf ซึ่งเป็น Logstash filter และ output configuration
filter {
json {
source => "message"
}
}
output {
elasticsearch {
hosts => ["localhost"]
manage_template => false
index => "elk-%{+YYYY.MM.dd}"
}
}
สร้าง 02-beats-input.conf ซึ่งเป็น Logstash input configuration file
input {
tcp {
port => 5044
}
}
จากนั้นทำการ build ELK docker image ด้วยคำสั่ง
$ docker build --tag elk .
ทำการ run elk docker container จาก elk image ด้วยคำสั่ง
$ docker run -p 5601:5601 -p 9200:9200 -p 5044:5044 --name elk elk
จากคำสั่งด้านบน เราจะได้ ELK stack run อยู่บน port ต่างๆ ดังนี้
Kibana - http://localhost:5601/app/kibana
Elastic Search - http://localhost:9200
Logstash - localhost:5044
Configure Logback
ในการส่ง application logs ไปที่ ELK Stack นั้น เราจะใช้ LogstashTcpSocketAppender โดยสร้าง Logback configuration file เริ่มทำการสร้าง file ชื่อ logback.xml ที่ src/main/resources/logback.xml และ config service name ของแต่ละ service สำหรับ Elasticsearch index และเพื่อที่จะส่ง log data ให้กับ Logstash นั้น เราต้องทำการเพิ่ม Logback dependencies ใน pom.xml ในแต่ละ service ด้วย
pom.xml
...
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>5.1</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
...
Pricing-Service — logback.xml
Product-Service — logback.xml
Searching for logs on Kibana
ทำการเรียก API ผ่านทาง API Gateway ดังนี้ http://localhost:9999/products/search?sku=sku-bnk48 และ http://localhost:9999/products/search?sku=sku-bnk48-gen2
ใน Kibana เราสามารถตรวจสอบทุกๆ log จาก microserivices ของเราดังนี้ โดยเราสามารถ search จาก log message หรือ search log ที่เกี่ยวข้างกับ transaction เดียวกันโดยใช้ trace ID ในการ query
ถ้าเราทำการ expand แต่ละ log row ก็จะมี details แสดงดังนี้
Distributed tracing with Zipkin
จากที่เราสร้าง unified logging platform ด้วย ELK stack และ Spring Cloud Sleuth นั้น ถือเป็น debugging tool ที่มีประสิทธิภาพเป็นอย่างมาก อย่างไรก็ตาม ใน microservices นั้น ยังจำเป็นต้องมี distributed tracing tools เพื่อใช้ในการทำ distributed tracing ซึ่งจะเป็นตัวช่วยในการหาสาเหตุของ performance issues ต่างๆ และ Zipkin จะเป็นตัวช่วยให้เราสามารถ trace transactions ตลอดทั้ง microservices โดย Zipkin ช่วยให้เราเห็นภาพรวมของ transaction และยังแสดงจำนวนเวลาที่ transaction ใช้ในแต่ละ service ด้วย
Zipkin Server as Docker container
ในการ set up Zipkin Server นั้น เราสามารถทำได้หลายวิธี ในบทความนี้เราจะใช้ Docker ในการสร้าง Zipkin Server โดยให้เราสร้าง docker-compose.yml ใน folder เดียวกันกับ Dockerfile และ Logstash configuration ที่เราได้สร้างก่อนหน้านี้ พร้อมทำการสร้าง ELK stack และ zipkin container ใน docker-compose.yml ดังนี้
docker-compose.yml
version: '3.6'
services:
elk:
build: .
ports:
- "5601:5601"
- "9200:9200"
- "5044:5044"
container_name: elk
zipkin:
image: openzipkin/zipkin
ports:
- 9411:9411
container_name: zipkin
จากนั้นทำการ run ELK stack และ Zipkin ด้วยคำสั่ง docker-compose up เราจะได้ ELK stack และ Zipkin server run อยู่บน port ต่างๆ ดังนี้
Kibana - http://localhost:5601/app/kibana
Elastic Search - http://localhost:9200
Logstash - http://localhost:5044
Zipkin server - http://localhost:9411
Setting up Zipkin dependencies
เพื่อที่จะ integrate service ของเรากับ Zipkin นั้น เราจะต้องเพิ่ม Maven dependency ที่ชื่อว่า spring-cloud-sleuth-zipkin ใน pom.xml ของแต่ละ service ดังนี้
pom.xml
...
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
...
หลังจากที่เพิ่ม spring-cloud-sleuth-zipkin dependency แล้ว ขั้นตอนต่อไปคือการ config ให้แต่ละ service ที่ต้องการเชื่อมต่อกับ Zipkin ให้ชี้ไปที่ Zipkin server ด้วยการกำหนด spring.zipkin.baseUrl property ใน application.yml
spring:
zipkin.baseUrl: http://localhost:9411
ถึงตรงนี้เราก็จะได้ clients config ที่สามารถเชื่อมต่อกับ Zipkin server แต่ก่อนที่เราจะใช้งาน Zipkin นั้น เราต้องทำการกำหนดว่าบ่อยแค่ไหนที่จะ write data ไปที่ Zipkin โดย default แล้ว Zipkin จะ write 10% ของ transactions ไปที่ Zipkin server โดยเราสามารถปรับเปลี่ยนค่า default นี้ได้จาก spring.sleuth.sampler.percentage property ซึ่งจะรับค่า 0 ถึง 1
เช่น
- 0 หมายความว่า Spring Cloud Sleuth จะไม่ส่ง data ไปที่ Zipkin เลย
- 0.5 หมายความว่า Spring Cloud Sleuth จะส่ง data จำนวน 50% ไปที่ Zipkin
ในที่นี้เราจะทำการส่ง trace information เสมอ ดังนั้นเราสามารถ set ค่าได้ที่ spring.sleuth.sampler.percentage property เป็น 1 หรือจะทำการ override default Sampler class ของ Spring Cloud Sleuth ด้วย AlwaysSampler ก็ได้
config ใน application.yml
spring:
zipkin.baseUrl: http://localhost:9411
sleuth.sampler.percentage: 1
หรือ override default Sampler class
@Bean
public Sampler defaultSampler() { return new AlwaysSampler();}
Using Zipkin to trace transactions
ทำการเรียก API ผ่านทาง API Gateway อีกครั้ง http://localhost:9999/products/search?sku=sku-bnk48
จะได้ output ดังนี้
Product-Service
Pricing-Service
จะเห็นว่าทั้ง Product-Service และ Pricing-Service จะได้ flag ของการส่ง log data ไปที่ Zipkin เป็น true แล้ว จากนั้นไปที่ http://localhost:9411 ทำการเลือก service จาก dropdown box แล้วกดปุ่ม Find traces ก็จะเห็น output ดังภาพด้านล่าง
หรือจะดูรายละเอียดจำนวนเวลาที่แต่ละ span ใช้ใน transaction ก็ได้ ดังนี้
คลิกแต่ละ span ก็จะได้รายละเอียดของ call timing และรายละเอียดของ HTTP call เพิ่มมากยิ่งขึ้น
Conclusion
ELK stack และ Zipkin เป็น tools อีกกลุ่มหนึ่งที่ได้รับความนิยมเป็นอย่างมากเมื่อสร้าง microservices ด้วย Spring Boot และ Spring Cloud ด้วย ELK stack และ Zipkin ในการทำ microservices monitoring ก็ไม่ใช่เรื่องยุ่งยากและซับซ้อนมากเกินไป Correlation IDs สามารถใช้ในการ link log จากหลายๆ service เข้าด้วยกัน ทำให้เราสามารถตรวจสอบ behavior ของ transaction ได้ และยังมี tools อื่นๆอีกมากมาย เช่น Hystrix, Turbine, Graylog, Splunk และ Papertrail ที่สามารถสร้าง microservices monitoring ด้วยเช่นกัน
ในบทความนี้เราก็ได้ทำการ integrate microservice แบบง่ายๆของเรากับ ELK stack และ Zipkin แต่ในโลกแห่งความเป็นจริงนั้น ทั้ง ELK stack และ Zipkin ต้องใช้ data stores แบบต่างๆมาใช้ในการเก็บ data ด้วย เพื่อที่จะสามารถเก็บไว้ดูได้ในอนาคต ซึ่งบทความนี้ไม่ได้กล่าวถึง สำหรับผู้ที่ต้องการศึกษาต่อ สามารถดูได้จาก document ของ ELK stack และ Zipkin สำหรับ source code ทั้งหมดของบทความนี้ สามารถหาได้จาก GitHub