ทุกวันนี้ เป็นไปไม่ได้เลยที่จะจินตนาการถึงโปรเจ็กต์ที่ใช้ Kubernetes โดยไม่มีสแต็ก ELK ซึ่งจะบันทึกบันทึกของทั้งแอปพลิเคชันและส่วนประกอบระบบของคลัสเตอร์ ในทางปฏิบัติของเรา เราใช้สแต็ก EFK กับ Fluentd แทน Logstash
Fluentd เป็นผู้รวบรวมบันทึกสากลสมัยใหม่ที่กำลังได้รับความนิยมมากขึ้นเรื่อยๆ และได้เข้าร่วม Cloud Native Computing Foundation ซึ่งเป็นเหตุผลว่าทำไมเวกเตอร์การพัฒนาจึงมุ่งเน้นไปที่การใช้ร่วมกับ Kubernetes
ข้อเท็จจริงของการใช้ Fluentd แทน Logstash ไม่ได้เปลี่ยนสาระสำคัญทั่วไปของชุดซอฟต์แวร์ อย่างไรก็ตาม Fluentd โดดเด่นด้วยความแตกต่างเฉพาะของตัวเองซึ่งเป็นผลมาจากความเก่งกาจของมัน
ตัวอย่างเช่น เมื่อเราเริ่มใช้ EFK ในโปรเจ็กต์ที่วุ่นวายซึ่งมีการบันทึกข้อมูลสูง เราต้องเผชิญกับความจริงที่ว่าใน Kibana มีข้อความบางข้อความแสดงซ้ำหลายครั้ง ในบทความนี้เราจะบอกคุณว่าเหตุใดปรากฏการณ์นี้จึงเกิดขึ้นและวิธีแก้ปัญหา
ปัญหาการทำสำเนาเอกสาร
ในโปรเจ็กต์ของเรา Fluentd ได้รับการปรับใช้เป็น DaemonSet (เปิดใช้งานโดยอัตโนมัติในอินสแตนซ์เดียวบนแต่ละโหนดของคลัสเตอร์ Kubernetes) และตรวจสอบบันทึก stdout คอนเทนเนอร์ใน /var/log/containers หลังจากรวบรวมและประมวลผล บันทึกในรูปแบบของเอกสาร JSON จะถูกส่งไปยัง ElasticSearch ซึ่งสร้างขึ้นในรูปแบบคลัสเตอร์หรือแบบสแตนด์อโลน ขึ้นอยู่กับขนาดของโปรเจ็กต์และข้อกำหนดด้านประสิทธิภาพและความทนทานต่อข้อผิดพลาด Kibana ถูกใช้เป็นส่วนต่อประสานกราฟิก
เมื่อใช้ Fluentd กับปลั๊กอินบัฟเฟอร์เอาต์พุต เราพบสถานการณ์ที่เอกสารบางรายการใน ElasticSearch มีเนื้อหาเหมือนกันทุกประการและแตกต่างกันเฉพาะในตัวระบุเท่านั้น คุณสามารถตรวจสอบได้ว่านี่เป็นข้อความที่ซ้ำกันโดยใช้บันทึก Nginx เป็นตัวอย่าง ในไฟล์บันทึก ข้อความนี้มีอยู่ในสำเนาเดียว:
127.0.0.1 192.168.0.1 - [28/Feb/2013:12:00:00 +0900] "GET / HTTP/1.1" 200 777 "-" "Opera/12.0" -
อย่างไรก็ตาม มีเอกสารหลายฉบับใน ElasticSearch ที่มีข้อความนี้:
{
"_index": "test-custom-prod-example-2020.01.02",
"_type": "_doc",
"_id": "HgGl_nIBR8C-2_33RlQV",
"_version": 1,
"_score": 0,
"_source": {
"service": "test-custom-prod-example",
"container_name": "nginx",
"namespace": "test-prod",
"@timestamp": "2020-01-14T05:29:47.599052886 00:00",
"log": "127.0.0.1 192.168.0.1 - [28/Feb/2013:12:00:00 0900] "GET / HTTP/1.1" 200 777 "-" "Opera/12.0" -",
"tag": "custom-log"
}
}
{
"_index": "test-custom-prod-example-2020.01.02",
"_type": "_doc",
"_id": "IgGm_nIBR8C-2_33e2ST",
"_version": 1,
"_score": 0,
"_source": {
"service": "test-custom-prod-example",
"container_name": "nginx",
"namespace": "test-prod",
"@timestamp": "2020-01-14T05:29:47.599052886 00:00",
"log": "127.0.0.1 192.168.0.1 - [28/Feb/2013:12:00:00 0900] "GET / HTTP/1.1" 200 777 "-" "Opera/12.0" -",
"tag": "custom-log"
}
}
ยิ่งไปกว่านั้น สามารถทำซ้ำได้มากกว่าสองครั้ง
ขณะแก้ไขปัญหานี้ในบันทึก Fluentd คุณสามารถดูคำเตือนจำนวนมากพร้อมเนื้อหาต่อไปนี้:
2020-01-16 01:46:46 +0000 [warn]: [test-prod] failed to flush the buffer. retry_time=4 next_retry_seconds=2020-01-16 01:46:53 +0000 chunk="59c37fc3fb320608692c352802b973ce" error_class=Fluent::Plugin::ElasticsearchOutput::RecoverableRequestFailure error="could not push logs to Elasticsearch cluster ({:host=>"elasticsearch", :port=>9200, :scheme=>"http", :user=>"elastic", :password=>"obfuscated"}): read timeout reached"
คำเตือนเหล่านี้เกิดขึ้นเมื่อ ElasticSearch ไม่สามารถส่งคืนการตอบสนองต่อคำขอภายในเวลาที่ระบุโดยพารามิเตอร์ request_timeout ซึ่งเป็นสาเหตุที่ทำให้ไม่สามารถล้างส่วนของบัฟเฟอร์ที่ส่งต่อได้ หลังจากนี้ Fluentd จะพยายามส่งส่วนของบัฟเฟอร์ไปยัง ElasticSearch อีกครั้ง และหลังจากพยายามหลายครั้งตามต้องการ การดำเนินการก็เสร็จสมบูรณ์:
2020-01-16 01:47:05 +0000 [warn]: [test-prod] retry succeeded. chunk_id="59c37fc3fb320608692c352802b973ce"
2020-01-16 01:47:05 +0000 [warn]: [test-prod] retry succeeded. chunk_id="59c37fad241ab300518b936e27200747"
2020-01-16 01:47:05 +0000 [warn]: [test-dev] retry succeeded. chunk_id="59c37fc11f7ab707ca5de72a88321cc2"
2020-01-16 01:47:05 +0000 [warn]: [test-dev] retry succeeded. chunk_id="59c37fb5adb70c06e649d8c108318c9b"
2020-01-16 01:47:15 +0000 [warn]: [kube-system] retry succeeded. chunk_id="59c37f63a9046e6dff7e9987729be66f"
อย่างไรก็ตาม ElasticSearch จะถือว่าแต่ละส่วนของบัฟเฟอร์ที่ถูกถ่ายโอนไม่ซ้ำกัน และกำหนดค่าฟิลด์ _id ที่ไม่ซ้ำกันให้กับพวกมันในระหว่างการสร้างดัชนี นี่คือลักษณะที่สำเนาของข้อความปรากฏขึ้น
ใน Kibana มีลักษณะดังนี้:
วิธีการแก้ปัญหา
มีหลายทางเลือกในการแก้ปัญหานี้ หนึ่งในนั้นคือกลไกที่สร้างไว้ในปลั๊กอิน fluent-plugin-elasticsearch เพื่อสร้างแฮชเฉพาะสำหรับแต่ละเอกสาร หากคุณใช้กลไกนี้ ElasticSearch จะจดจำการทำซ้ำในขั้นตอนการส่งต่อและป้องกันเอกสารที่ซ้ำกัน แต่เราต้องคำนึงว่าวิธีการแก้ปัญหานี้ต้องดิ้นรนกับการสืบสวนและไม่ได้กำจัดข้อผิดพลาดโดยไม่หมดเวลาดังนั้นเราจึงละทิ้งการใช้งาน
เราใช้ปลั๊กอินบัฟเฟอร์ในเอาต์พุต Fluentd เพื่อป้องกันการสูญเสียบันทึกในกรณีที่เกิดปัญหาเครือข่ายระยะสั้นหรือความเข้มของการบันทึกเพิ่มขึ้น หาก ElasticSearch ไม่สามารถเขียนเอกสารลงในดัชนีได้ทันทีด้วยเหตุผลบางประการ เอกสารจะถูกจัดคิวและจัดเก็บไว้ในดิสก์ ดังนั้นในกรณีของเราเพื่อที่จะกำจัดสาเหตุของปัญหาที่นำไปสู่ข้อผิดพลาดที่อธิบายไว้ข้างต้นจำเป็นต้องตั้งค่าที่ถูกต้องสำหรับพารามิเตอร์การบัฟเฟอร์ซึ่งบัฟเฟอร์เอาต์พุต Fluentd จะมีขนาดเพียงพอและ ขณะเดียวกันก็สามารถเคลียร์ได้ตามเวลาที่กำหนด
เป็นที่น่าสังเกตว่าค่าของพารามิเตอร์ที่กล่าวถึงด้านล่างเป็นรายบุคคลในแต่ละกรณีของการใช้บัฟเฟอร์ในปลั๊กอินเอาต์พุตเนื่องจากขึ้นอยู่กับปัจจัยหลายประการ: ความเข้มของการเขียนข้อความไปยังบันทึกตามบริการ ประสิทธิภาพระบบดิสก์ เครือข่าย โหลดช่องสัญญาณและแบนด์วิธ ดังนั้น เพื่อให้ได้การตั้งค่าบัฟเฟอร์ที่เหมาะสมสำหรับแต่ละกรณี แต่ไม่ซ้ำซ้อน หลีกเลี่ยงการค้นหาที่มีความยาวสุ่มสี่สุ่มห้า คุณสามารถใช้ข้อมูลการดีบักที่ Fluentd เขียนลงในบันทึกระหว่างการดำเนินการ และรับค่าที่ถูกต้องค่อนข้างรวดเร็ว
ในขณะที่บันทึกปัญหา การกำหนดค่ามีลักษณะดังนี้:
<buffer>
@type file
path /var/log/fluentd-buffers/kubernetes.test.buffer
flush_mode interval
retry_type exponential_backoff
flush_thread_count 2
flush_interval 5s
retry_forever
retry_max_interval 30
chunk_limit_size 8M
queue_limit_length 8
overflow_action block
</buffer>
เมื่อแก้ไขปัญหาจะมีการเลือกค่าของพารามิเตอร์ต่อไปนี้ด้วยตนเอง:
chunk_limit_size - ขนาดของชิ้นข้อความในบัฟเฟอร์ที่ถูกแบ่ง
- flush_interval - ช่วงเวลาหลังจากที่บัฟเฟอร์ถูกล้าง
- Queue_limit_length - จำนวนชิ้นสูงสุดในคิว
- request_timeout คือเวลาที่สร้างการเชื่อมต่อระหว่าง Fluentd และ ElasticSearch
ขนาดบัฟเฟอร์ทั้งหมดสามารถคำนวณได้โดยการคูณพารามิเตอร์ Queue_limit_length และ chunk_limit_size ซึ่งสามารถตีความได้ว่าเป็น “จำนวนสูงสุดของชิ้นในคิว ซึ่งแต่ละชิ้นมีขนาดที่กำหนด” หากขนาดบัฟเฟอร์ไม่เพียงพอ คำเตือนต่อไปนี้จะปรากฏในบันทึก:
2020-01-21 10:22:57 +0000 [warn]: [test-prod] failed to write data into buffer by buffer overflow action=:block
หมายความว่าบัฟเฟอร์ไม่มีเวลาในการล้างตามเวลาที่กำหนด และข้อมูลที่เข้าสู่บัฟเฟอร์เต็มจะถูกบล็อก ซึ่งจะทำให้ส่วนหนึ่งของบันทึกสูญหาย
คุณสามารถเพิ่มบัฟเฟอร์ได้สองวิธี: โดยการเพิ่มขนาดของแต่ละก้อนในคิว หรือจำนวนก้อนที่สามารถอยู่ในคิวได้
หากคุณตั้งค่าขนาดก้อน chunk_limit_size มากกว่า 32 เมกะไบต์ ElasticSeacrh จะไม่ยอมรับ เนื่องจากแพ็กเก็ตขาเข้าจะมีขนาดใหญ่เกินไป ดังนั้น หากคุณต้องการเพิ่มบัฟเฟอร์เพิ่มเติม จะเป็นการดีกว่าถ้าเพิ่มความยาวคิวสูงสุด Queue_limit_length
เมื่อบัฟเฟอร์หยุดโอเวอร์โฟลว์และเหลือเพียงข้อความการหมดเวลาไม่เพียงพอเท่านั้น คุณสามารถเริ่มเพิ่มพารามิเตอร์ request_timeout อย่างไรก็ตาม หากคุณตั้งค่าเป็นมากกว่า 20 วินาที คำเตือนต่อไปนี้จะเริ่มปรากฏในบันทึก Fluentd:
2020-01-21 09:55:33 +0000 [warn]: [test-dev] buffer flush took longer time than slow_flush_log_threshold: elapsed_time=20.85753920301795 slow_flush_log_threshold=20.0 plugin_id="postgresql-dev"
ข้อความนี้ไม่ส่งผลต่อการทำงานของระบบ แต่อย่างใด และหมายความว่าเวลาล้างบัฟเฟอร์ใช้เวลานานกว่าที่กำหนดโดยพารามิเตอร์ slow_flush_log_threshold นี่คือข้อมูลการแก้ไขข้อบกพร่อง และเราใช้เมื่อเลือกค่าของพารามิเตอร์ request_timeout
อัลกอริธึมการเลือกทั่วไปมีดังนี้:
- ตั้งค่า request_timeout เป็นค่าที่รับประกันว่าเกินความจำเป็น (หลายร้อยวินาที) ในระหว่างการตั้งค่า เกณฑ์หลักสำหรับการตั้งค่าที่ถูกต้องของพารามิเตอร์นี้คือการหายไปของคำเตือนเนื่องจากการไม่หมดเวลา
- รอข้อความเกี่ยวกับการเกินขีดจำกัด slow_flush_log_threshold ข้อความเตือนในช่อง elapsed_time จะแสดงเวลาจริงที่บัฟเฟอร์ถูกล้าง
- ตั้งค่า request_timeout เป็นค่าที่มากกว่าค่า elapsed_time สูงสุดที่ได้รับในช่วงระยะเวลาการสังเกต เราคำนวณค่า request_timeout เป็น elapsed_time + 50%
- หากต้องการลบคำเตือนเกี่ยวกับการฟลัชบัฟเฟอร์ขนาดยาวออกจากบันทึก คุณสามารถเพิ่มค่าของ slow_flush_log_threshold เราคำนวณค่านี้เป็น elapsed_time + 25%
ค่าสุดท้ายของพารามิเตอร์เหล่านี้ตามที่ระบุไว้ก่อนหน้านี้จะได้รับแยกกันสำหรับแต่ละกรณี โดยการปฏิบัติตามอัลกอริธึมข้างต้น เรารับประกันว่าจะกำจัดข้อผิดพลาดที่นำไปสู่ข้อความซ้ำๆ
ตารางด้านล่างแสดงจำนวนข้อผิดพลาดต่อวันซึ่งนำไปสู่การทำซ้ำข้อความการเปลี่ยนแปลงในกระบวนการเลือกค่าของพารามิเตอร์ที่อธิบายไว้ข้างต้น:
โหนด-1
โหนด-2
โหนด-3
โหนด-4
ก่อนหลัง
ก่อนหลัง
ก่อนหลัง
ก่อนหลัง
ไม่สามารถล้างบัฟเฟอร์ได้
1749/2
694/2
47/0
1121/2
ลองอีกครั้งสำเร็จ
410/2
205/1
24/0
241/2
เป็นที่น่าสังเกตว่าการตั้งค่าผลลัพธ์อาจสูญเสียความเกี่ยวข้องเมื่อโครงการเติบโตขึ้น และจำนวนบันทึกก็เพิ่มขึ้นตามไปด้วย สัญญาณหลักของการหมดเวลาไม่เพียงพอคือการส่งคืนข้อความเกี่ยวกับบัฟเฟอร์ฟลัชที่ยาวไปยังบันทึก Fluentd ซึ่งก็คือเกินขีดจำกัด slow_flush_log_threshold จากจุดนี้เป็นต้นไป ยังคงมีระยะขอบเล็กน้อยก่อนที่จะเกินพารามิเตอร์ request_timeout ดังนั้นจึงจำเป็นต้องตอบกลับข้อความเหล่านี้ในเวลาที่เหมาะสม และทำซ้ำขั้นตอนการเลือกการตั้งค่าที่เหมาะสมที่สุดที่อธิบายไว้ข้างต้น
ข้อสรุป
การปรับแต่งบัฟเฟอร์เอาต์พุต Fluentd อย่างละเอียดเป็นหนึ่งในขั้นตอนหลักของการกำหนดค่าสแต็ก EFK โดยกำหนดความเสถียรของการทำงานและการวางตำแหน่งเอกสารที่ถูกต้องในดัชนี ตามอัลกอริธึมการกำหนดค่าที่อธิบายไว้ คุณสามารถมั่นใจได้ว่าบันทึกทั้งหมดจะถูกเขียนลงในดัชนี ElasticSearch ตามลำดับที่ถูกต้อง โดยไม่มีการทำซ้ำหรือสูญเสีย
อ่านบทความอื่น ๆ ในบล็อกของเราด้วย:
Go และ Zabbix 5.0 กลายเป็นเพื่อนกัน การอัปเกรดคลัสเตอร์ Kubernetes โดยไม่ต้องหยุดทำงาน Kubernetes: เหตุใดการตั้งค่าการจัดการทรัพยากรระบบจึงมีความสำคัญ เคล็ดลับง่ายๆ สามประการในการย่อขนาดรูปภาพ Docker การสำรองข้อมูลโครงการเว็บที่ต่างกันจำนวนมาก
ที่มา: will.com