Легковесный сборщик логов на примере FluentD в MicroK8s через WERF

Image for post
Image for post

Занимаясь проектом в домашнем кластере построенным на MicroK8s я столкнулся с проблемой падения приложения, если сборщик логов не доступен в данный момент.

Используя классическую связку Elasticsearch, Logstash, Kibana, приложение поставляло логи по TCP на порт 12201 в Logstash, который был развернут в другом пространстве имен (infrastructure) и был урезан по ресурсами.

Каждый раз когда Logstash умирал, приложение было какое то время недоступно и переход на UDP не дал бы существенных улучшений. Логи ценны и выбрасывать их было нельзя.

Поэтому мой выбор пал на FluentD. Это легковесный лог-брокер написанный на Ruby, который может работать на 32 MB RAM и 0.1 CPU. Сталкиваться с теми же проблемы с TCP + GLEF не хотелось, поэтому принял решение читать логи из файлов.

Если POD запущенный в kubernetes пишет свои логи в stdout, то их можно найти в /var/logs/containers. Осталось дело за малым, просто считать файлы логов и отправить их в elasticsearch.

Я буду использовать WERF для сборки и доставки образа и Docker Hub для хранения образов и GitHub Actions как runner для запуска всего pipeline.

В корне проекта создам werf.yml. Собирать буду на основе образа linux alpine. Чистый fluentD:v1.9.1–1.0 занимает в сжатом состоянии всего 15 MB. Я буду использовать elasticsearch + kibana от logz.io.

Плагин fluent-plugin-logzio нужен будет для отправки данных в logz.io.

В дальнейшем pod с FluentD будет читать логи с файловой системы node где он запущен для этого пользователь FluentD установлен root.

Теперь нужно создать простой chart для доставки кода

И в итоге получаем простую структуру helm chart.

Deployment

Выглядит совершенно обычно и я не буду добавлять возможность автоматического масштабирования PODов в этой статье. Тут нужно обратить внимание на секции volumes. Смонтирую системные каталоги, где containers это том, который содержит файлы логов контейнеров, а в /var/log буду хранить файл с данным о текущей позиции курсора чтения файлов:

Config map (.helm/templates/config.yaml)

На предыдущем шаге я указал, что хочу дать PODу возможность читать файлы логов, которые находятся на HOSTе где развернут MikroK8s.

Все мои проекты, которые пишут логи в stdout находятся в пространстве имен apps и искать файлы логов FluentD будет именно по этому паттерну.

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

Кратко про секции:

Source — источники данных

Match — output в хранилище

В дальнейшем придется немного корректировать данные, которые попадают в коллекторы к FluentD.

GitHub Action

Кластер доступен из мира, резервировать отдельный self-hosted runner дорого и не хотелось бы.

Для начала заведем репозиторий на github.com. В проекте создадим файл .github/workflows/main.yml. В нем и опишем flow работы runner.

Будет удобно если при каждом merge в master, FluentD автоматически бы доставлялся с обновленной конфигурацией в кластер. Сразу же нужно завести в настройках репозитория секреты, в которых будем хранить чувствительную информацию.

Image for post
Image for post
Image for post
Image for post

KUBE_CONFIG_BASE64_DATE _STAGE— ~/.kube/config | base64

REGISTRY_PASSWORD — пароль к нашему docker registry

REGISTRY_USER — пользователь docker registry

REGISTRY_REPOSITORY — имя репозитория

  1. Runner клонирует код;

2. Назначаем тег будущего артефакта;

3. Осуществляем вход в наш docker registry;

4. Converge (build/push/publish);

Шаг “Converge” вызывает уже заранее подготовленный метод WERF, который делает сборку артефакта, push в docker registry, deploy helm chart в kubernetes cluster.

Теперь при следующем merge в master актуальный FluentD будет развернут в кластере автоматически.

Про переменные окружения WERF можно прочитать в официальной документации

Первые проблемы с разбором текста: JSON не совсем JSON.

Когда Kubernetes захватывает stdout PODа, он добавляет к нему свою metadata, а FluentD ожидает чистый json в файлах:

Менять log_driver в K8S не подходящее решение, но всегда можно подправить фильтрацию данных в настройках FluentD.

Во время чтения строки разобьем ее на компоненты: timestamp, system, log. Stdout контейнера находится в ключе log и json лог обернут и представляет собой строку, а не объект.

Применю фильтр, что бы преобразовать json-string в объект и поднять на верхний уровень все его значения.

Результат:

Итоговый файл конфигурации

В итоге получаем корректно разобранные логи.

Image for post
Image for post
  1. Ускорилась работа приложения. Больше не тратится время на установку. соединения с коллектором.
  2. Если коллектор перезагружается, то это никак не влияет на работу приложения.
  3. Потребление ресурсов сократилось.
  4. Если logz.io будет не доступен, то FluentD будет складывать логи в RAM, а если памяти выделенной на POD не останется, то FluentD просто перестанет читать логи до высвобождения оперативной памяти, после чего просто продолжит читать с места где остановился.

Castle builder, pragmatic, software architect

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store