У наш час немагчыма ўявіць праект на базе 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. У файле лога дадзенае паведамленне існуе ў адзіным асобніку:
Падчас фіксацыі дадзенай праблемы ў логах 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 паўторна і праз адвольную колькасць спроб аперацыя завяршаецца паспяхова:
Аднак, ElasticSearch успрымае кожны з перасланых фрагментаў буфера як унікальны і прысвойвае ім унікальныя значэнні палёў _id пры індэксацыі. Такім чынам і з'яўляюцца копіі паведамленняў.
У Kibana гэта выглядае так:
Рашэнне праблемы
Існуе некалькі варыянтаў рашэння дадзенай праблемы. Адзін з іх - убудаваны ў убудова fluent-plugin-elasticsearch механізм генерацыі унікальнага хеша для кожнага дакумента. Калі выкарыстоўваць дадзены механізм, ElasticSearch будзе распазнаваць паўторы на стадыі перасылання і не дапушчаць дубліраванні дакументаў. Але нельга не ўлічваць, што дадзены спосаб рашэння праблемы дужаецца са следствам і не ўхіляе памылку з недахопам тайм-аўту, таму мы адмовіліся ад яго ўжывання.
Мы выкарыстоўваем буферызавальны плягін на выхадзе Fluentd, каб не дапусціць страты логаў у выпадку кароткачасовых непаладак з сеткай або ўзрослай інтэнсіўнасці запісу логаў. Калі па якім-небудзь чынніку ElasticSearch не можа імгненна запісаць дакумент у азначнік, дакумент пападае ў чаргу, якая захоўваецца на дыску. Таму, у нашым выпадку, каб ухіліць крыніцу праблемы, якая прыводзіць да ўзнікнення апісанай вышэй памылкі, неабходна задаць карэктныя значэнні параметраў буферызацыі, пры якіх выходны буфер Fluentd будзе дастатковага аб'ёму і пры гэтым паспяваць чысціцца за адведзены час.
Варта адзначыць, што значэнні параметраў, прамова аб якіх пайдзе ніжэй, індывідуальныя ў кожным пэўным выпадку выкарыстання буферызацыі ў выходных убудовах, бо залежаць ад мноства фактараў: інтэнсіўнасці запісу ў лог паведамленняў сэрвісамі, прадукцыйнасці дыскавай сістэмы, загружанасці сеткавага канала і яго прапускной здольнасці. Таму, каб атрымаць прыдатныя для кожнага асобнага выпадку, але не залішнія налады буфера, пазбягаючы працяглага перабору ўсляпую, можна скарыстацца адладкавай інфармацыяй, якую піша ў свой лог Fluentd падчас прац і параўнальна хутка атрымаць карэктныя значэнні.
На момант фіксацыі праблемы канфігурацыя выглядала наступным чынам:
У ходзе вырашэння праблемы ўручную падбіраліся значэнні наступных параметраў:
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%.
Выніковыя значэнні дадзеных параметраў, як было заўважана раней, атрымліваюцца індывідуальнымі для кожнага выпадку. Прытрымліваючыся прыведзенаму вышэй алгарытму, мы гарантавана ўхіляем памылку, якая прыводзіць да паўтору паведамленняў.
У табліцы ніжэй паказана, як змяняецца колькасць памылак за суткі, якія прыводзяць да дублявання паведамленняў, падчас падбору значэнняў апісаных вышэй параметраў:
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 у правільным парадку, без паўтораў і страт.