The OpenNET Project / Index page

[ новости /+++ | форум | теги | ]

Отправка логов из kubernetes в clickhouse
Хочу поделится опытом отправки логов java-приложений, развернутых в kubernetes,
в базу clickhouse. Для отправки используем fluent-bit, который настроим на
отправку логов по http в формате json_stream.

Пара слов о fluent-bit

Fluent-bit  работает с записями. Каждая запись состоит из тега и
именованного массива значений.

  • Input Секции input указывают как и откуда брать данные, какой тег присвоить записям и в какой парсер передать их для получения массива значений. Парсер указывается параметром Parser в секции Input. В нашем случае берём теги из названия файла при помощи regexp.
  • Parser Секция parsers указывает как получить из сообщения массив значений. В случае с kubernetes все сообщения представляют из себя JSON с 3 полями: log, stream и time. В нашем случае поле log также содержит JSON.
  • Filter Пройдя парсинг, все сообщения попадают в фильтры, применение которых настраивается параметром Match. В каждый фильтр попадают только те сообщения, которые имеют тег, попадающий в regexp, указанный в Match. В нашем случае фильтры также удаляют сообщения, которые пришли из служебных namespace и лишние поля, чтобы сообщения смогли попасть в clickhouse без ошибок, даже если что-то пошло не так.
  • Помещение тегов в лог Fluent-bit использует теги только для маршрутизации сообщений, подразумевается, что теги не должны попадать в лог. Для того, чтобы в лог попала информаци о том в каком namespace, contaner и pod произошло событие, применяется скрипт на lua. Если распарсить сообщение не удалось или поле message оказалось пустым после парсинга, значит в выводе приложения был не JSON или он был в не верном формате. Тогда в поле message помещается всё сообщение без парсинга.
  • Output Секция указывает куда направить сообщения. Применение секций Output определяется параметром Match аналогично с секцией Filter. В нашем случае сообщения уходят в clickhouse по http в формате json_stream. Примеры Наши приложения выводят в логи в формате JSON в stdout, их собирает kubernetes и создает симлинки на них в /var/log/containers. Логи работы пода java-pod-5cb8d7898-94sjc из деплоймента java-pod в неймспейсе default попадают в файл вида /var/log/containers/java-pod-5cb8d7898-94sjc_default_java-pod-08bc375575ebff925cff93f0672af0d3d587890df55d6bd39b3b3a962fc2acb9.log Пример записи {"log":"{\"timeStamp\":\"2021-11-24T11:00:00.000Z\",\"message\":\"My message id: 8543796, country: RU\",\"logger\":\"com.YavaApp.app.service.YavaService\",\"thread\":\"http-nio-8080-exec-2\",\"level\":\"INFO\",\"levelValue\":40000}\n","stream":"stdout","time":"2021-11-24T11:00:00.000000000Z"} Как видно из примера, в JSON-записи, поле log содержит в себе экранированный JSON, который также нужно разобрать, а из имени файла понятно к какому поду, деплою и неймспейсу принадлежит запись. Clickhouse По умолчанию clickhouse слушает команды по протоколу http на порту 8123. Менять эти настройки нет необходимости. Создадим в clickhouse схему logs и таблицу log в ней. create database logs; use logs; create table logs.log( pod_time DateTime('Etc/UTC'), namespace String, container String, pod String, timeStamp String, stream String, thread String, level String, levelValue Int, logger String, message String ) ENGINE = MergeTree PARTITION BY toYYYYMM(pod_time) ORDER BY pod_time; Файлы конфигурации Файл конфигурации fluent-bit для kubernetes будет выглядеть примерно так: apiVersion: v1 kind: ConfigMap metadata: labels: k8s-app: fluent-bit name: fluent-bit namespace: monitoring data: filter.conf: | [FILTER] Name lua Match * script make_tags.lua call make_tags [FILTER] Name grep Match kube.* Exclude namespace monitoring Exclude namespace metallb-system Exclude namespace gitlab-managed-apps Exclude namespace kube-* [FILTER] Name record_modifier Match kube.* Whitelist_key pod_time Whitelist_key namespace Whitelist_key container Whitelist_key pod Whitelist_key timeStamp Whitelist_key stream Whitelist_key thread Whitelist_key level Whitelist_key levelValue Whitelist_key logger Whitelist_key message Record cluster k8s-test fluent-bit.conf: | [SERVICE] Flush 1 Log_Level info Daemon off Parsers_File parsers.conf @INCLUDE filter.conf @INCLUDE input.conf @INCLUDE output.conf input.conf: | [INPUT] Name tail Path /var/log/containers/*.log Parser kub-logs Refresh_Interval 5 Mem_Buf_Limit 20MB Skip_Long_Lines On DB /var/log/flb_kube_default.db DB.Sync Normal Tag kube.<namespace_name>.<container_name>.<pod_name> Tag_Regex (?<pod_name>[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*)_(?<namespace_name>[^_]+)_(?<container_name>.+)- make_tags.lua: | function make_tags(tag, timestamp, record) new_record = record local tag_list = {} for s in string.gmatch(tag, "[^.]+") do table.insert(tag_list, s) end new_record["namespace"] = tag_list[2] new_record["container"] = tag_list[3] new_record["pod"] = tag_list[4] if (record["message"] == nil) then new_record["message"] = record["log"] end return 1, timestamp, new_record end output.conf: | [OUTPUT] Name http Host clickhouse-address Port 8123 URI /?user=user&password=pass&database=logs&query=INSERT%20INTO%20log%20FORMAT%20JSONEachRow Format json_stream Json_date_key pod_time Json_date_format epoch parsers.conf: | [PARSER] Name kub-logs Format json Time_Key time Time_Format %Y-%m-%dT%H:%M:%S.%L # Command | Decoder | Field | Optional Action # ==============|==============|=========|================= Decode_Field_As escaped_utf8 log do_next Decode_Field json log Конфигурация DaemonSet, который развернет по одному инстансу fluent-bit на каждой рабочей ноде. apiVersion: apps/v1 kind: DaemonSet metadata: name: fluent-bit namespace: monitoring labels: k8s-app: fluent-bit spec: selector: matchLabels: k8s-app: fluent-bit template: metadata: labels: k8s-app: fluent-bit spec: priorityClassName: system-node-critical containers: - name: fluent-bit image: fluent/fluent-bit:1.8 imagePullPolicy: Always volumeMounts: - name: config mountPath: /fluent-bit/etc/fluent-bit.conf subPath: fluent-bit.conf - name: config mountPath: /fluent-bit/etc/input.conf subPath: input.conf - name: config mountPath: /fluent-bit/etc/output.conf subPath: output.conf - name: config mountPath: /fluent-bit/etc/parsers.conf subPath: parsers.conf - name: config mountPath: /fluent-bit/etc/filter.conf subPath: filter.conf - name: config mountPath: /fluent-bit/etc/make_tags.lua subPath: make_tags.lua - name: var-log mountPath: /var/log - name: var-lib-fluent mountPath: /var/lib/fluent - name: var-lib-docker mountPath: /var/lib/docker readOnly: true - name: run-log mountPath: /run/log - name: etcmachineid mountPath: /etc/machine-id readOnly: true volumes: - name: config configMap: defaultMode: 420 name: fluent-bit - name: var-log hostPath: path: /var/log - name: var-lib-fluent hostPath: path: /var/lib/fluent - name: var-lib-docker hostPath: path: /var/lib/docker - name: run-log hostPath: path: /run/log - name: etcmachineid hostPath: path: /etc/machine-id type: File
  •  
    30.11.2021 , Автор: Аноним
    Ключи: kubernetes, log, clickhouse / Лицензия: CC-BY
    Раздел:    Корень / Администратору / Система / Syslog, ведение логов

    Обсуждение [ Линейный режим | Показать все | RSS ]
  • 1.1, анон (?), 01:12, 03/12/2021 [ответить] [﹢﹢﹢] [ · · · ]  
  • +/
    чем кликхаус для хранения и обработки логов лучше ES?
     
     
  • 2.2, Аноним (2), 09:39, 03/12/2021 [^] [^^] [^^^] [ответить]  
  • +/
    Быстрее, примерно в 100500 раз.
     
  • 2.4, XoRe (ok), 15:30, 03/12/2021 [^] [^^] [^^^] [ответить]  
  • +/
    Смотря какие объемы логов.
    На малых объемах логов - грамотно сделанная схема работы с clickhouse работает быстрее, чем дефолтная схема работы с ES. Это шуточка с двойным дном. Тюнить clickhouse приятнее, чем ES. Там вы рано или поздно упретесь в java с его GC. И тюнинг ES превратится в тюнинг java.

    На больших объемах логов ES умрет, а clickhouse - нет. Тем clickhouse и лучше.
    Еще из объективных плюсов - грамотная схема с clickhouse позволит логам занимать меньше места. Опять же, становится актуально на больших объемах данных.

     
  • 2.12, Vitto74 (ok), 19:43, 13/12/2021 [^] [^^] [^^^] [ответить]  
  • +/
    Разработчики сказали, что им так удобнее. Они уже использовали ES, не понравилось. Я никому не навязываю, просто делюсь опытом.


     

  • 1.3, Аноним (2), 09:47, 03/12/2021 [ответить] [﹢﹢﹢] [ · · · ]  
  • +/
    Очень неоптимальная схема, максимально нагружающая КХ в момент вставки. Вставлять в него данные рекомендуется блоками хотя бы от 100 тыс. записей, чтобы нормально отрабатывали алгоритмы сортировки в MergeTree.

    Ну и по мелочи, например, для namespace вместо String можно использовать LowCardinality(String).

     
     
  • 2.9, Vitto74 (ok), 19:29, 13/12/2021 [^] [^^] [^^^] [ответить]  
  • +1 +/
    По умолчанию fluetn-bit отправляет данные раз в 5 секунд. В документации clickhouse рекомендуется писать в базу не чаще раза в секунду. Действующая схема оптимальна для применения в моём случае, но нет универсальной схемы, которая подойдет всем. В статье я хотел поделиться опытом, который поможет адаптировать fluent-bit для других ситуаций, в которых он в принципе может быть полезен.
     

  • 1.5, Аноним (5), 16:48, 03/12/2021 [ответить] [﹢﹢﹢] [ · · · ]  
  • +/
    Основная проблема Clickhouse это отсутствие хорошей морды для просмотра логов. Во всяком так было на момент когда я пробовал. Разрабам такой вариант не понравился, CH язык запросов мало кто знает, ES был привычнее.

    Но пробовали так, делали. Правда, вместо fluent-bit мы написали своего демонюгу на Go чтобы вытаскивать логи из docker и journald. Сделали отправку батчами и оптимизировали парсеры и структуру во все щели: получались какие-то сумасшедшие цифры по производительности в миллионы строк/сек с кластера с минимальной нагрузкой на демон форвардинга. Писали напрямую с коллекторов в CH.

     
     
  • 2.6, Аноним (5), 16:49, 03/12/2021 [^] [^^] [^^^] [ответить]  
  • +/
    ЗЫ кому интересно поржать, могу выложить код схемы и демона
     
     
  • 3.8, Deepwalker (??), 17:29, 03/12/2021 [^] [^^] [^^^] [ответить]  
  • +2 +/
    Интересно конечно, выкладывайте.
     
  • 2.11, Vitto74 (ok), 19:41, 13/12/2021 [^] [^^] [^^^] [ответить]  
  • +/
    Мы тоже сначала свой костыль написали, но на java это смотрелось не очень - не самый подходящий для этого инструмент. Поэтому поковырявшись, настроили fluent-bit. Один экземпляр занимает 5Mb памяти и около мегабайта диска,а нагрузку на cpu и диск вообще не заметили.
     
  • 2.15, Аноним (15), 22:15, 15/01/2022 [^] [^^] [^^^] [ответить]  
  • +/
    > CH язык запросов мало кто знает

    SQL-select для одной таблички мало кто знает из разработчиков, которые логи бэка смотрят? Хорошая команда.

     

  • 1.7, ФФФФФФ (?), 17:09, 03/12/2021 [ответить] [﹢﹢﹢] [ · · · ]  
  • +1 +/
    Loki? не?
     
     
  • 2.10, Vitto74 (ok), 19:31, 13/12/2021 [^] [^^] [^^^] [ответить]  
  • +/
    Не для моего кейса.
     
     
  • 3.16, Аноним (16), 02:40, 26/01/2022 [^] [^^] [^^^] [ответить]  
  • +/
    Почему, в чём основные минусы?
     

  • 1.13, Антон (??), 00:19, 23/12/2021 [ответить] [﹢﹢﹢] [ · · · ]  
  • +/
    Что используете в роли просмотрщика логов?
     
  • 1.14, Alex_K (??), 00:44, 29/12/2021 [ответить] [﹢﹢﹢] [ · · · ]  
  • +/
    Оверинжиниринг...
     
  • 1.17, specter (ok), 13:49, 04/02/2022 [ответить] [﹢﹢﹢] [ · · · ]  
  • +/
    Почему не Vector https://vector.dev/ ?
     
  • 1.18, Igor (??), 18:32, 03/06/2022 [ответить] [﹢﹢﹢] [ · · · ]  
  • +/
    Способ хороший, однако должен заметить, что приведённая конфигурация fluent-bit может терять чанки. Как я понял, пайплайн работает так: из файла читаются чанки и поднимаются в память. После успешного чтения в DB записывается новая позиция. Далее чанк обрабатывается парсерами, фильтрами и направляется в output. Всё это время чанк держится в памяти. Однако если в output отправить не удалось, то информация об этом не будет никуда сохранена. А если процесс перезапустится, так и не успев отправить этот чанк, то чанк будет потерян: перечитываться заново чанк не будет (т.к. в базе уже записана новая позиция), а сам чанк был в памяти, и после рестарта процесса не сохранился.

    Чтобы избежать потери логов, нужно настроить filesystem storage и бесконечные ретраи в output. Тогда после чтения чанк сразу будет записываться на диск, и только потом будет производиться его обработка. Если процесс рестартится, то он перечитывает все сохранённые чанки и продолжает попытку их обработки и отправки. А бесконечные ретраи нужны чтобы эти чанки не дропались после N-ого неуспешного ретрая отправки.

     
     
  • 2.19, Vitto74 (ok), 02:30, 05/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    Бесконечные попытки отправки мы делать не стали т.к. потеря логов при недоступности clickhouse - это не очень большая проблема и с таким мы смиримся легко.
    С проблемой потери чанков, не отправленных в момент перезапуска или при недоступности clickhouse, мы еще не сталкивались. Можно подробнее об этой проблеме? Или ссылку на статью, где это писано?
     

     Добавить комментарий
    Имя:
    E-Mail:
    Заголовок:
    Текст:




    Партнёры:
    PostgresPro
    Inferno Solutions
    Hosting by Hoster.ru
    Хостинг:

    Закладки на сайте
    Проследить за страницей
    Created 1996-2024 by Maxim Chirkov
    Добавить, Поддержать, Вебмастеру