Fluentd logų kolektorius

Kas yra Fluentd ir kodėl jis reikalingas

Kai tavo aplikacija auga, logų kiekis pradeda kelti rimtų galvos skausmų. Viena programa generuoja kelis megabaitus per dieną, kita – gigabaitus, o dar reikia viską surinkti, išfiltruoti, išsaugoti ir analizuoti. Čia ir ateina į pagalbą Fluentd – atviro kodo logų kolektorius, kuris tapo vienu populiariausių sprendimų šioje srityje.

Fluentd gimė Japonijoje, Treasure Data kompanijoje, ir dabar yra Cloud Native Computing Foundation (CNCF) projektas. Tai reiškia, kad jis puikiai dera su Kubernetes, Docker ir visa šiuolaikine cloud infrastruktūra. Pagrindinis jo tikslas – suvienodinti logų rinkimą ir maršrutizavimą, kad nebereikėtų kiekvienai programai rašyti atskiro kodo, kaip ir kur siųsti logus.

Pagalvokite apie Fluentd kaip apie universalų logų siurblį su daugybe antgalių. Jis gali surinkti logus iš failų, Docker konteinerių, syslog, HTTP užklausų ir dar kelių dešimčių šaltinių. Tada tuos logus gali filtruoti, transformuoti, praturtinti papildoma informacija ir išsiųsti į Elasticsearch, MongoDB, Amazon S3, Kafka ar bet kurią kitą sistemą. Visa tai konfigūruojama vienoje vietoje, be programavimo.

Architektūra ir pagrindiniai komponentai

Fluentd architektūra pagrįsta plugin sistema, kuri suteikia neįtikėtiną lankstumą. Yra keturi pagrindiniai plugin tipai: Input, Parser, Filter ir Output. Kiekvienas atlieka savo vaidmenį logų apdorojimo grandinėje.

Input pluginai yra atsakingi už duomenų surinkimą. Populiariausi – tail (skaito failus kaip Unix komanda tail -f), forward (priima logus iš kitų Fluentd instancijų), http (priima logus per HTTP endpoint). Yra dar dešimtys kitų – nuo Windows Event Log iki MQTT protokolo.

Parser pluginai išanalizuoja nestruktūrizuotus logus ir paverčia juos struktūrizuotais JSON objektais. Fluentd palaiko daugybę formatų: Apache/Nginx logus, syslog, CSV, regex pagrindu sukurtus parserius. Tai labai svarbu, nes tolimesnis apdorojimas dirba su struktūrizuotais duomenimis.

Filter pluginai leidžia transformuoti, praturtinti ar atmesti įrašus. Galite pridėti papildomų laukų (pvz., hostname, environment), pakeisti reikšmes, išfiltruoti debug lygmens logus production aplinkoje. Čia ir slypi Fluentd galybė – galite sukurti sudėtingas logų apdorojimo taisykles be programavimo.

Output pluginai išsiunčia apdorotus logus į paskirties vietas. Elasticsearch, MongoDB, Kafka, S3, Google Cloud Storage – palaikoma beveik viskas. Galite siųsti tuos pačius logus į kelias vietas vienu metu, kas labai patogu backup’ams ar skirtingoms analizės sistemoms.

Diegimas ir pradinis konfigūravimas

Fluentd diegimas priklauso nuo jūsų operacinės sistemos. Linux sistemose paprasčiausias būdas – naudoti oficialius paketus. Ubuntu/Debian:

curl -fsSL https://toolbelt.treasuredata.com/sh/install-ubuntu-focal-td-agent4.sh | sh

Tai įdiegs td-agent – Fluentd versiją su papildomomis bibliotekomis ir stabilesniais plugin’ais. CentOS/RHEL sistemoms yra analogiški skriptai. Docker aplinkoje galite naudoti oficialų image:

docker pull fluent/fluentd:latest

Po diegimo pagrindinis konfigūracijos failas yra /etc/td-agent/td-agent.conf (arba /etc/fluent/fluent.conf, priklausomai nuo diegimo būdo). Konfigūracija rašoma specialia sintakse, kuri primena Apache ar Nginx konfigūracijas.

Paprasčiausias pavyzdys – surinkti Nginx access logus ir išsiųsti į Elasticsearch:

<source>
  @type tail
  path /var/log/nginx/access.log
  pos_file /var/log/td-agent/nginx-access.pos
  tag nginx.access
  <parse>
    @type nginx
  </parse>
</source>

<match nginx.access>
  @type elasticsearch
  host elasticsearch.example.com
  port 9200
  index_name nginx-logs
  type_name access_log
</match>

Šis konfigūracija skaito Nginx access log failą, išanalizuoja jį pagal standartinį Nginx formatą ir siunčia į Elasticsearch. pos_file parametras nurodo, kur saugoti informaciją apie paskutinę perskaitytą poziciją faile – tai užtikrina, kad po Fluentd perkrovimo nesikartotų tie patys logai.

