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, падняты ў кластарным або standalone выглядзе, у залежнасці ад маштабаў праекту і патрабаванняў да прадукцыйнасці і адмоваўстойлівасці. У якасці графічнага інтэрфейсу выкарыстоўваецца 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%.

Выніковыя значэнні дадзеных параметраў, як было заўважана раней, атрымліваюцца індывідуальнымі для кожнага выпадку. Прытрымліваючыся прыведзенаму вышэй алгарытму, мы гарантавана ўхіляем памылку, якая прыводзіць да паўтору паведамленняў.

У табліцы ніжэй паказана, як змяняецца колькасць памылак за суткі, якія прыводзяць да дублявання паведамленняў, падчас падбору значэнняў апісаных вышэй параметраў:

node-1
node-2
node-3
node-4

Да/пасля
Да/пасля
Да/пасля
Да/пасля

failed to flush the buffer
1749/2
694/2
47/0
1121/2

retry succeeded
410/2
205/1
24/0
241/2

Варта дадаткова адзначыць, што атрыманыя наладкі могуць страціць сваю актуальнасць падчас ростаў праекту і, адпаведна, павелічэнні колькасці логаў. Першаснай прыкметай недахопу ўсталяванага тайм-аўту з'яўляецца вяртанне ў лог Fluentd паведамленняў аб доўгай ачыстцы буфера, гэта значыць перавышэнне парога slow_flush_log_threshold. З гэтага моманту ёсць яшчэ невялікі запас да перавышэння параметра request_timeout, таму неабходна своечасова адрэагаваць на дадзеныя паведамленні і паўторна выканаць працэс падбору аптымальных налад, апісаны вышэй.

Заключэнне

Тонкая настройка выходнага буфера Fluentd з'яўляецца адным з галоўных этапаў налады EFK стэка, які вызначае стабільнасць яго працы і карэктнасць размяшчэння дакументаў у індэксах. Арыентуючыся на апісаны алгарытм налады, можна быць упэўненым, што ўсе логі будуць запісаныя ў азначнік ElasticSearch у правільным парадку, без паўтораў і страт.

Таксама чытайце іншыя артыкулы ў нашым блогу:

Крыніца: habr.com

Дадаць каментар