Fluentd: เหตุใดการกำหนดค่าบัฟเฟอร์เอาต์พุตจึงเป็นสิ่งสำคัญ

Fluentd: เหตุใดการกำหนดค่าบัฟเฟอร์เอาต์พุตจึงเป็นสิ่งสำคัญ

ทุกวันนี้ เป็นไปไม่ได้เลยที่จะจินตนาการถึงโปรเจ็กต์ที่ใช้ 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 มีลักษณะดังนี้:

Fluentd: เหตุใดการกำหนดค่าบัฟเฟอร์เอาต์พุตจึงเป็นสิ่งสำคัญ

วิธีการแก้ปัญหา

มีหลายทางเลือกในการแก้ปัญหานี้ หนึ่งในนั้นคือกลไกที่สร้างไว้ในปลั๊กอิน 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

อัลกอริธึมการเลือกทั่วไปมีดังนี้:

  1. ตั้งค่า request_timeout เป็นค่าที่รับประกันว่าเกินความจำเป็น (หลายร้อยวินาที) ในระหว่างการตั้งค่า เกณฑ์หลักสำหรับการตั้งค่าที่ถูกต้องของพารามิเตอร์นี้คือการหายไปของคำเตือนเนื่องจากการไม่หมดเวลา
  2. รอข้อความเกี่ยวกับการเกินขีดจำกัด slow_flush_log_threshold ข้อความเตือนในช่อง elapsed_time จะแสดงเวลาจริงที่บัฟเฟอร์ถูกล้าง
  3. ตั้งค่า request_timeout เป็นค่าที่มากกว่าค่า elapsed_time สูงสุดที่ได้รับในช่วงระยะเวลาการสังเกต เราคำนวณค่า request_timeout เป็น elapsed_time + 50%
  4. หากต้องการลบคำเตือนเกี่ยวกับการฟลัชบัฟเฟอร์ขนาดยาวออกจากบันทึก คุณสามารถเพิ่มค่าของ 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 ตามลำดับที่ถูกต้อง โดยไม่มีการทำซ้ำหรือสูญเสีย

อ่านบทความอื่น ๆ ในบล็อกของเราด้วย:

ที่มา: will.com

เพิ่มความคิดเห็น