Стадия жизненного цикла модуля: Общедоступная версия

У модуля есть требования для установки

Чтение логов из всех подов кластера и направление их в Loki

apiVersion: deckhouse.io/v1alpha2
kind: ClusterLoggingConfig
metadata:
  name: all-logs
spec:
  type: KubernetesPods
  destinationRefs:
  - loki-storage
---
apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: loki-storage
spec:
  type: Loki
  loki:
    endpoint: http://loki.loki:3100

Чтобы направить логи в кластерный модуль loki, укажите d8-loki в параметре destinationRefs ClusterLoggingConfig. Дополнительные примеры можно найти в документации.

Чтение логов подов из указанного неймспейса с указанным label и перенаправление одновременно в Loki и Elasticsearch

Чтение логов подов из неймспейса whispers только с label app=booking и перенаправление одновременно в Loki и Elasticsearch:

apiVersion: deckhouse.io/v1alpha2
kind: ClusterLoggingConfig
metadata:
  name: whispers-booking-logs
spec:
  type: KubernetesPods
  kubernetesPods:
    namespaceSelector:
      labelSelector:
        matchExpressions:
        - key: "kubernetes.io/metadata.name"
          operator: In
          values: [whispers]
    labelSelector:
      matchLabels:
        app: booking
  destinationRefs:
  - loki-storage
  - es-storage
---
apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: loki-storage
spec:
  type: Loki
  loki:
    endpoint: http://loki.loki:3100
---
apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: es-storage
spec:
  type: Elasticsearch
  elasticsearch:
    endpoint: http://192.168.1.1:9200
    index: logs-%F
    auth:
      strategy: Basic
      user: elastic
      password: c2VjcmV0IC1uCg==

Создание source в неймспейсе и чтение логов всех подов в этом неймспейсе с направлением их в Loki

Следующий pipeline создает source в неймспейсе test-whispers, читает логи всех подов в этом неймспейсе и пишет их в Loki:

apiVersion: deckhouse.io/v1alpha1
kind: PodLoggingConfig
metadata:
  name: whispers-logs
  namespace: tests-whispers
spec:
  clusterDestinationRefs:
    - loki-storage
---
apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: loki-storage
spec:
  type: Loki
  loki:
    endpoint: http://loki.loki:3100

Чтение только подов в указанном неймспейсе и с определенным label

Пример чтения только подов, имеющих label app=booking, в неймспейсе test-whispers:

apiVersion: deckhouse.io/v1alpha1
kind: PodLoggingConfig
metadata:
  name: whispers-logs
  namespace: tests-whispers
spec:
  labelSelector:
    matchLabels:
      app: booking
  clusterDestinationRefs:
    - loki-storage
---
apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: loki-storage
spec:
  type: Loki
  loki:
    endpoint: http://loki.loki:3100

Переход с Promtail на Log-Shipper

В ранее используемом URL Loki требуется убрать путь /loki/api/v1/push.

Vector сам добавит этот путь при работе с Loki.

Работа с Grafana Cloud

Данная документация подразумевает, что у Вас уже создан ключ API.

Для начала Вам потребуется закодировать в base64 Ваш токен доступа к Grafana Cloud.

Grafana cloud API key

echo -n "<YOUR-GRAFANACLOUD-TOKEN>" | base64 -w0

Затем нужно создать ClusterLogDestination

apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: loki-storage
spec:
  loki:
    auth:
      password: PFlPVVItR1JBRkFOQUNMT1VELVRPS0VOPg==
      strategy: Basic
      user: "<YOUR-GRAFANACLOUD-USER>"
    endpoint: <YOUR-GRAFANACLOUD-URL> # Например https://logs-prod-us-central1.grafana.net или https://logs-prod-eu-west-0.grafana.net
  type: Loki

Теперь можно создать PodLogginConfig или ClusterPodLoggingConfig и отправлять логи в Grafana Cloud.

Добавление Loki в Deckhouse Grafana

Вы можете работать с Loki из встроенной в Deckhouse Grafana. Достаточно добавить GrafanaAdditionalDatasource.

apiVersion: deckhouse.io/v1
kind: GrafanaAdditionalDatasource
metadata:
  name: loki