Tagų sistema ir maršrutizavimas

Tagų sistema – tai Fluentd širdis. Kiekvienas log įrašas gauna tag’ą, kuris vėliau naudojamas maršrutizavimui. Tag’ai hierarchiniai, naudojant taškus kaip skirtukus: app.production.web, system.auth, docker.container.nginx.

Match direktyvos naudoja pattern matching, kad nukreiptų logus su tam tikrais tag’ais į reikiamus output’us. Galite naudoti wildcard’us:

<match app.**>
  @type file
  path /var/log/application/${tag}
</match>

Dvigubas žvaigždutė ** atitinka bet kokį tag’ą, prasidedantį app.. Vienguba žvaigždutė * atitinka tik vieną hierarchijos lygį. Tai leidžia sukurti labai lanksčias maršrutizavimo taisykles.

Praktikoje dažnai naudojamas rewrite_tag_filter plugin’as, kuris leidžia pakeisti tag’us pagal log’o turinį. Pavyzdžiui, galite skirtingus error lygius siųsti į skirtingas vietas:

<match app.**>
  @type rewrite_tag_filter
  <rule>
    key level
    pattern /^error$/
    tag ${tag}.errors
  </rule>
  <rule>
    key level
    pattern /^(warn|info|debug)$/
    tag ${tag}.normal
  </rule>
</match>

<match app.**.errors>
  @type slack
  webhook_url https://hooks.slack.com/services/YOUR/WEBHOOK/URL
</match>

<match app.**.normal>
  @type elasticsearch
  host localhost
  port 9200
</match>

Šis pavyzdys rodo, kaip error lygmens logai gali būti siunčiami į Slack realiu laiku, o kiti – į Elasticsearch įprastai analizei.

Filtravimas ir duomenų transformacija

Filter pluginai leidžia modifikuoti log įrašus prieš juos išsiunčiant. Vienas dažniausiai naudojamų – record_transformer, kuris prideda ar modifikuoja laukus:

<filter app.**>
  @type record_transformer
  <record>
    hostname ${hostname}
    environment production
    timestamp ${time}
  </record>
</filter>

Tai prideda tris papildomus laukus prie kiekvieno log įrašo. ${hostname} ir ${time} yra Fluentd kintamieji, kurie automatiškai užpildomi.

Kitas naudingas filter’is – grep, kuris leidžia filtruoti įrašus pagal tam tikrus kriterijus:

<filter app.**>
  @type grep
  <exclude>
    key message
    pattern /health-check/
  </exclude>
  <exclude>
    key status
    pattern /^2\d\d$/
  </exclude>
</filter>

Šis pavyzdys atmeta visus logus, kuriuose message laukas turi „health-check” arba status kodas prasideda 2 (sėkmingi HTTP atsakymai). Tai labai sumažina logų kiekį, kai jums nerūpi sėkmingi health check’ai.

parser filter’is leidžia išanalizuoti sudėtingesnius logų formatus. Pavyzdžiui, jei jūsų aplikacija logina JSON stringus kaip paprastą tekstą:

<filter app.**>
  @type parser
  key_name message
  <parse>
    @type json
  </parse>
</filter>

Tai išanalizuos message lauko turinį kaip JSON ir visus jo laukus padarys prieinamus kaip atskirų laukų.

Kubernetes ir Docker integracija

Šiuolaikinėse infrastruktūrose Fluentd dažniausiai naudojamas kaip DaemonSet Kubernetes klasteryje. Tai reiškia, kad vienas Fluentd pod’as veikia kiekviename node’e ir renka visų konteinerių logus.

Kubernetes automatiškai išsaugo konteinerių logus JSON formatu /var/log/containers/ kataloge. Fluentd gali juos skaityti ir automatiškai praturtinti Kubernetes metadata – pod name, namespace, labels, annotations:

