Настройка логирования в docker при помощи Loki на s3 и Grafana
Обзор
Для начала, давайте посмотрим верхнеуровнево на то, что мы хотим сделать
Кратко о том, что происходит на схеме:
- Docker logging driver считывает логи со всех контейнеров, которые логируют что-либо в нашем докере
- Docker logging driver считывает логи из файлов логов хоста
- Docker logging driver отправляет считанные логи в Loki через его API
- За кадром loki индексирует логи и организует их хранение
- Пользователь открывает Grafana(она поднята в том же докере, где и все остальное)
- Grafana делает запросы к Loki на получение логов и визуализирует их
Перейдем к настройке всего этого добра.
Установка и настройка драйвера логирования
Чтобы в Loki появлялись логи, надо чтобы кто-то их туда отправлял. У Docker есть для этого специальный механизм - драйверы логирования. Они есть для многих сервисов, например для Splunk, Amazon Cloud Watch, ELK и многих других. Нас интересует плагин Loki docker plugin.
Устанавливаем драйвер логирования:
sudo docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions
Теперь у нас есть возможность отправлять логи в Loki. Это можно сделать двумя способами:
- Указывать специальные параметры при запуске каждого контейнера
- Принудительно отправлять все логи со всех запускаемых на данном хосте контейнеров в Loki
Я думаю, что в большинстве случаев второй вариант предпочтителен, так как требует настройки один раз и помогает собрать логи со всех, даже тех, кто этого не хочет.
Для принудительной отправки логов в Loki, необходимо отредактировать файл /etc/docker/daemon.json. Создайте его, если его нет:
{
"log-driver": "loki",
"log-opts": {
"loki-url": "http://127.0.0.1:3100/loki/api/v1/push"
}
}
Здесь все довольно просто:
log-driver указывает на то, что логирование будет осуществляться при помощи Loki
loki-url указывает адрес, по которому драйвер должен отправить логи.
После изменений необходимо перезапустить сервис докера:
sudo systemctl restart docker
С этого момента все контейнеры, которые были заново созданы, будут отправлять логи прямиком в Loki. Вот только его пока не существует.
Давайте исправим это
Настройка бакета s3 в Яндекс.Облако
Но сначала нужно получить параметры для доступа к бакету s3, в котором мы будем хранить наши логи. Я привожу инструкцию для Яндекс Облака, но примерно также можно сделать в любом другом сервисе, предоставляющим объектное хранилище.
Сначала создадим сервисный аккаунт:
- Заходим в консоль Яндекс Облако: https://cloud.yandex.ru/
- Переходим во вкладку Сервисные аккаунты
- Нажимаем кнопку Создать сервисный аккаунт
- Задаем имя
- Выбираем права: storage.editor, storage.uploader, storage.viewer
- Создаем
- Переходим в созданный сервисный аккаунт
- Нажимаем кнопку Создать ключ
- Выбираем Создать статический ключ доступа
- Сохраняем себе идентификатор ключа и секретный ключ
Теперь создаем бакет:
- В меню Все сервисы выбираем Object storage
- Нажимаем кнопку Создать бакет
- Задаем имя
- Создаем бакет
Вот и все, url вашего бакета будет вида http://storage.yandexcloud.net/ + название созданного бакета
Настройка Loki для хранения логов в s3 хранилище
Создайте папку loki в домашней директории:
mkdir ~/loki
Перейдите в созданную папку:
cd ~/loki
Создайте и отредактируйте в домашней папке файл local-config.yaml:
auth_enabled: false
server:
http_listen_port: 3100
common:
ring:
instance_addr: 127.0.0.1
kvstore:
store: memberlist
replication_factor: 1
path_prefix: /etc/loki
ingester:
wal:
dir: "/tmp/wal"
replay_memory_ceiling: 4GB
schema_config:
configs:
- from: 2023-09-12
store: boltdb-shipper
object_store: s3
schema: v11
index:
prefix: index_
period: 24h
storage_config:
boltdb_shipper:
active_index_directory: /loki/index
cache_location: /loki/index_cache
shared_store: s3
aws:
s3: s3://{Access key}:{Access key secret}@{s3 bucket url}
s3forcepathstyle: true
compactor:
working_directory: /loki/compactor
shared_store: s3
compaction_interval: 5m
Разберем все параметры
Мы не планируем использовать multi tenancy, так что смело отключаем:
auth_enabled: false
Loki будет ожидать логи и запросы от графаны на 3100 порту:
http_listen_port: 3100
IP адрес инстанса Loki:
instance_addr: 127.0.0.1
Следующая настройка позволяет повысить доставляемость логов в постоянное хранилище за счет создания двух и более реплик Loki. Нам это сейчас не нужно, но в некоторых проектах может быть полезно.
store: memberlist
Следующий фрагмент определяет настройки ingester. Это компонент внутри Loki, который отвечает за запись логов в постоянное хранилище(например, s3) и считывает их оттуда.
dir указывает директорию, в которой будут храниться логи, пока они не отправлены в постоянное хранилище
replay_memory_ceiling указывает размер логов, хранящийся локально. Если объем логов достигнет данного значения, ingester их немедленно отправит в постоянное хранилище.
ingester:
wal:
dir: "/tmp/wal"
replay_memory_ceiling: 4GB
Следующий блок определяет настройки хранения индекса.
from - начиная с какой даты будут созданы индексы
store - тип хранилища индексов. У нас будет boltdb-shipper, который позволяет использовать локальные индексы, которые периодически синхронизируются с хранилищем
object_store - тип хранилища логов. Мы настраиваем s3
schema_config:
configs:
- from: 2023-09-12
store: boltdb-shipper
object_store: s3
schema: v11
Далее идут настройки boltdb_shipper.
active_index_directory - локальная директория для хранения индексов
cache_location - локальная директория для хранения кэша индексов
shared_store - постоянное хранилище индексов
boltdb_shipper:
active_index_directory: /loki/index
cache_location: /loki/index_cache
shared_store: s3
Наконец-то, сами настройки s3. Тут все просто.
s3 - строка для доступа к бакету, где будут храниться логи
s3forcepathstyle - доступ к бакету по пути
s3: s3://{Access key}:{Access key secret}@{s3 bucket url}
s3forcepathstyle: true
Compactor оптимизирует шарды индексов для лучшей производительности
working_directory - сюда compactor будет скачивать файлы для последующей оптимизации
shared_store - постоянное хранилище индексов
compaction_interval - как часто проводить оптимизацию
compactor:
working_directory: /loki/compactor
shared_store: s3
compaction_interval: 5m
Пишем docker-compose для Loki и Grafana
Мы на финишной прямой! Осталось создать docker-compose.yml файл и запустить нашу связку Loki + Grafana.
Итак, создадим на docker-compose.yml в той же папке ~/loki:
version: "3"
networks:
loki:
services:
loki:
image: grafana/loki:2.9.0
hostname: loki
ports:
- "3100:3100"
volumes:
- ~/loki:/etc/loki
command: -config.file=/etc/loki/local-config.yaml
networks:
- loki
grafana:
environment:
- GF_PATHS_PROVISIONING=/etc/grafana/provisioning
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
entrypoint:
- sh
- -euc
- |
mkdir -p /etc/grafana/provisioning/datasources
cat <<EOF > /etc/grafana/provisioning/datasources/ds.yaml
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
orgId: 1
url: http://loki:3100
basicAuth: false
isDefault: true
version: 1
editable: false
EOF
/run.sh
image: grafana/grafana:latest
ports:
- "3200:3000"
networks:
- loki
Разумеется, рассмотрим все повнимательнее.
Для начала задаем сеть с названием loki, чтобы у нас не было проблем с доступностью сервисов друг для друга:
networks:
loki:
Далее идет блок с Loki.
image - берем стандартный docker образ grafana/loki
hostname - задаем на всякий случай hostname
ports - паблишим и маппим стандартный порт Loki
volumes - маппим нашу папку с настройками в директорию /etc/loki контейнера с Loki
command - указываем путь к файлу настройки
networks - включаем сервис loki в сеть loki
services:
loki:
image: grafana/loki:2.9.0
hostname: loki
ports:
- "3100:3100"
volumes:
- ~/loki:/etc/loki
command: -config.file=/etc/loki/local-config.yaml
networks:
- loki
Теперь к настройкам Grafana.
Задаем переменные окружения:
grafana:
environment:
- GF_PATHS_PROVISIONING=/etc/grafana/provisioning
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
Теперь при помощи entrypoint указываем сервису, что нужно создать datasource, который будет обращаться к нашему loki. Это можно будет сделать руками после запуска контейнера в интерфейсе Grafana, но лучше автоматизировать
entrypoint:
- sh
- -euc
- |
mkdir -p /etc/grafana/provisioning/datasources
cat <<EOF > /etc/grafana/provisioning/datasources/ds.yaml
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
orgId: 1
url: http://loki:3100
basicAuth: false
isDefault: true
version: 1
editable: false
EOF
/run.sh
image: grafana/grafana:latest
ports:
- "3200:3000"
networks:
- loki
Вот и все!
Запускаем docker compose:
sudo docker compose up -d --build
Ждем, когда все запустится и переходим в браузере по адресу {ip сервера}:3200.
Должна открыться Grafana. Переходим в раздел Explore, выбираем нужный контейнер и делаем запросы логов.
Что в итоге
В итоге мы получили удобную систему логов для всех контейнеров, которые когда-либо будут созданы на данном хосте. Более того, мы можем перенаправлять в Loki логи с других хостов, просто указав внешний ip в настройках драйвера логирования докера.
Особенно приятно, что все логи теперь храняться в облаке на s3, а значит никуда не пропадут, могут храниться там вечно за копейки и больше у вас никогда не забьется диск из-за большого лог файла!
Ну и конечно же больше не надо лазить по бесконечным лог файлам грепами или выполнять docker logs.