The OpenNET Project / Index page

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



"Раздел полезных советов: Отправка логов из kubernetes в clickhouse"
Вариант для распечатки  
Пред. тема | След. тема 
Форум Разговоры, обсуждение новостей
Изначальное сообщение [ Отслеживать ]

"Раздел полезных советов: Отправка логов из kubernetes в clickhouse"  +1 +/
Сообщение от auto_tips (?), 03-Дек-21, 01:12 
Хочу поделится опытом отправки логов java-приложений, развернутых в kubernetes, в базу clickhouse. Для отправки используем fluent-bit, который настроим на отправку логов по http в формате json_stream.

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

[[https://github.com/fluent/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


URL:
Обсуждается: https://www.opennet.ru/tips/info/3196.shtml

Ответить | Правка | Cообщить модератору

Оглавление

Сообщения [Сортировка по ответам | RSS]

1. Сообщение от анон (?), 03-Дек-21, 01:12   +/
чем кликхаус для хранения и обработки логов лучше ES?
Ответить | Правка | Наверх | Cообщить модератору
Ответы: #2, #4, #12

2. Сообщение от Аноним (2), 03-Дек-21, 09:39   +/
Быстрее, примерно в 100500 раз.
Ответить | Правка | Наверх | Cообщить модератору
Родитель: #1

3. Сообщение от Аноним (2), 03-Дек-21, 09:47   +/
Очень неоптимальная схема, максимально нагружающая КХ в момент вставки. Вставлять в него данные рекомендуется блоками хотя бы от 100 тыс. записей, чтобы нормально отрабатывали алгоритмы сортировки в MergeTree.

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

Ответить | Правка | Наверх | Cообщить модератору
Ответы: #9

4. Сообщение от XoRe (ok), 03-Дек-21, 15:30   +/
Смотря какие объемы логов.
На малых объемах логов - грамотно сделанная схема работы с clickhouse работает быстрее, чем дефолтная схема работы с ES. Это шуточка с двойным дном. Тюнить clickhouse приятнее, чем ES. Там вы рано или поздно упретесь в java с его GC. И тюнинг ES превратится в тюнинг java.

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

Ответить | Правка | Наверх | Cообщить модератору
Родитель: #1

5. Сообщение от Аноним (5), 03-Дек-21, 16:48   +/
Основная проблема Clickhouse это отсутствие хорошей морды для просмотра логов. Во всяком так было на момент когда я пробовал. Разрабам такой вариант не понравился, CH язык запросов мало кто знает, ES был привычнее.

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

Ответить | Правка | Наверх | Cообщить модератору
Ответы: #6, #11, #15

6. Сообщение от Аноним (5), 03-Дек-21, 16:49   +/
ЗЫ кому интересно поржать, могу выложить код схемы и демона
Ответить | Правка | Наверх | Cообщить модератору
Родитель: #5 Ответы: #8

7. Сообщение от ФФФФФФ (?), 03-Дек-21, 17:09   +1 +/
Loki? не?
Ответить | Правка | Наверх | Cообщить модератору
Ответы: #10

8. Сообщение от Deepwalkeremail (??), 03-Дек-21, 17:29   +2 +/
Интересно конечно, выкладывайте.
Ответить | Правка | Наверх | Cообщить модератору
Родитель: #6

9. Сообщение от Vitto74 (ok), 13-Дек-21, 19:29   +1 +/
По умолчанию fluetn-bit отправляет данные раз в 5 секунд. В документации clickhouse рекомендуется писать в базу не чаще раза в секунду. Действующая схема оптимальна для применения в моём случае, но нет универсальной схемы, которая подойдет всем. В статье я хотел поделиться опытом, который поможет адаптировать fluent-bit для других ситуаций, в которых он в принципе может быть полезен.
Ответить | Правка | Наверх | Cообщить модератору
Родитель: #3

10. Сообщение от Vitto74 (ok), 13-Дек-21, 19:31   +/
Не для моего кейса.
Ответить | Правка | Наверх | Cообщить модератору
Родитель: #7 Ответы: #16

11. Сообщение от Vitto74 (ok), 13-Дек-21, 19:41   +/
Мы тоже сначала свой костыль написали, но на java это смотрелось не очень - не самый подходящий для этого инструмент. Поэтому поковырявшись, настроили fluent-bit. Один экземпляр занимает 5Mb памяти и около мегабайта диска,а нагрузку на cpu и диск вообще не заметили.
Ответить | Правка | Наверх | Cообщить модератору
Родитель: #5

12. Сообщение от Vitto74 (ok), 13-Дек-21, 19:43   +/
Разработчики сказали, что им так удобнее. Они уже использовали ES, не понравилось. Я никому не навязываю, просто делюсь опытом.


Ответить | Правка | Наверх | Cообщить модератору
Родитель: #1

13. Сообщение от Антон (??), 23-Дек-21, 00:19   +/
Что используете в роли просмотрщика логов?
Ответить | Правка | Наверх | Cообщить модератору

14. Сообщение от Alex_Kemail (??), 29-Дек-21, 00:44   +/
Оверинжиниринг...
Ответить | Правка | Наверх | Cообщить модератору

15. Сообщение от Аноним (15), 15-Янв-22, 22:15   +/
> CH язык запросов мало кто знает

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

Ответить | Правка | Наверх | Cообщить модератору
Родитель: #5

16. Сообщение от Аноним (16), 26-Янв-22, 02:40   +/
Почему, в чём основные минусы?
Ответить | Правка | Наверх | Cообщить модератору
Родитель: #10

17. Сообщение от specter (ok), 04-Фев-22, 13:49   +/
Почему не Vector https://vector.dev/ ?
Ответить | Правка | Наверх | Cообщить модератору

18. Сообщение от Igor (??), 03-Июн-22, 18:32   +/
Способ хороший, однако должен заметить, что приведённая конфигурация fluent-bit может терять чанки. Как я понял, пайплайн работает так: из файла читаются чанки и поднимаются в память. После успешного чтения в DB записывается новая позиция. Далее чанк обрабатывается парсерами, фильтрами и направляется в output. Всё это время чанк держится в памяти. Однако если в output отправить не удалось, то информация об этом не будет никуда сохранена. А если процесс перезапустится, так и не успев отправить этот чанк, то чанк будет потерян: перечитываться заново чанк не будет (т.к. в базе уже записана новая позиция), а сам чанк был в памяти, и после рестарта процесса не сохранился.

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

Ответить | Правка | Наверх | Cообщить модератору
Ответы: #19

19. Сообщение от Vitto74 (ok), 05-Июл-22, 02:30   +/
Бесконечные попытки отправки мы делать не стали т.к. потеря логов при недоступности clickhouse - это не очень большая проблема и с таким мы смиримся легко.
С проблемой потери чанков, не отправленных в момент перезапуска или при недоступности clickhouse, мы еще не сталкивались. Можно подробнее об этой проблеме? Или ссылку на статью, где это писано?
Ответить | Правка | Наверх | Cообщить модератору
Родитель: #18


Архив | Удалить

Рекомендовать для помещения в FAQ | Индекс форумов | Темы | Пред. тема | След. тема




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

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