Хочу поделится опытом отправки логов 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:
Обсуждается: http://www.opennet.dev/tips/info/3196.shtml