Перейти к основному содержимому

Пишем логи из Managed K8s в Yandex Cloud Logging при помощи Fluentbit

· 7 мин. чтения

Если вы хотите отгружать логи из Managed Kubernetes в Yandex Cloud, для этого есть как минимум два пути:

  1. Можно установить helm из магазина приложений.
  2. Можно разобраться и гибко настроить все самому.

Тут конечно же мы рассмотрим второй способ.

Генератор логов

Для начала нам понадобится генератор логов: простое приложение, чтобы на его примере мы могли рассмотреть как указывается парсер логов.

К счастью у меня в одной из предыдущих статей про fluentbit уже написано такое приложение (шаг 1) и оно нам подойдет. Нужно собрать контейнер и запушить, например, в Yandex Cloud Container Registry.

Деплой в k8s

Надеюсь с развертыванием кластера k8s вы справитесь сами. Если что вот инструкция.

Теперь нам надо создать файл с описанием Deployment’а нашего приложения.

loggen-deployment.yaml showLineNumbers
apiVersion: apps/v1
kind: Deployment
metadata:
name: loggen-deployment
labels:
app: loggen
spec:
replicas: 3
selector:
matchLabels:
app: loggen
template:
metadata:
labels:
app: loggen
annotations:
fluentbit.io/parser: app_log_parser
yc.logging.tag: loggen
spec:
containers:
- name: loggen
image: cr.yandex/crpk105d8omff2r209qi/loggen:2022.06.13-234725c
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"

Обратите внимание на строку 22 — туда вы должны вставить урл откуда можно скачать докер образ, который вы собрали и запушили в репозиторий на шаге 1. А также на строки 17–18, где указаны аннотации, к которым мы вернемся чуть позднее.

Установка Fluentbit

Тут все просто. Для начала мы будем следовать официальной инструкции.

Тут приведен пример для Kubernetes 1.21 и ниже, так как на текущий момент только такие версии доступны в Yandex Cloud.

kubectl create namespace logging
kubectl create -f https://raw.githubusercontent.com/fluent/fluent-bit-kubernetes-logging/master/fluent-bit-service-account.yaml
kubectl create -f https://raw.githubusercontent.com/fluent/fluent-bit-kubernetes-logging/master/fluent-bit-role.yaml
kubectl create -f https://raw.githubusercontent.com/fluent/fluent-bit-kubernetes-logging/master/fluent-bit-role-binding.yaml

Если, когда вы читаете доступна старшая версия, то в официальной инструкции уже есть пример конфигов и для неё.

Дальше нужно создать ConfigMap yaml-файл.

