Одна из самых частых и понятных задач в разработке и эксплуатации — доставка логов. И дальше в статье мы с вами используем Fluent Bit для доставки логов из виртуальной машины в сервис Yandex Cloud Logging.
Шаг 1. Пишем логи в systemd
Возможно, у вас уже есть сервис, поставку чьих логов вы хотели бы настроить. Тогда переходите к шагу 2. Если же нет, то можно воспользоваться следующим кодом на Python, чтобы генерировать тестовые логи, которые впоследствии мы будем отправлять.
Итак, нам понадобится Python 3.6 и выше.
1. Заведём директорию, куда сложим все необходимые файлы:
sudo mkdir /usr/local/bin/logtest
cd /usr/local/bin/logtest
2. Добавим следующий код в файл logtest.py:
import logging
import random
import sys
import time
from systemdlogging.toolbox import check_for_systemd
from systemdlogging.toolbox import SystemdFormatter
from systemdlogging.toolbox import SystemdHandler
# Следующие несколько строк могут быть заменены на вызов `systemdlogging.toolbox.init_systemd_logging`
# Но я приведу этот код полностью в целях демонстрации.
# Проверяем доступен ли systemd
systemd_ok = check_for_systemd()
if systemd_ok:
handler = SystemdHandler()
# syslog_id: значение которое будет использовано в поле SYSLOG_IDENTIFIER.
handler.syslog_id = ''
handler.setFormatter(SystemdFormatter())
# получаем инстанс объекта логгера
logger = logging.getLogger()
logger.addHandler(handler)
else:
# Ветка будет использована для локальной отладки, если у вас нет systemd (MacOS, Windows)
logger = logging.getLogger(__name__)
# Выводить логи будем в STDOUT
handler = logging.StreamHandler(stream=sys.stdout)
handler.setFormatter(logging.Formatter(
'[%(levelname)s] %(code)d %(message)s'
))
logger.addHandler(handler)
# Опционально можно настроить уровень логирования по умолчанию
logger.setLevel(logging.DEBUG)
# Мы могли бы обойтись и простым логированием случайных чисел, но я решил генерировать URL-подобные значения.
PATHS = [
'/',
'/admin',
'/hello',
'/docs',
]
PARAMS = [
'foo',
'bar',
'query',
'search',
None
]
def fake_url():
path = random.choice(PATHS)
param = random.choice(PARAMS)
if param:
val = random.randint(0, 100)
param += '=%s' % val
code = random.choices([200, 400, 404, 500], weights=[10, 2, 2, 1])[0]
return '?'.join(filter(None, [path, param])), code
if __name__ == '__main__':
while True:
# создаем пару код и значение URL
path, code = fake_url()
# Если код 200, то пишем в лог с уровнем Info
# Cloud Logging ориентируется на текстовое написание уровня логирования, поэтому в `context` передадим
# дополнительное значение `SEVERITY`. Оно попадет в journald и сможет быть прочитано в плагине yc-logging.
# В продуктовом коде это стоит вынести в `SystemdHandler`, чтобы не потерять,
# но в примере я оставлю так для наглядности.
if code == 200:
logger.info(
'Path: %s',
path,
extra={"code": code, "context": {"SEVERITY": "info"}},
)
# Иначе с уровнем Error
else:
logger.error(
'Error: %s',
path,
extra={"code": code, "context": {"SEVERITY": "error"}},
)
# Ждем 1 секунду, чтобы излишне не засорять журнал
time.sleep(1)
3. Заведём virtualenv и установим в него все нужные зависимости:
sudo apt install python3-pip python3.8-venv
python -m venv venv
source venv/bin/activate
pip3 install systemd-logging
Теперь нам понадобится скрипт logtest.sh, который будет вызываться systemd для запуска нашего сервиса:
#!/bin/bash
SCRIPT_PATH=$(dirname "$(realpath "$0")")
. "$SCRIPT_PATH/venv/bin/activate"
python "$SCRIPT_PATH/logtest.py"
4. Создадим файл logtest.service с описанием нашего сервиса:
[Unit]
Description=Sample to show logging from a Python application to systemd
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/logtest/logtest.sh
Restart=on-abort
[Install]
WantedBy=multi-user.target
5. Этот файл может лежать в любом каталоге, который вам нравится. Я бы предложил положить его с исходным кодом на Python для этого приложения. Но чтобы systemd нашёл файл, нам нужно придерживаться соглашения о том, куда складывать unit-файлы. Не буду вдаваться в подробности о systemd (тут на целую книгу хватит, это выходит за рамки поста в блоге) — убедимся, что наш файл модуля имеет symlink в /etc/systemd/system. Для этого выполним команду:
sudo ln -s "$(pwd)/logtest.service" \
/etc/systemd/system/logtest.service
6. Теперь можно перезагрузить systemd:
sudo systemctl daemon-reload
7. Проверим, что всё ОК:
systemctl status logtest.service
Там должно быть написано, что сервис загружен, но не активен.
8. Запустим systemd:
systemctl start logtest.service
Теперь, если снова посмотреть статус, то там должно быть active (running).
Шаг 2. Ставим Fluent Bit
Но сначала проясним пару моментов.
Fluent Bit vs Fluentd
Чтобы упростить работу с логами, когда-то появился Fluentd. Он был написан на Ruby и со временем вырос в целую экосистему, которая включает и Fluent Bit. Быстрое сравнение приведено в таблице:
Как видно, для установки Fluent Bit не нужны никакие зависимости типа Ruby. И хотя к нему есть меньше плагинов, чем для Fluentd, зато он потребляет значительно меньше памяти.
Теперь разберёмся, как его поставить
1. Добавим GPG-ключ, которым подписаны пакеты в репозитории Fluent Bit.
$ wget -qO - https://packages.fluentbit.io/fluentbit.key | sudo apt-key add -
2. В Ubuntu добавим в файл /etc/apt/sources.list строчку:
deb https://packages.fluentbit.io/ubuntu/focal focal main
3. Затем обновим индексы:
sudo apt-get update
4. Теперь можно установить последнюю версию td-agent-bit:
sudo apt-get install td-agent-bit
5. После этого его остаётся только перезапустить:
sudo service td-agent-bit start
6. Что всё ОК, можно проверить командой:
sudo service td-agent-bit status
Там должно быть active (running).
Шаг 3. Подключаем плагин
Fluent Bit поддерживает плагины, написанные на Go. Это экспериментальный, но вполне рабочий API.
1. Итак, клонируем репозиторий с кодом плагина:
git clone https://github.com/yandex-cloud/fluent-bit-plugin-yandex.git
2. Для начала нам понадобится Go. Инструкция по установке тут.
3. Теперь скомпилируем библиотеку:
export fluent_bit_version=1.8.6
export plugin_version=dev
CGO_ENABLED=1 go build -buildmode=c-shared \
-o ./yc-logging.so \
-ldflags "-X main.PluginVersion=${plugin_version}" \
-ldflags "-X main.FluentBitVersion=${fluent_bit_version}"
4. Копируем полученную библиотеку:
sudo cp yc-logging.so /usr/lib/td-agent-bit/yc-logging.so
5. И регистрируем её в файле с конфигурацией плагинов /etc/td-agent-bit/plugins.conf:
[PLUGINS]
Path /usr/lib/td-agent-bit/yc-logging.so
6. Вносим изменения в конфиг самого сервиса. folder_id укажите свой. Если к ВМ привязан service account с правами писать в Yandex Cloud Logging, то authorization укажите instance-service-account. Другие варианты авторизации и значение остальных параметров можно посмотреть тут.
[INPUT]
Name systemd
Tag host.*
Systemd_Filter _SYSTEMD_UNIT=logtest.service
[OUTPUT]
Name yc-logging
Match *
resource_type logtest
folder_id b1g***
message_key MESSAGE
level_key SEVERITY
default_level WARN
authorization instance-service-account
7. Перезагружаем сервис td-agent-bit:
sudo systemctl restart td-agent-bit
8. Отлично. Теперь можно убедиться, что логи поступают:
P. S. Yandex Cloud Logging — это serverless-сервис в Yandex.Cloud. Если вам интересна экосистема Serverless-сервисов и все, что с этим связано, заходите в сообщество в Telegram, где можно обсудить serverless в целом.
Настройка работы Fluent Bit в контейнерах ещё проще. Но о ней я расскажу в следующей публикации.