spec:
  access: Proxy
  basicAuth: false
  jsonData:
    maxLines: 5000
    timeInterval: 30s
  type: loki
  url: http://loki.loki:3100

Поддержка Elasticsearch < 6.X

Для Elasticsearch < 6.0 нужно включить поддержку doc_type индексов. Сделать это можно следующим образом:

apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: es-storage
spec:
  type: Elasticsearch
  elasticsearch:
    endpoint: http://192.168.1.1:9200
    docType: "myDocType" # Укажите значение здесь. Оно не должно начинаться с '_'.
    auth:
      strategy: Basic
      user: elastic
      password: c2VjcmV0IC1uCg==

Шаблон индекса для Elasticsearch

Существует возможность отправлять сообщения в определенные индексы на основе метаданных с помощью шаблонов индексов:

apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: es-storage
spec:
  type: Elasticsearch
  elasticsearch:
    endpoint: http://192.168.1.1:9200
    index: "k8s-{{ namespace }}-%F"

В приведенном выше примере для каждого неймспейса Kubernetes будет создан свой индекс в Elasticsearch.

Эта функция также хорошо работает в комбинации с extraLabels:

apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: es-storage
spec:
  type: Elasticsearch
  elasticsearch:
    endpoint: http://192.168.1.1:9200
    index: "k8s-{{ service }}-{{ namespace }}-%F"
  extraLabels:
    service: "{{ service_name }}"

В результате выполняются следующие изменения:

  1. Если сообщение имеет формат JSON, поле service_name этого документа JSON перемещается на уровень метаданных.
  2. Новое поле метаданных service используется в шаблоне индекса.

Пример интеграции со Splunk

Существует возможность отсылать события из Deckhouse в Splunk. Для настройки Splunk выполните следующие действия:

  1. Endpoint должен быть таким же, как имя Вашего экземпляра Splunk с портом 8088 и без указания пути, например https://prd-p-xxxxxx.splunkcloud.com:8088.
  2. Чтобы добавить token для доступа, откройте пункт меню Setting -> Data inputs, добавьте новый HTTP Event Collector и скопируйте token.
  3. Укажите индекс Splunk для хранения логов, например logs.
apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: splunk
spec:
  type: Splunk
  splunk:
    endpoint: https://prd-p-xxxxxx.splunkcloud.com:8088
    token: xxxx-xxxx-xxxx
    index: logs
    tls:
      verifyCertificate: false
      verifyHostname: false

destination не поддерживает лейблы пода для индексирования. Рассмотрите возможность добавления нужных лейблов с помощью опции extraLabels.

extraLabels:
  pod_label_app: '{{ pod_labels.app }}'

Простой пример Logstash

Чтобы отправлять логи в Logstash, на стороне Logstash должен быть настроен входящий поток tcp и его кодек должен быть json.

Пример минимальной конфигурации Logstash:

input {
  tcp {
    port => 12345
    codec => json
  }
}
output {
  stdout { codec => json }
}

Пример манифеста ClusterLogDestination:

apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: logstash
spec:
  type: Logstash
  logstash:
    endpoint: logstash.default:12345

Syslog

Следующий пример показывает, как отправлять сообщения через сокет по протоколу TCP в формате syslog:

apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: rsyslog
spec:
  type: Socket
  socket:
    mode: TCP
    address: 192.168.0.1:3000
    encoding: 
      codec: Syslog
  extraLabels:
    syslog.severity: "alert"
    # Поле request_id должно присутствовать в сообщении.
    syslog.message_id: "{{ request_id }}"

Пример интеграции с Graylog

Убедитесь, что в Graylog настроен входящий поток для приема сообщений по протоколу TCP на указанном порту. Пример манифеста для интеграции с Graylog:

apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: test-socket2-dest
spec:
  type: Socket
  socket:
    address: graylog.svc.cluster.local:9200
    mode: TCP
    encoding:
      codec: GELF

Логи в CEF формате

Существует способ формировать логи в формате CEF, используя codec: CEF, с переопределением cef.name и cef.severity по значениям из поля message (лога приложения) в формате JSON.

В примере ниже app и log_level это ключи содержащие значения для переопределения:

apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: siem-kafka
spec:
  extraLabels:
    cef.name: '{{ app }}'
    cef.severity: '{{ log_level }}'
  type: Kafka
  kafka:
    bootstrapServers:
      - my-cluster-kafka-brokers.kafka:9092
    encoding:
      codec: CEF
    tls:
      verifyCertificate: false
      verifyHostname: true
    topic: logs

Так же можно вручную задать свои значения:

extraLabels:
  cef.name: 'TestName'
  cef.severity: '1'

Сбор событий Kubernetes

События Kubernetes могут быть собраны log-shipper’ом, если events-exporter включен в настройках модуля extended-monitoring.

Включите events-exporter, изменив параметры модуля extended-monitoring:

apiVersion: deckhouse.io/v1alpha1
kind: ModuleConfig
metadata:
  name: extended-monitoring
spec:
  version: 1
  settings:
    events:
      exporterEnabled: true

Выложите в кластер следующий ClusterLoggingConfig, чтобы собирать сообщения с пода events-exporter:

apiVersion: deckhouse.io/v1alpha2
kind: ClusterLoggingConfig
metadata:
  name: kubernetes-events
spec:
  type: KubernetesPods
  kubernetesPods:
    labelSelector:
      matchLabels:
        app: events-exporter
    namespaceSelector:
      labelSelector:
        matchExpressions:
        - key: "kubernetes.io/metadata.name"
          operator: In
          values: [d8-monitoring]
  destinationRefs:
  - loki-storage

Фильтрация логов

Логи можно фильтровать с помощью следующих фильтров:

  • labelFilter — применяется к метаданным, например имени контейнера (container), неймспейсу (namespace) или имени пода (pod_name);
  • logFilter — применяется к полям самого сообщения, если оно в JSON-формате.

Сборка логов только для контейнера nginx

apiVersion: deckhouse.io/v1alpha2
kind: ClusterLoggingConfig
metadata:
  name: nginx-logs
spec:
  type: KubernetesPods
  labelFilter:
  - field: container
    operator: In
    values: [nginx]
  destinationRefs:
  - loki-storage

Сборка логов без строки, содержащей GET /status" 200

apiVersion: deckhouse.io/v1alpha2
kind: ClusterLoggingConfig
metadata:
  name: all-logs
spec:
  type: KubernetesPods
  destinationRefs:
  - loki-storage
  labelFilter:
  - field: message
    operator: NotRegex
    values:
    - .*GET /status" 200$

Аудит событий kubelet’а

apiVersion: deckhouse.io/v1alpha2
kind: ClusterLoggingConfig
metadata:
  name: kubelet-audit-logs
spec:
  type: File
  file:
    include:
    - /var/log/kube-audit/audit.log
  logFilter:
  - field: userAgent  
    operator: Regex
    values: ["kubelet.*"]
  destinationRefs:
  - loki-storage

Системные логи Deckhouse

apiVersion: deckhouse.io/v1alpha2
kind: ClusterLoggingConfig
metadata:
  name: system-logs
spec:
  type: File
  file:
    include:
    - /var/log/syslog
  labelFilter:
  - field: message
    operator: Regex
    values:
    - .*d8-kubelet-forker.*
    - .*containerd.*
    - .*bashible.*
    - .*kernel.*
  destinationRefs:
  - loki-storage

Если Вам нужны только логи одного пода или малой группы подов, постарайтесь использовать настройки kubernetesPods, чтобы сузить количество читаемых файлов. Фильтры необходимы только для высокогранулярной настройки.

Настройка равномерности чтения при множестве активно растущих файлов

apiVersion: deckhouse.io/v1alpha2
kind: ClusterLoggingConfig
metadata:
  name: many-growing-files
