Мы ў Badoo стала маніторым свежыя тэхналогіі і ацэньваем, ці варта выкарыстоўваць іх у нашай сістэме. Адным з такіх даследаванняў і жадаем падзяліцца з супольнасцю. Яно прысвечана Loki – сістэме агрэгавання логаў.
Loki - гэтае рашэнне для захоўвання і прагляду логаў, таксама гэты стэк падае гнуткую сістэму для іх аналізу і адпраўкі дадзеных у Prometheus. У маі выйшла чарговае абнаўленне, якое актыўна прасоўваюць стваральнікі. Нас зацікавіла, што ўмее Loki, якія магчымасці падае і ў якой ступені можа выступаць у якасці альтэрнатывы ELK – стэка, які мы выкарыстоўваем цяпер.
Што такое Loki
Grafana Loki - гэта набор кампанентаў для паўнавартаснай сістэмы працы з логамі. У адрозненне ад іншых падобных сістэм Loki заснаваны на ідэі індэксаваць толькі метададзеныя логаў – labels (гэтак жа, як і ў Prometheus), a самі логі сціскаць побач у асобныя чанкі.
Перш чым перайсці да апісання таго, што можна рабіць пры дапамозе Loki, хачу растлумачыць, што маецца на ўвазе пад "ідэяй індэксаваць толькі метададзеныя". Параўнальны падыход Loki і падыход да індэксавання ў традыцыйных рашэннях, такіх як Elasticsearch, на прыкладзе радка з лога nginx:
Традыцыйныя сістэмы парсяць радок цалкам, уключаючы палі з вялікай колькасцю ўнікальных значэнняў user_id і item_id, і захоўваюць усё ў вялікія азначнікі. Перавагай дадзенага падыходу з'яўляецца тое, што можна выконваць складаныя запыты хутка, бо амаль усе дадзеныя - у індэксе. Але за гэта даводзіцца плаціць тым, што азначнік становіцца вялікім, што выліваецца ў патрабаванні да памяці. У выніку паўнатэкставы індэкс логаў супастаўны па памеры з самімі логамі. Для таго каб па ім хутка шукаць, азначнік павінен быць загружаны ў памяць. І чым больш логаў, тым хутчэй азначнік павялічваецца і тым больш памяці ён спажывае.
Падыход Loki патрабуе, каб з радка былі вынятыя толькі неабходныя дадзеныя, колькасць значэнняў якіх невяліка. Такім чынам, мы атрымліваем невялікі азначнік і можам шукаць дадзеныя, фільтруючы іх па часе і па праіндэксаваным палям, а затым скануючы пакінутае рэгулярнымі выразамі або пошукам падрадка. Працэс здаецца не самым хуткім, але Loki падзяляе запыт на некалькі частак і выконвае іх паралельна, апрацоўваючы вялікую колькасць дадзеных за кароткі час. Колькасць шардоў і паралельных запытаў у іх канфігуруецца; такім чынам, колькасць дадзеных, якую можна апрацаваць за адзінку часу, лінейна залежыць ад колькасці прадстаўленых рэсурсаў.
Гэты кампраміс паміж вялікім хуткім азначнікам і маленькім азначнікам з раўналежным поўным пераборам дазваляе Loki кантраляваць кошт сістэмы. Яе можна гнутка наладжваць і пашыраць у адпаведнасці з запатрабаваннямі.
Loki-стэк складаецца з трох кампанентаў: Promtail, Loki, Grafana. Promtail збірае логі, апрацоўвае іх і адпраўляе ў Loki. Loki іх захоўвае. А Grafana умее запытваць дадзеныя з Loki і паказваць іх. Наогул Loki можна выкарыстоўваць не толькі для захоўвання логаў і пошуку па іх. Увесь стэк дае вялікія магчымасці па апрацоўцы і аналізу паступаючых дадзеных, выкарыстоўваючы Prometheus way.
Апісанне працэсу ўстаноўкі можна знайсці тут.
Пошук па логах
Шукаць па логах можна ў спецыяльным інтэрфейсе Grafana – Explorer. Для запытаў выкарыстоўваецца мова LogQL, вельмі падобная на PromQL, якая выкарыстоўваецца ў Prometheus. У прынцыпе, яго можна разглядаць як размеркаваны grep.
Інтэрфейс пошуку выглядае так:
Сам запыт складаецца з дзвюх частак: selector і filter. Selector - гэта пошук па індэксаваным метададзеным (лэйблам), якія прысвоены логам, а filter - пошукавы радок або рэгэксп, з дапамогай якога адфільтроўваюцца запісы, вызначаныя селектарам. У прыведзеным прыкладзе: У фігурных дужках селектар, усё што пасля фільтр.
{image_name="nginx.promtail.test"} |= "index"
З-за прынцыпу працы Loki нельга рабіць запыты без селектара, але лэйблы можна рабіць калі заўгодна агульнымі.
Селектар - гэта key-value значэння ў фігурных дужках. Можна камбінаваць селектары і задаваць розныя ўмовы пошуку, выкарыстоўваючы аператары =, != або рэгулярныя выразы:
{instance=~"kafka-[23]",name!="kafka-dev"}
// Найдёт логи с лейблом instance, имеющие значение kafka-2, kafka-3, и исключит dev
Фільтр - гэта тэкст або рэгэксп, які адфільтруе ўсе дадзеныя, атрыманыя селектарам.
Ёсць магчымасць атрымання ad-hoc-графікаў па атрыманых даных у рэжыме metrics. Напрыклад, можна пазнаць частату з'яўлення ў логах nginx запісы, утрымоўвальнай радок index:
Поўнае апісанне магчымасцяў можна знайсці ў дакументацыі LogQL.
Парсінг логаў
Ёсць некалькі спосабаў сабраць логі:
З дапамогай Promtail, стандартнага кампанента стэка для збору логаў.
Выкарыстоўваць Fluentd ці Fluent Bit, якія ўмеюць адпраўляць дадзеныя ў Loki. У адрозненне ад Promtail яны маюць гатовыя парсеры практычна для любога віду лога і спраўляюцца ў тым ліку з multiline-логамі.
Звычайна для парсінгу выкарыстоўваюць Promtail. Ён робіць тры рэчы:
Знаходзіць крыніцы дадзеных.
Прымацоўвае да іх лэйблы.
Адпраўляе дадзеныя ў Loki.
У сапраўдны момант Promtail можа чытаць логі з лакальных файлаў і з systemd journal. Ён павінен быць устаноўлены на кожную машыну, з якой збіраюцца логі.
Ёсць інтэграцыя з Kubernetes: Promtail аўтаматычна праз Kubernetes REST API пазнае стан кластара і збірае логі з ноды, сэрвісу ці пода, адразу развешваючы лэйблы на аснове метададзеных з Kubernetes (імя пода, імя файла і т. д.).
Таксама можна развешваць лэйблы на аснове дадзеных з лога пры дапамозе Pipeline. Pipeline Promtail можа складацца з чатырох тыпаў стадый. Больш падрабязна - у афіцыйнай дакументацыі, тут жа адзначу некаторыя нюансы.
Parsing stages. Гэта стадыя RegEx і JSON. На гэтым этапе мы здабываем дадзеныя з логаў у так званую extracted map. Здабываць можна з JSON, проста капіюючы патрэбныя нам палі ў extracted map, або праз рэгулярныя выразы (RegEx), дзе ў extracted map "мапяцца" named groups. Extracted map уяўляе сабой key-value сховішча, дзе key - імя поля, а value - яго значэнне з логаў.
Transform stages. У гэтай стадыі ёсць дзве опцыі: transform, дзе мы задаем правілы трансфармацыі, і source - крыніца дадзеных для трансфармацыі з extracted map. Калі ў extracted map такога поля няма, тое яно будзе створана. Такім чынам, можна ствараць лэйблы, якія не заснаваны на extracted map. На гэтым этапе мы можам маніпуляваць дадзенымі ў extracted map, выкарыстоўваючы дастаткова магутны Golang Template. Акрамя таго, трэба памятаць, што extracted map цалкам загружаецца пры парсінгу, што дае магчымасць, напрыклад, правяраць значэнне ў ёй: "{{if .tag}tag value exists{end}}". Template падтрымлівае ўмовы, цыклы і некаторыя радковыя функцыі, такія як Replace і Trim.
Action stages. На гэтым этапе можна зрабіць што-небудзь з вынятым:
Стварыць лэйбл з extracted data, які праіндэксуецца Loki.
Змяніць або ўсталяваць час падзеі з лога.
Змяніць дадзеныя (тэкст лога), якія сыдуць у Loki.
Стварыць метрыкі.
Filtering stages. Стадыя match, на якой можна або адправіць у /dev/null запісы, якія нам не патрэбныя, або накіраваць іх на далейшую апрацоўку.
Пакажу на прыкладзе апрацоўкі звычайных nginx-логаў, як можна парсіць логі пры дапамозе Promtail.
Для тэсту возьмем у якасці nginx-proxy мадыфікаваную выяву nginx jwilder/nginx-proxy:alpine і невялікі дэман, які ўмее пытаць сам сябе па HTTP. У дэмана зададзена некалькі эндпаінтаў, на якія ён можа даваць адказы рознага памеру, з рознымі HTTP-статусамі і з рознай затрымкай.
Збіраць логі будзем з докер-кантэйнераў, якія можна знайсці па дарозе /var/lib/docker/containers/ / -json.log
У docker-compose.yml наладжваем Promtail і паказваем шлях да канфіга:
Дадаем у promtail.yml шлях да логаў (у канфізе ёсць опцыя "docker", якая робіць тое ж самае адной радком, але гэта было б не так наглядна):
scrape_configs:
- job_name: containers
static_configs:
labels:
job: containerlogs
__path__: /var/lib/docker/containers/*/*log # for linux only
Пры ўключэнні такой канфігурацыі ў Loki будуць пападаць логі са ўсіх кантэйнераў. Каб гэтага пазбегнуць, змяняем налады тэставага nginx у docker-compose.yml - дадаем лагіраванне поле tag:
Разбіраны request_url. З дапамогай рэгэкспу вызначаем прызначэнне запыту: да статыкі, да фатаграфій, да API і ўсталёўваны ў extracted map які адпавядае ключ.
- template:
source: request_type
template: "{{if .photo}}photo{{else if .static_type}}static{{else if .api_request}}api{{else}}other{{end}}"
Пры дапамозе ўмоўных аператараў у Template правяраем усталяваныя палі ў extracted map і ўсталёўваны для поля request_type патрэбныя значэнні: photo, static, API. Прызначаем other, калі не атрымалася. Цяпер request_type утрымоўвае тып запыту.
Усталеўваны лэйблы api_request, virtual_host, request_type і статут (HTTP status) на падставе таго, што атрымалася пакласці ў extracted map.
- output:
source: nginx_log_row
Змяняем output. Цяпер у Loki сыходзіць вычышчаны nginx-лог з extracted map.
Пасля запуску прыведзенага канфіга можна ўбачыць, што кожнаму запісу прысвоены пазнакі на аснове дадзеных з лога.
Трэба мець на ўвазе, што выманне пазнак з вялікай колькасцю значэнняў (cardinality) можа істотна запаволіць працу Loki. Гэта значыць не варта змяшчаць у азначнік, напрыклад, user_id. Падрабязней пра гэта чытайце ў артыкуле “How labels in Loki можа быць log queries faster and easier”. Але гэта не значыць, што нельга шукаць па user_id без азначнікаў. Трэба выкарыстоўваць фільтры пры пошуку ("грыпаць" па дадзеных), а індэкс тут выступае як ідэнтыфікатар патоку.
Візуалізацыя логаў
Loki можа выступаць у ролі крыніцы дадзеных для графікаў Grafana, выкарыстоўваючы LogQL. Падтрымліваюцца наступныя функцыі:
rate - колькасць запісаў у секунду;
count over time - колькасць запісаў у зададзеным дыяпазоне.
Яшчэ прысутнічаюць якія агрэгуюць функцыі Sum, Avg і іншыя. Можна будаваць дастаткова складаныя графікі, напрыклад графік колькасці HTTP-памылак:
Стандартны data source Loki некалькі зрэзаны па функцыянальнасці ў параўнанні з data source Prometheus (напрыклад, нельга змяніць легенду), але Loki можна падлучыць як крыніца з тыпам Prometheus. Я не ўпэўнены, што гэта дакументаваныя паводзіны, але, мяркуючы па адказе распрацоўшчыкаў”How to configure Loki як Prometheus datasource? · Issue #1222 · grafana/loki”, напрыклад, гэта цалкам законна, і Loki цалкам сумяшчальны з PromQL.
Дадаем Loki як data source з тыпам Prometheus і дапісваем URL /loki:
І можна рабіць графікі, як у тым выпадку, калі б мы працавалі з метрыкамі з Prometheus:
Я думаю, што разыходжанне ў функцыянальнасці часовае і распрацоўшчыкі ў будучыні гэта паправяць.
Метрыкі
У Loki даступныя магчымасць вымання лікавых метрык з логаў і адпраўка іх у Prometheus. Напрыклад, у логу nginx прысутнічае колькасць байтаў на адказ, а таксама, пры вызначанай мадыфікацыі стандартнага фармату лога, і час у секундах, якое запатрабавалася на адказ. Гэтыя дадзеныя можна атрымаць і адправіць у Prometheus.
Опцыя дазваляе вызначаць і абнаўляць метрыкі на аснове даных з extracted map. Гэтыя метрыкі не адпраўляюцца ў Loki яны з'яўляюцца ў Promtail /metrics endpoint. Prometheus павінен быць сканфігураваны такім чынам, каб атрымаць дадзеныя, атрыманыя на гэтай стадыі. У прыведзеным прыкладзе для request_type=“api” мы збіраем метрыку-гістаграму. З гэтым тыпам метрык зручна атрымліваць перцентили. Для статыкі і фота мы збіраем суму байтаў і колькасць радкоў, у якіх мы атрымалі байты, каб вылічыць сярэдняе значэнне.
Такім чынам можна даведацца, напрыклад, чатыры самыя павольныя запыты. Таксама на дадзеныя метрыкі можна наладзіць маніторынг.
маштабаванне
Loki можа быць як у адзіночным рэжыме (single binary mode), так і ў шаравальным (horizontally-scalable mode). У другім выпадку ён можа захоўваць дадзеныя ў воблака, прычым чанкі і індэкс захоўваюцца асобна. У версіі 1.5 рэалізавана магчымасць захоўвання ў адным месцы, але пакуль не рэкамендуецца выкарыстоўваць яе ў прадакшэне.
Чанкі можна захоўваць у S3-сумяшчальным сховішча, для захоўвання індэксаў – выкарыстоўваць гарызантальна якія маштабуюцца базы дадзеных: Cassandra, BigTable або DynamoDB. Іншыя часткі Loki - Distributors (для запісу) і Querier (для запытаў) - stateless і таксама маштабуюцца гарызантальна.
Для тэставання атрымоўванага памеру азначніка я ўзяў логі з кантэйнера nginx, для якога наладжваўся Pipeline, прыведзены вышэй. Файл з логамі змяшчаў 406 радкі сумарным аб'ёмам 624 Мб. Генераваліся логі на працягу гадзіны, прыкладна па 109 запісаў у секунду.
Прыклад двух радкоў з лога:
Пры індэксацыі ELK гэта дало памер азначніка 30,3 Мб:
У выпадку з Loki гэта дало прыкладна 128 Кб азначніка і прыкладна 3,8 Мб дадзеных у чанках. Варта адзначыць, што лог быў штучна згенераваны і не адрозніваўся вялікай разнастайнасцю дадзеных. Просты gzip на зыходным докераўскім JSON-логу з дадзенымі даваў кампрэсію 95,4%, а з улікам таго, што ў сам Loki дасылаўся толькі вычышчаны nginx-лог, то сціск да 4 Мб вытлумачальна. Сумарная колькасць унікальных значэнняў для лэйблаў Loki была 35, што тлумачыць невялікі памер індэкса. Для ELK лог таксама чысціўся. Такім чынам, Loki сціснуў зыходныя дадзеныя на 96%, а ELK – на 70%.
Спажыванне памяці
Калі параўноўваць увесь стэк Prometheus і ELK, то Loki есць у некалькі разоў менш. Зразумела, што сэрвіс на Go спажывае менш, чым сэрвіс на Java, і параўнанне памеру JVM Heap Elasticsearch і выдзеленай памяці для Loki некарэктна, але тым не менш варта адзначыць, што Loki выкарыстоўвае значна менш памяці. Яго перавага па CPU не так відавочна, але таксама прысутнічае.
Хуткасць
Loki хутчэй пажырае логі. Хуткасць залежыць ад шматлікіх фактараў – што за логі, як выдасканалена мы іх парусем, сетка, дыск і т. д. – але яна адназначна вышэй, чым у ELK (у маім цесцю – прыкладна ў два разы). Тлумачыцца гэта тым, што Loki кладзе значна менш дадзеных у азначнік і, адпаведна, менш чакай марнуе на індэксаванне. З хуткасцю пошуку пры гэтым сітуацыя зваротная: Loki адчувальна прытармажвае на дадзеных памерам больш за некалькі гігабайтаў, у ELK ж хуткасць пошуку ад памеру дадзеных не залежыць.
Пошук па логах
Loki істотна саступае ELK па магчымасцях пошуку па логах. Grep з рэгулярнымі выразамі - гэта моцная рэч, але ён саступае дарослай базе дадзеных. Адсутнасць range-запытаў, агрэгацыя толькі па лэйблах, немагчымасць шукаць без лэйблаў – усё гэта абмяжоўвае нас у пошуках цікавай інфармацыі ў Loki. Гэта не разумее, што з дапамогай Loki нічога нельга знайсці, але вызначае флоу працы з логамі, калі вы спачатку знаходзіце праблему на графіках Prometheus, а потым па гэтых лэйблах шукаеце, што здарылася ў логах.
Інтэрфейс
Па-першае, гэта прыгожа (прабачце, не мог утрымацца). Grafana мае прыемны воку інтэрфейс, але Kibana значна больш функцыянальная.
Плюсы і мінусы Loki
З плюсаў можна адзначыць, што Loki інтэгруецца з Prometheus, адпаведна, метрыкі і алертынг мы атрымліваем са скрынкі. Ён зручны для збору логаў і іх захоўвання з Kubernetes Pods, бо мае ўспадкаваны ад Prometheus service discovery і аўтаматычна наважвае лэйблы.
З мінусаў - слабая дакументацыя. Некаторыя рэчы, напрыклад асаблівасці і магчымасці Promtail, я выявіў толькі падчас вывучэнняў кода, балазе open-source. Яшчэ адзін мінус - слабыя магчымасці парсінгу. Напрыклад, Loki не ўмее парсіць multiline-лагі. Таксама да недахопаў можна аднесці тое, што Loki – адносна маладая тэхналогія (рэліз 1.0 быў у лістападзе 2019 года).
Заключэнне
Loki - на 100% цікавая тэхналогія, якая падыходзіць для невялікіх і сярэдніх праектаў, дазваляючы вырашаць мноства задач агрэгавання логаў, пошуку па логах, маніторынгу і аналізу логаў.
Мы не выкарыстоўваем Loki у Badoo, бо маем ELK-стэк, які нас уладкоўвае і які за шмат гадоў аброс рознымі кастамнымі рашэннямі. Для нас каменем спатыкнення з'яўляецца пошук па логах. Маючы амаль 100 Гб логаў у дзень, нам важна ўмець знаходзіць усё і крыху больш і рабіць гэта хутка. Для пабудовы графікаў і маніторынгу мы выкарыстоўваем іншыя рашэнні, якія заточаныя пад нашы патрэбы і інтэграваныя паміж сабой. У стэка Loki ёсць адчувальныя плюсы, але ён не дасць нам больш, чым у нас ёсць, і яго перавагі сапраўды не перакрыюць кошт міграцыі.
І хоць пасля даследавання стала зразумела, што мы Loki выкарыстоўваць не можам, спадзяемся, што дадзены пост дапаможа вам у выбары.
Рэпазітар з кодам, выкарыстаным у артыкуле, знаходзіцца тут.