apiVersion: v1
kind: ConfigMap
metadata:
name: fluent-bit-config
namespace: logging
labels:
k8s-app: fluent-bit
data:
# Configuration files: server, input, filters and output
# ======================================================
fluent-bit.conf: |
[SERVICE]
Flush 1
Log_Level info
Daemon off
Parsers_File parsers.conf
HTTP_Server On
HTTP_Listen 0.0.0.0
HTTP_Port 2020
@INCLUDE input-kubernetes.conf
@INCLUDE filter-kubernetes.conf
@INCLUDE output-yc.conf
input-kubernetes.conf: |
[INPUT]
Name tail
Tag kube.*
Path /var/log/containers/*.log
Parser docker
DB /var/log/flb_kube.db
Mem_Buf_Limit 5MB
Skip_Long_Lines On
Refresh_Interval 10
filter-kubernetes.conf: |
[FILTER]
Name kubernetes
Match kube.*
Kube_URL https://kubernetes.default.svc:443
Kube_CA_File /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
Kube_Token_File /var/run/secrets/kubernetes.io/serviceaccount/token
Kube_Tag_Prefix kube.var.log.containers.
Merge_Log On
Keep_Log Off
K8S-Logging.Parser On
K8S-Logging.Exclude Off
[FILTER]
Name rewrite_tag
Match kube.*
Rule $kubernetes['annotations']['yc.logging.tag'] ^(.+)$ yc.logging.$kubernetes['annotations']['yc.logging.tag'] false
Emitter_Name re_emitted
[FILTER]
Name stdout
Match yc.logging.*
output-yc.conf: |
[OUTPUT]
Name yc-logging
Match yc.logging.*
group_id e23e1gpq9futicl6sna4
resource_type {kubernetes/labels/app}
resource_id {kubernetes/pod_name}
message_key text
level_key severity
default_level WARN
authorization instance-service-account
parsers.conf: |
[PARSER]
Name apache
Format regex
Regex ^(?<host>[^ ]*) [^ ]* (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^\"]*?)(?: +\S*)?)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$
Time_Key time
Time_Format %d/%b/%Y:%H:%M:%S %z
[PARSER]
Name apache2
Format regex
Regex ^(?<host>[^ ]*) [^ ]* (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$
Time_Key time
Time_Format %d/%b/%Y:%H:%M:%S %z
[PARSER]
Name apache_error
Format regex
Regex ^\[[^ ]* (?<time>[^\]]*)\] \[(?<level>[^\]]*)\](?: \[pid (?<pid>[^\]]*)\])?( \[client (?<client>[^\]]*)\])? (?<message>.*)$
[PARSER]
Name nginx
Format regex
Regex ^(?<remote>[^ ]*) (?<host>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^\"]*?)(?: +\S*)?)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$
Time_Key time
Time_Format %d/%b/%Y:%H:%M:%S %z
[PARSER]
Name json
Format json
Time_Key time
Time_Format %d/%b/%Y:%H:%M:%S %z
[PARSER]
Name docker
Format json
Time_Key time
Time_Format %Y-%m-%dT%H:%M:%S.%L
Time_Keep On
[PARSER]
# http://rubular.com/r/tjUt3Awgg4
Name cri
Format regex
Regex ^(?<time>[^ ]+) (?<stream>stdout|stderr) (?<logtag>[^ ]*) (?<message>.*)$
Time_Key time
Time_Format %Y-%m-%dT%H:%M:%S.%L%z
[PARSER]
Name syslog
Format regex
Regex ^\<(?<pri>[0-9]+)\>(?<time>[^ ]* {1,2}[^ ]* [^ ]*) (?<host>[^ ]*) (?<ident>[a-zA-Z0-9_\/\.\-]*)(?:\[(?<pid>[0-9]+)\])?(?:[^\:]*\:)? *(?<message>.*)$
Time_Key time
Time_Format %b %d %H:%M:%S
[PARSER]
Name app_log_parser
Format regex
Regex ^\[req_id=(?<req_id>[0-9a-fA-F\-]+)\] \[(?<severity>.*)\] (?<code>\d+) (?<text>.*)$
Types code:integer

А теперь давайте разберем подробнее секции конфига.

[INPUT]
Name tail
Tag kube.*
Path /var/log/containers/*.log
Parser docker
DB /var/log/flb_kube.db
Mem_Buf_Limit 5MB
Skip_Long_Lines On
Refresh_Interval 10

В секции INPUT описывается, что Fluentbit будет при помощи инпута tail вычитывать файлы из директории /var/log/containers/, куда k8s складывает логи контейнеров бегущих на текущей ноде. Логи складываются в json-формате Docker’а, поэтому к ним будет применяться парсер docker, который описан ниже в секции парсеров.

[PARSER]
Name docker
Format json
Time_Key time
Time_Format %Y-%m-%dT%H:%M:%S.%L
Time_Keep On

Далее идет несколько настроек для оптимизации кеширования и потребления памяти при инджесте логов. Плагин Tail input не будет добавлять более 5 МБ в движок, пока они не будут сброшены в YC Logging. Это ограничение реализации back pressure.

[FILTER]
Name kubernetes
Match kube.*
Kube_URL [https://kubernetes.default.svc:443](https://kubernetes.default.svc/)
Kube_CA_File /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
Kube_Token_File /var/run/secrets/kubernetes.io/serviceaccount/token
Kube_Tag_Prefix kube.var.log.containers.
Merge_Log On
Keep_Log Off
K8S-Logging.Parser On
K8S-Logging.Exclude Off

Фильтр Kubernetes обогатит записи метаданными Kubernetes, в частности метками и аннотациями. Фильтр ходит в API k8s только в том случае, если он не может найти информацию в кэше.

Тут стоит упомянуть первую из аннотаций из шага 2 — fluentbit.io/parser — именно на основе нее при включенной опции K8S-Logging.Parser будет выбираться парсер, который будет парсит лог нашего приложения. В этом примере app_log_parser.

Теперь подробнее рассмотрим второй фильтр — rewrite_tag.

[FILTER]
Name rewrite_tag
Match kube.*
Rule $kubernetes['annotations']['yc.logging.tag'] ^(.+)$ yc.logging.$kubernetes['annotations']['yc.logging.tag'] false
Emitter_Name re_emitted

В нем мы будем использовать значение второй аннотации из шага 2 — yc.logging.tag. Этот фильтр находит логи в поле по пути kubernetes/annotations/yc.logging.tag есть данные и на основе них формирует новый тег вида yc.logging.*, который мы в свою очередь будем использовать, для того чтобы направлять записи в YC Loggging.

[FILTER]
Name stdout
Match yc.logging.*

Последний фильтр stdout удобен для отладки. Он выводит в логи самого контейнера с Fluentbit записи с тегом совпадающим с фильтром yc.logging.*. В продакшене его стоит убрать.

Вывод фильтра stdout

Теперь разберем конфиг плагина YC Logging.

[OUTPUT]
Name yc-logging
Match yc.logging.*
group_id e23*****
resource_type {kubernetes/labels/app}
resource_id {kubernetes/pod_name}
message_key text
level_key severity
default_level WARN
authorization instance-service-account

Как видно отправлять мы собираемся лишь записи с тегом yc.logging.*. Таким образом если вы хотите настроить отправку логов в разные лог-группы, то вам стоит настроить фильтры так, чтобы им присваивались разные теги.

В полях resource_type и resource_id используется подстановка значений из записи лога. Именно поэтому удобно использовать фильтр stdout, чтобы наглядно видеть какие значения можно использовать.

Вот так будут выглядеть фильтры при отображении логов в UI.

Также тут можно использовать значения из сервиса метаданных виртуальной машины, на которой бежит под. Для этого используются двойные фигурные скобки. Подробнее про конфиг и синтаксис в репозитории с кодом плагина.

DaemonSet

Ну и напоследок файл конфигурации для деплоя DaemonSet Fluentbit’а. В файле указан самый свежий на текущий момент контейнер с предустановленным плагином для Yandex Cloud Logging.

fluent-bit-ds.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluent-bit
namespace: logging
labels:
k8s-app: fluent-bit-logging
version: v1
kubernetes.io/cluster-service: "true"
spec:
selector:
matchLabels:
k8s-app: fluent-bit-logging
template:
metadata:
labels:
k8s-app: fluent-bit-logging
version: v1
kubernetes.io/cluster-service: "true"
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "2020"
prometheus.io/path: /api/v1/metrics/prometheus
spec:
containers:
- name: fluent-bit
image: cr.yandex/yc/fluent-bit-plugin-yandex:v2.0.2-fluent-bit-1.9.3
imagePullPolicy: Always
ports:
- containerPort: 2020
volumeMounts:
- name: varlog
mountPath: /var/log
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
- name: fluent-bit-config
mountPath: /fluent-bit/etc/
terminationGracePeriodSeconds: 10
volumes:
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
- name: fluent-bit-config
configMap:
name: fluent-bit-config
serviceAccountName: fluent-bit
tolerations:
- key: node-role.kubernetes.io/master
operator: Exists
effect: NoSchedule
- operator: "Exists"
effect: "NoExecute"
- operator: "Exists"
effect: "NoSchedule"

Развертывание

kubectl apply -f fluent-bit-config.yaml
kubectl apply -f fluent-bit-ds.yaml
kubectl apply -f loggen-deployment.yaml

Отлично. Теперь, если все правильно, вы увидите логи в выбранной вами лог-группе.