<source>
  @type tail
  path /var/log/containers/*.log
  pos_file /var/log/fluentd-containers.log.pos
  tag kubernetes.*
  read_from_head true
  <parse>
    @type json
    time_format %Y-%m-%dT%H:%M:%S.%NZ
  </parse>
</source>

<filter kubernetes.**>
  @type kubernetes_metadata
  @id filter_kube_metadata
</filter>

kubernetes_metadata filter’is prisijungia prie Kubernetes API ir praturtina kiekvieną log įrašą informacija apie pod’ą, konteinerį, namespace’ą ir kt. Tai neįtikėtinai naudinga analizuojant logus – galite filtruoti pagal namespace, ieškoti problemų konkrečiame deployment’e ar sekti, kaip veikia tam tikras servisas.

Docker aplinkoje be Kubernetes galite naudoti Docker logging driver’į arba tiesiog skaityti Docker logus iš /var/lib/docker/containers/. Bet rekomenduoju naudoti Fluentd kaip Docker logging driver’į:

docker run --log-driver=fluentd --log-opt fluentd-address=localhost:24224 --log-opt tag=docker.{{.Name}} your-image

Tai siunčia visus konteinerio logus tiesiai į Fluentd, kuris klauso 24224 porte.

Performance tuning ir best practices

Fluentd parašytas Ruby kalba, kas reiškia, kad jis nėra pats greičiausias logų kolektorius. Bet yra Fluent Bit – lengvesnė C kalba parašyta versija, kuri naudoja mažiau resursų. Praktikoje dažnai naudojama kombinacija: Fluent Bit kaip lightweight kolektorius kiekviename node’e, o Fluentd kaip centralizuotas agregatorius ir procesorius.

Kai turite didelį logų srautą, svarbu optimizuoti buffer’ius. Fluentd naudoja buffer’ius prieš išsiunčiant duomenis į output’us. Yra du buffer tipų: memory ir file. Memory greitesnis, bet prarandate duomenis, jei Fluentd užstringa. File buffer’is saugesnis:

<match app.**>
  @type elasticsearch
  host localhost
  port 9200
  <buffer>
    @type file
    path /var/log/fluentd/buffer/elasticsearch
    flush_interval 5s
    chunk_limit_size 2M
    queue_limit_length 32
    retry_max_interval 30
    retry_forever true
  </buffer>
</match>

flush_interval nurodo, kaip dažnai siųsti sukauptus duomenis. chunk_limit_size – maksimalus vieno chunk’o dydis. queue_limit_length – kiek chunk’ų gali būti eilėje. Jei paskirties sistema nepasiekiama, Fluentd bandys siųsti iš naujo pagal retry_max_interval ir retry_forever parametrus.

Svarbu monitorinti patį Fluentd. Jis turi įtaisytą monitoring endpoint’ą:

<source>
  @type monitor_agent
  bind 0.0.0.0
  port 24220
</source>

Prisijungę prie http://localhost:24220/api/plugins.json gausite JSON su visų plugin’ų statistika – kiek įrašų apdorota, kiek buffer’iuose, kiek klaidų.

Kitas svarbus dalykas – structured logging aplikacijose. Vietoj paprastų teksto eilučių, loginkite JSON formatą. Tai labai palengvina Fluentd darbą – nereikia sudėtingų regex parser’ių, viskas jau struktūrizuota. Daugelis logging bibliotekų (logrus Go kalboje, winston Node.js, structlog Python) palaiko JSON formatą out of the box.

Realūs naudojimo scenarijai ir patarimai

Vienas dažniausių scenarijų – centralizuotas logų saugojimas. Turite keliolika ar keliasdešimt serverių, kiekvienas generuoja logus. Vietoj to, kad SSH’intumėtės į kiekvieną serverį ir grep’intumėte failus, visus logus siunčiate į Elasticsearch per Fluentd. Tada naudojate Kibana vizualizacijai ir paieškai.

Bet ne visi logai vienodai svarbūs. Production aplinkoje generuojama tiek daug logų, kad saugoti viską tampa brangu. Čia praverčia išmanus filtravimas:

<filter app.production.**>
  @type grep
  <regexp>
    key level
    pattern /^(error|warn)$/
  </regexp>
</filter>

<filter app.production.**>
  @type sampling
  interval 10
  sample_rate 1
</filter>

Pirmasis filter’is praleidžia tik error ir warning lygmens logus. Antrasis naudoja sampling – iš info/debug lygmens logų paima tik kas dešimtą įrašą. Tai drastiškai sumažina logų kiekį, bet vis tiek turite pakankamai duomenų statistikai.

Kitas naudingas scenarijus – logų siuntimas į kelias vietas. Pavyzdžiui, visus logus siunčiate į Elasticsearch trumpalaikiam saugojimui (7 dienos), bet error lygmens logus dar ir į S3 ilgalaikiam archyvui:

<match app.**>
  @type copy
  <store>
    @type elasticsearch
    host localhost
    port 9200
  </store>
  <store>
    @type grep
    <regexp>
      key level
      pattern /^error$/
    </regexp>
    <store>
      @type s3
      aws_key_id YOUR_AWS_KEY
      aws_sec_key YOUR_AWS_SECRET
      s3_bucket your-logs-bucket
      s3_region us-east-1
      path logs/errors/
    </store>
  </store>
</match>

copy plugin’as dubliuoja logus į kelis output’us. Antrasis store turi įdėtą grep filter’į, kuris praleidžia tik error lygmens logus į S3.

Dar vienas praktiškas patarimas – naudokite secure_forward plugin’ą, kai siunčiate logus per internetą. Paprastas forward plugin’as siunčia duomenis nešifruotus. secure_forward naudoja TLS ir autentifikaciją:

<source>
  @type secure_forward
  self_hostname fluentd-aggregator
  shared_key your_shared_secret_key
  secure yes
  <user>
    username fluentd
    password your_password
  </user>
</source>

Tai ypač svarbu, jei jūsų logai turi jautrią informaciją – API raktus, IP adresus, user ID ir pan.

Alternatyvos ir kada rinktis Fluentd

Fluentd nėra vienintelis logų kolektorius rinkoje. Yra Logstash (Elastic Stack dalis), Fluent Bit, Vector, Filebeat ir kiti. Kiekvienas turi savo privalumų ir trūkumų.

Logstash galingesnis duomenų transformacijoms – turi daugiau filter’ių ir galimybių. Bet jis reikalauja daugiau resursų (JVM overhead) ir sudėtingesnis konfigūruoti. Jei jau naudojate Elastic Stack, Logstash gali būti natūralesnis pasirinkimas.

Fluent Bit, kaip minėjau, yra lengvesnė Fluentd versija. Jis naudoja 10-20 kartų mažiau atminties ir yra greitesnis. Bet turi mažiau plugin’ų ir funkcionalumo. Ideali kombinacija – Fluent Bit edge’uose (serveriuose, konteineriuose), Fluentd centre (agregavimui ir sudėtingoms transformacijoms).

Vector – naujesnis projektas, parašytas Rust kalba. Labai greitas ir efektyvus, turi puikią dokumentaciją. Bet mažesnė bendruomenė ir mažiau plugin’ų nei Fluentd. Jei jums reikia maksimalaus performance ir nesvarbu plugin’ų ekosistema, Vector geras pasirinkimas.

Filebeat – dar vienas lightweight kolektorius iš Elastic šeimos. Labai paprastas, bet ir labai ribotas. Tinka, jei tiesiog norite siųsti failus į Elasticsearch be jokių transformacijų.

Fluentd rinktis verta, kai:
– Reikia lankstumo – daug skirtingų šaltinių ir paskirčių
– Naudojate Kubernetes – Fluentd turi puikią integraciją
– Reikia sudėtingų transformacijų ir maršrutizavimo
– Svarbi bendruomenė ir plugin’ų ekosistema
– Norite vendor-neutral sprendimo (ne prisirišti prie Elastic ar kito vendor’io)

Kai viskas suveikia kaip reikiant

Fluentd nėra silver bullet, bet tai tikrai vienas geriausių įrankių logų valdymui šiuolaikinėse infrastruktūrose. Jis suteikia tą lankstumą, kurio reikia, kai jūsų sistema auga ir komplikuojasi. Pradėjus nuo paprasto Nginx logų rinkimo, galite išaugti iki sudėtingos multi-cloud infrastruktūros su šimtais servisų ir konteinerių.

Svarbiausia – nepersistengti iš karto. Pradėkite paprastai: surinkite logus iš vieno šaltinio, išsiųskite į vieną paskirties vietą. Kai tai veikia, pradėkite pridėti filter’ius, transformacijas, papildomas paskirties vietas. Fluentd konfigūracija gali tapti labai sudėtinga, jei iš karto bandysite aprėpti viską.

Monitorinkite patį Fluentd – tai kritinė infrastruktūros dalis. Jei Fluentd užstringa, prarandate logus, o tai reiškia, kad nematote problemų savo sistemoje. Naudokite monitoring endpoint’ą, sekite buffer’ių būseną, nustatykite alertus, kai buffer’iai prisipildo ar output’ai nepasiekiami.

Ir nepamirškite dokumentuoti savo konfigūraciją. Po kelių mėnesių patys neprisiminsite, kodėl tas konkretus filter’is ar match pattern’as ten yra. Komentarai konfigūracijos faile gelbsti.

Fluentd bendruomenė aktyvi, dokumentacija gera, plugin’ų daugybė. Jei kažkas neveikia ar reikia specifinio funkcionalumo, greičiausiai jau kažkas susidūrė su ta pačia problema ir yra sprendimas. GitHub issues, Google Groups, Stack Overflow – visur rasite pagalbos.

Galiausiai, logai yra tik duomenys. Svarbu, ką su jais darote. Fluentd padeda tuos duomenis surinkti, apdoroti ir pristatyti, kur reikia. Bet analizė, alertai, insights – tai jau jūsų darbas. Investuokite laiką į tinkamą Elasticsearch/Kibana setup’ą, sukurkite naudingus dashboard’us, nustatykite protingus alertus. Tada Fluentd taps neatsiejama jūsų observability stack’o dalimi.

Daugiau

Logstash grok patterns: logų parserinimas

E-komercijos platformos: WooCommerce ar Shopify?