The OpenNET Project / Index page

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



"Раздел полезных советов: Отправка логов из kubernetes в clickhouse"
Версия для распечатки Пред. тема | След. тема
Форум Разговоры, обсуждение новостей
Исходное сообщение [ Отслеживать ]
Заметили полезную информацию ? Пожалуйста добавьте в FAQ на WIKI.
"Раздел полезных советов: Отправка логов из 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ообщить модератору

Оглавление
Раздел полезных советов: Отправка логов из kubernetes в clickhouse, auto_tips, 03-Дек-21, 01:12  [смотреть все]
Форумы | Темы | Пред. тема | След. тема



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

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