spec:
  type: File
  file:
    include:
    - /var/log/myapp/*.log
    maxReadBytes: 65536
  destinationRefs:
  - loki-storage

Параметр maxReadBytes ограничивает количество байт, считываемых из одного файла за один проход, прежде чем Vector перейдёт к следующему подходящему файлу. Уменьшите значение, если несколько файлов растут быстро одновременно и чтение нужно распределить между ними равномернее. Большие значения ускоряют вычитывание накопившегося отставания, но увеличивают потребление памяти. Если параметр не задан, применяется встроенное значение Vector (2048 байт).

Тот же параметр доступен для источника kubernetesPods (spec.kubernetesPods.maxReadBytes в ClusterLoggingConfig) и для PodLoggingConfig (spec.maxReadBytes). Для логов подов oldestFirst по умолчанию равен true: более новые файлы не читаются, пока не дочитаны более старые, и maxReadBytes задаёт только размер одной порции чтения. Чтобы распределить чтение между подами, задайте также oldestFirst: false:

apiVersion: deckhouse.io/v1alpha1
kind: PodLoggingConfig
metadata:
  name: many-growing-pods
spec:
  labelSelector:
    matchLabels:
      app: chatty-app
  maxReadBytes: 65536
  oldestFirst: false
  clusterDestinationRefs:
  - loki-storage

Приоритетное чтение более старых файлов логов

apiVersion: deckhouse.io/v1alpha2
kind: ClusterLoggingConfig
metadata:
  name: drain-old-files-first
spec:
  type: File
  file:
    include:
    - /var/log/myapp/*.log
    oldestFirst: true
  destinationRefs:
  - loki-storage

Параметр oldestFirst заставляет Vector в первую очередь вычитывать самые старые из подходящих файлов, прежде чем переходить к более новым, вместо равномерного распределения пропускной способности чтения между всеми файлами. Это полезно для файлов логов с временными метками в имени, когда старые файлы должны быть полностью обработаны (и затем удалены при ротации), прежде чем начнётся чтение новых. Для источника file по умолчанию (если параметр не задан) используется значение false.

Тот же параметр доступен для источника kubernetesPods (spec.kubernetesPods.oldestFirst в ClusterLoggingConfig) и для PodLoggingConfig (spec.oldestFirst) — там по умолчанию (если параметр не задан) используется значение true, как и во встроенном источнике Vector kubernetes_logs. Установите false, чтобы вернуться к равномерному чтению по всем отслеживаемым подам:

apiVersion: deckhouse.io/v1alpha1
kind: PodLoggingConfig
metadata:
  name: many-growing-pods
spec:
  labelSelector:
    matchLabels:
      app: chatty-app
  oldestFirst: false
  clusterDestinationRefs:
  - loki-storage

Преобразование логов

В ресурсе ClusterLogDestination можно задать цепочку трансформаций по порядку. Поддерживаются следующие действия: ParseMessage, SetTimestamp, ReplaceKeys, ReplaceValue, AddLabels, DropLabels, DropEvent.

Преобразование логов в структурированный объект

Вы можете использовать трансформацию ParseMessage, чтобы преобразовать строку в поле message в структурированный объект. Для формата String задаётся регулярное выражение с именованными группами и карта setLabels. Ключи результата сопоставляются с группами через шаблоны {{ имя_группы }}. При использовании нескольких трансформаций ParseMessage, преобразование строки должно выполняться последним.

В parseMessage.targetLabel нельзя задать путь .parsed_data.

apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: string-to-json
spec:
  # ...
  transformations:
    - action: ParseMessage
      parseMessage:
        sourceFormat: String
        string:
          regex: "^(?P<msg>.*)$"
          setLabels:
            msg: "{{ msg }}"

Пример изначальной записи в логе:

/docker-entrypoint.sh: Configuration complete; ready for start up

Результат преобразования:

{... "message": { 
  "msg": "/docker-entrypoint.sh: Configuration complete; ready for start up"
  }
}

Преобразование логов в формате Klog в структурированный объект

Вы можете использовать трансформацию ParseMessage, чтобы распарсить логи в формате Klog и преобразовать их в структурированный объект.

apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: klog-to-json
spec:
  # ...
  transformations:
    - action: ParseMessage
      parseMessage:
        sourceFormat: Klog

Пример изначальной записи в логе:

I0505 17:59:40.692994   28133 klog.go:70] hello from klog

Результат преобразования:

{... "message": {
  "file":"klog.go",
  "id":28133,
  "level":"info",
  "line":70,
  "message":"hello from klog",
  "timestamp":"2025-05-05T17:59:40.692994Z"
  }
}

Преобразование логов в формате Syslog в структурированный объект

Вы можете использовать трансформацию ParseMessage, чтобы распарсить логи в формате Syslog и преобразовать их в структурированный объект.

apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: syslog-to-json
spec:
  # ...
  transformations:
    - action: ParseMessage
      parseMessage:
        sourceFormat: SysLog

Пример изначальной записи в логе:

<13>1 2020-03-13T20:45:38.119Z dynamicwireless.name non 2426 ID931 [exampleSDID@32473 iut="3" eventSource= "Application" eventID="1011"] Try to override the THX port, maybe it will reboot the neural interface!

Результат преобразования:

{... "message": {
  "appname": "non",
  "exampleSDID@32473": {
    "eventID": "1011",
    "eventSource": "Application",
    "iut": "3"
  },
  "facility": "user",
  "hostname": "dynamicwireless.name",
  "message": "Try to override the THX port, maybe it will reboot the neural interface!",
  "msgid": "ID931",
  "procid": 2426,
  "severity": "notice",
  "timestamp": "2020-03-13T20:45:38.119Z",
  "version": 1
}}

Преобразование логов в формате CLF в структурированный объект

Вы можете использовать трансформацию ParseMessage, чтобы распарсить логи в формате CLF и преобразовать их в структурированный объект.

apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: clf-to-json
spec:
  # ...
  transformations:
    - action: ParseMessage
      parseMessage:
        sourceFormat: CLF

Пример изначальной записи в логе:

127.0.0.1 bob frank [10/Oct/2000:13:55:36 -0700] \"GET /apache_pb.gif HTTP/1.0\" 200 2326

Результат преобразования:

{... "message": {
  "host": "127.0.0.1",
  "identity": "bob",
  "message": "GET /apache_pb.gif HTTP/1.0",
  "method": "GET",
  "path": "/apache_pb.gif",
  "protocol": "HTTP/1.0",
  "size": 2326,
  "status": 200,
  "timestamp": "2000-10-10T20:55:36Z",
  "user": "frank"
}}

Преобразование логов в формате logfmt в структурированный объект

Вы можете использовать трансформацию ParseMessage, чтобы распарсить логи в формате logfmt и преобразовать их в структурированный объект.

apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: logfmt-to-json
spec:
  # ...
  transformations:
    - action: ParseMessage
      parseMessage:
        sourceFormat: Logfmt

Пример изначальной записи в логе:

@timestamp=\"Sun Jan 10 16:47:39 EST 2021\" level=info msg=\"Stopping all fetchers\" tag#production=stopping_fetchers id=ConsumerFetcherManager-1382721708341 module=kafka.consumer.ConsumerFetcherManager

Результат преобразования:

{... "message": {
  "@timestamp": "Sun Jan 10 16:47:39 EST 2021",
  "id": "ConsumerFetcherManager-1382721708341",
  "level": "info",
  "module": "kafka.consumer.ConsumerFetcherManager",
  "msg": "Stopping all fetchers",
  "tag#production": "stopping_fetchers"
}}

Парсинг JSON и уменьшение вложенности

Вы можете использовать трансформацию ParseMessage, чтобы парсить записи в логах в формате JSON. С помощью параметра depth можно контролировать глубину вложенности.

apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: parse-json
spec:
  # ...
  transformations:
    - action: ParseMessage
      parseMessage:
        sourceFormat: JSON
        json:
          depth: 1

Пример изначальной записи в логе:

{"level" : { "severity": "info" },"msg" : "fetching.module.release"}

Результат преобразования:

{... "message": {
  "level" : "{ \"severity\": \"info\" }",
  "msg" : "fetching.module.release"
  }
}

Пример парсинга записей смешанных форматов в объект

При указании нескольких трансформаций ParseMessage они применяются к каждой входящей записи по порядку сверху вниз. Сначала запись проверяется первой трансформацией: если парсинг не удался, применяется следующая трансформация. При успешном парсинге содержимое поля message становится структурированным объектом, и следующие трансформации к этой записи уже не применяются. Поэтому сначала следует указывать преобразования для конкретных форматов, например JSON, а универсальное преобразование строки — последним. Если поставить преобразование строки выше, оно может успешно обработать запись, предназначенную для JSON или другого формата.

apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: parse-json
spec:
  # ...
  transformations:
  - action: ParseMessage
    parseMessage:
      sourceFormat: JSON
  - action: ParseMessage
    parseMessage:
      sourceFormat: Klog
  - action: ParseMessage
    parseMessage:
      sourceFormat: String
      string:
        regex: "^(?P<text>.*)$"
        setLabels:
          text: "{{ text }}"

Пример изначальной записи в логе:

/docker-entrypoint.sh: Configuration complete; ready for start up
{"level" : { "severity": "info" },"msg" : "fetching.module.release"}
I0505 17:59:40.692994   28133 klog.go:70] hello from klog

Результат преобразования:

{... "message": {
  "text": "/docker-entrypoint.sh: Configuration complete; ready for start up"
  }
}
{... "message": {
  "level" : "{ "severity": "info" }",
  "msg" : "fetching.module.release"
  }
}
{... "message": {
  "file":"klog.go",
  "id":28133,
  "level":"info",
  "line":70,
  "message":"hello from klog",
  "timestamp":"2025-05-05T17:59:40.692994Z"
  }
}

Время события из лога

Трансформация SetTimestamp берёт время события из поля лога и записывает его в .timestamp. Применяйте её после ParseMessage, который превращает лог в структурированный объект.

  • sourceLabel — путь к полю со временем, например .message.time или ."@timestamp".
  • format — RFC3339 (по умолчанию), RFC2822, Unix (секунды, число или строка), CLF (время из access-лога nginx/apache) или строка формата strftime.

Если поля нет или его не удаётся разобрать, в .timestamp остаётся время чтения строки лога. Формат без смещения от UTC разбирается в локальной зоне ноды.

Loki с out_of_order_action: rewrite_timestamp может сдвигать записи, которые старше последней записи в потоке.

JSON-лог со временем в поле time ({"time": "2024-01-01T10:00:00+03:00", "msg": "started"}):

apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: json-time
spec:
  # ...
  transformations:
    - action: ParseMessage
      parseMessage:
        sourceFormat: JSON
    - action: SetTimestamp
      setTimestamp:
        sourceLabel: .message.time

JSON-лог с Unix-временем в секундах ({"ts": 1704092400, "msg": "started"}):

  transformations:
    - action: ParseMessage
      parseMessage:
        sourceFormat: JSON
    - action: SetTimestamp
      setTimestamp:
        sourceLabel: .message.ts
        format: Unix

Строка access-лога nginx (10.0.0.1 - - [01/Jan/2024:10:00:00 +0300] "GET / HTTP/1.1" 200):

  transformations:
    - action: ParseMessage
      parseMessage:
        sourceFormat: String
        string:
          regex: '^(?P<remote_addr>\S+) \S+ \S+ \[(?P<time_local>[^\]]+)\] "(?P<request>[^"]*)" (?P<status>\d+)'
          setLabels:
            remote_addr: "{{ remote_addr }}"
            time_local: "{{ time_local }}"
            request: "{{ request }}"
            status: "{{ status }}"
    - action: SetTimestamp
      setTimestamp:
        sourceLabel: .message.time_local
        format: CLF

Произвольный формат ({"time": "2024-01-01 10:00:00", "msg": "started"}), разбирается в локальной зоне ноды:

  transformations:
    - action: ParseMessage
      parseMessage:
        sourceFormat: JSON
    - action: SetTimestamp
      setTimestamp:
        sourceLabel: .message.time
        format: "%Y-%m-%d %H:%M:%S"

Замена лейблов

Вы можете использовать трансформацию ReplaceKeys, чтобы заменить source на target в заданных ключах лейблов.

Перед применением трансформации ReplaceKeys к полю message или его вложенным полям необходимо преобразовать запись лога в структурированный объект с помощью трансформации ParseMessage.

apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: replace-dot
spec:
  # ...
  transformations:
    - action: ReplaceKeys
      replaceKeys:
        source: "."
        target: "_"
        labels:
          - .pod_labels

Пример изначальной записи в логе:

{"msg" : "fetching.module.release"} # Лейбл пода pod.app=test

Результат преобразования:

{... "message": {
  "msg" : "fetching.module.release"
  },
  "pod_labels": {
    "pod_app": "test"
  }
}

Замена подстрок в строковых полях

Трансформация ReplaceValue заменяет вхождения регулярного выражения source в скалярных строковых полях. В target можно задать литерал или шаблон с именованными группами {{ имя }} из source.

Если замена нужна для поля внутри структурированного message, сначала примените ParseMessage.

apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: redact-digits
spec:
  # ...
  transformations:
    - action: ReplaceValue
      replaceValue:
        source: \d+
        target: REDACTED
        labels:
          - .message

Пример изначальной записи в логе:

user id 12345 connected from 10.0.0.1

Результат преобразования:

{... "message": "user id REDACTED connected from REDACTED"
}

Добавление лейблов из литералов и шаблонов

Трансформация AddLabels добавляет поля со статическими значениями или шаблонами вида {{ .pod_labels.app }}. Трансформации выполняются после применения extraLabels. Если имена лейблов совпадают, у значения из AddLabels будет приоритет.

Поле when содержит необязательный список условий, объединяемых логическим «И». Слева в условии указывается путь к полю записи лога — строка с ведущей точкой и сегментами через точку (например .namespace, .pod_labels.app, .message.level). Каждый элемент списка может быть одним из следующих типов:

  • сравнение значения — .namespace == "production". Поддерживаются операторы ==, !=, =~ и !=~;
  • проверка наличия поля — в строке условия указывается только путь без оператора, например, .pod_labels.team (поле должно существовать);
  • проверка отсутствия поля — !.pod_labels.legacy.
apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: add-env-from-pod
spec:
  # ...
  transformations:
    - action: AddLabels
      addLabels:
        when:
          - '.namespace == "production"'
        setLabels:
          .env: prod
          .source_app: "{{ .pod_labels.app }}"

Результат преобразования для события в неймспейсе production с лейблом пода app=api:

{..."namespace": "production", "pod_labels": { "app": "api" } "env": "prod", "source_app": "api", ...
}

Удаление событий

Трансформация DropEvent удаляет событие лога, если истинны все условия из when. Поле when имеет тот же синтаксис, что и в AddLabels, условия объединяются логическим «И». Условие на отсутствующем поле ложно, поэтому событие остаётся.

Трансформация выполняется на своей позиции в списке и видит поля, добавленные предыдущими трансформациями. В отличие от logFilter и labelFilter в ClusterLoggingConfig, которые фильтруют на источнике для всех хранилищ, DropEvent действует только для того хранилища, где задан.

apiVersion: deckhouse.io/v1alpha2
kind: ClusterLogDestination
metadata:
  name: drop-user-events
spec:
  # ...
  transformations:
    - action: AddLabels
      addLabels:
        when:
          - ".message =~ 'AddUser'"
        setLabels:
          .action: adduser
    - action: AddLabels
      addLabels:
        when:
          - ".message =~ 'DelUser'"
        setLabels:
          .action: deluser
    - action: DropEvent
      dropEvent:
        when:
          - ".action"

События, в message которых есть AddUser или DelUser, получают лейбл action и удаляются; остальные события отправляются без изменений.

Удаление лейблов

Вы можете использовать трансформацию DropLabels, чтобы удалить заданные лейблы из записей логов. У каждого элемента списка dropLabels.labels есть поле label (путь) и опционально keepKeys. Если для элемента задан keepKeys, поле label должно указывать на объект; внутри него удаляются все вложенные ключи, кроме перечисленных в keepKeys (имена без ведущей точки). Если keepKeys у элемента не задан — путь из label удаляется целиком.

Перед применением трансформации DropLabels к полю message или его вложенным полям необходимо преобразовать запись лога в структурированный объект с помощью трансформации ParseMessage.

apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: drop-label
spec:
  # ...
  transformations:
    - action: DropLabels
      dropLabels:
        labels:
          - label: .example

Пример удаления заданного лейбла из структурированного сообщения

В этом примере показано, как Вы можете удалить лейбл из структурированного JSON-сообщения. Сначала применяется трансформация ParseMessage для парсинга сообщения, после чего применяется DropLabels для удаления указанного лейбла.

apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: drop-label
spec:
  # ...
  transformations:
    - action: ParseMessage
      parseMessage:
        sourceFormat: JSON
    - action: DropLabels
      dropLabels:
        labels:
          - label: .message.example

Пример изначальной записи в логе:

{"msg" : "fetching.module.release", "example": "test"}

Результат преобразования:

{... "message": {
  "msg" : "fetching.module.release"
  }
}

Отправка логов в Elasticsearch с трансформациями

Ниже приведён пример ресурса ClusterLogDestination для отправки логов в Elasticsearch.

В этом примере:

  • JSON из поля message разбирается и переносится в корень события (targetLabel: ".");
  • в именах ключей в pod_labels точки заменяются на подчёркивания;
  • исходное поле message удаляется.
apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: es-storage
spec:
  type: Elasticsearch
  elasticsearch:
    endpoint: http://elasticsearch:9200
    index: logs-%F
  transformations:
    - action: ParseMessage
      parseMessage:
        sourceFormat: JSON
        targetLabel: "."
    - action: ReplaceKeys
      replaceKeys:
        source: "."
        target: "_"
        labels:
          - .pod_labels
    - action: DropLabels
      dropLabels:
        labels:
          - label: .message

Пример исходной записи: приложение на Java пишет структурированный JSON в stdout, поэтому в поле message события попадает JSON-строка. Поле msg внутри этой строки содержит текст программного лога.

{... "namespace": "prod", "pod_labels": {"app.kubernetes.io/name": "orders", "app": "api"}, "message": "{\"@timestamp\":\"2025-03-25T10:15:00.123Z\",\"level\":\"INFO\",\"logger\":\"com.example.OrderService\",\"msg\":\"Order id=42 created; user=1001\"}"}

Результат после преобразований:

  • поля из JSON перенесены в корень события;
  • в pod_labels точки в именах ключей заменены на подчёркивания;
  • поле message удалено.
{... "namespace": "prod",
  "pod_labels": {
    "app_kubernetes_io/name": "orders",
    "app": "api"
  },
  "@timestamp": "2025-03-25T10:15:00.123Z",
  "level": "INFO",
  "logger": "com.example.OrderService",
  "msg": "Order id=42 created; user=1001"
}

Настройка сборки логов с продуктовых неймспейсов с помощью селектора меток неймспейса

apiVersion: deckhouse.io/v1alpha2
kind: ClusterLoggingConfig
metadata:
  name: production-logs
spec:
  type: KubernetesPods
  kubernetesPods:
    namespaceSelector:
      labelSelector:
        matchLabels:
          environment: production
  destinationRefs:
  - loki-storage

Исключение подов и неймспейсов, используя label

Существует преднастроенный label для исключения определенных подов и неймспейсов: log-shipper.deckhouse.io/exclude=true. Он помогает остановить сбор логов с подов и неймспейсов без изменения глобальной конфигурации.

---
apiVersion: v1
kind: Namespace
metadata:
  name: test-namespace
  labels:
    log-shipper.deckhouse.io/exclude: "true"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-deployment
spec:
  # ...
  template:
    metadata:
      labels:
        log-shipper.deckhouse.io/exclude: "true"

Включение буферизации

Настройка буферизации логов необходима для улучшения надежности и производительности системы сбора логов. Буферизация может быть полезна в следующих случаях:

  1. Временные перебои с подключением. Если есть временные перебои или нестабильность соединения с системой хранения логов (например, с Elasticsearch), буфер позволяет временно сохранять логи и отправить их, когда соединение восстановится.

  2. Сглаживание пиков нагрузки. При внезапных всплесках объёма логов буфер позволяет сгладить пиковую нагрузку на систему хранения логов, предотвращая её перегрузку и потенциальную потерю данных.

  3. Оптимизация производительности. Буферизация помогает оптимизировать производительность системы сбора логов за счёт накопления логов и отправки их группами, что снижает количество сетевых запросов и улучшает общую пропускную способность.

Пример включения буферизации в оперативной памяти

apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: loki-storage
spec:
  buffer:
    memory:
      maxEvents: 4096
    type: Memory
  type: Loki
  loki:
    endpoint: http://loki.loki:3100

Пример включения буферизации на диске

apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: loki-storage
spec:
  buffer:
    disk:
      maxSize: 1Gi
    type: Disk
  type: Loki
  loki:
    endpoint: http://loki.loki:3100

Пример определения поведения при переполнении буфера

apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: loki-storage
spec:
  buffer:
    disk:
      maxSize: 1Gi
    type: Disk
    whenFull: DropNewest
  type: Loki
  loki:
    endpoint: http://loki.loki:3100

Более подробное описание параметров доступно в ресурсе ClusterLogDestination.