Сбор логов Nginx на LXC Proxmox -> Alloy — Loki — Grafana

автор: rss9020 4 мин. чтения
Интересная задача

Веб сервер на Nginx на LXC контейнере в Proxmox находится в DMZ и полностью изолирован от внутренних сервисов. Необходимо собрать с него логи и передать их на внутренний сервис Loki-Grafana. При этом не открывая портов во внутреннюю сеть и не снижая безопасность внутренних сервисов.

Решение

Пользуемся возможность Proxmox получить доступ к файлам внутри контейнера LXC на котором работает Nginx. Поднимаем еще один LXC контейнер для сборщика логов Alloy и Loki, и монтируем папку c логами nginx внутрь нашего нового контейнера. Alloy собирает логи с локальной (примонтированой) папки и пробрасывает их в Loki, дальше Grafana их оттуда забирает для визуализации. То есть открытие сетевых портов в зону DMZ нам вообще не нужно.

Почему такой способ безопаснее
  • не открываются входящие порты
  • Alloy читает логи локально через bind mount в режиме read only
  • компрометация nginx контейнера не даёт доступа к Loki
Устанавливаем Loki и Alloy

Небольшая ремарка: Loki и ALLOY размещаем в отельный LXC даже если у нас есть уже Prometheus, а он у нас есть.

Хотя Loki и Prometheus созданы одной компанией (Grafana Labs) и отлично работают вместе, у них принципиально разная механика работы с диском и процессором.

Почему совмещать в одном LXC — плохая идея?

  1. Борьба за оперативную память (RAM):
    • Prometheus держит актуальную базуpct метрик (TSDB) в оперативной памяти и периодически сбрасывает её на диск. Ему нужно много RAM для быстрого построения графиков.
    • Loki при получении логов от Alloy активно использует RAM для буферизации (ingester) перед отправкой кусков (chunks) в хранилище. При резком наплыве логов (например, DDOS-атака или сканирование вашего WordPress) Loki может забрать всю память и вызвать Out of Memory (OOM) Killer, который «прибьет» либо сам Loki, либо Prometheus.
  2. Дисковая подсистема (I/O):
    • Логи пишутся постоянно и хаотично. Метрики пишутся строго по таймеру сжатыми блоками. Находясь в одном контейнере, они будут постоянно конкурировать за скорость чтения/записи диска вашего Proxmox.
  3. Обновления и бэкапы:
    • Раздельные контейнеры позволяют независимо бэкапить метрики (они обычно весят мало) и логи (они растут очень быстро). Если у вас закончится место из-за логов, Prometheus продолжит жить и мониторить систему.

Установка Loki в LXC контейнер

Проверяем актуальную версию шаблона LXC

Скачиваем

pveam update && \
pveam download debian-13-standard_13.1-2_amd64.tar.zst

Создаём контейнер 1 ядро, 4гб диск, 2гб оперативной памяти, привилегированный. Для простого bind mount логов удобнее использовать privileged LXC, иначе потребуется отдельно настраивать UID/GID mapping и права доступа.

pct create 105 local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst \
--hostname loki \
--cores 1 \
--memory 2048 \
--swap 512 \
--rootfs local-lvm:4 \
--net0 name=eth0,bridge=vmbr0,ip=dhcp \
--unprivileged 0 \
--features nesting=1 \
--onboot 1

Запускаем:

pct start 105
pct enter 105

Потом внутри контейнера:

apt update && apt install -y curl wget nano

Монтируем папку c логами Nginx:

mp0: /var/lib/lxc/108/rootfs/var/log/nginx,mp=/var/log/nginx-from-dmz,ro=1

Readonly mount монтирование

Важный момент с точки зрения безопасности

ro=1 делает mount только для чтения.
Контейнер с Alloy/Loki может читать логи nginx,
но не может их изменить или удалить.

Проблемы с доступом к репозиториям из России
Вариант установки с зеркалом Yandex
apt install gpg dirmngr
mkdir -p ~/.gnupg && chmod 700 ~/.gnupg

gpg --no-default-keyring --keyring /etc/apt/keyrings/grafana.asc --keyserver hkps://keyserver.ubuntu.com --recv-keys B53AE77BADB630A683046005963FA27710458545
gpg --no-default-keyring --keyring /etc/apt/keyrings/grafana.asc --export B53AE77BADB630A683046005963FA27710458545 > /etc/apt/keyrings/grafana.gpg
chmod 644 /etc/apt/keyrings/grafana.asc

nano /etc/apt/sources.list.d/grafana-mirror.list
deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://mirror.yandex.ru/mirrors/packages.grafana.com/oss/deb/ stable main

Вариант с GitHub

Доступ к репозиторию Grafana Labs режется у российских провайдеров/CDN. Поэтому проще всего для Grafana Loki в LXC — вообще не использовать apt-репозиторий Grafana, а скачать бинарники напрямую с GitHub Releases. GitHub обычно доступен стабильнее.

Для Loki:

cd /tmp

wget https://github.com/grafana/loki/releases/download/v3.0.0/loki-linux-amd64.zip

apt install -y unzip
unzip loki-linux-amd64.zip

mv loki-linux-amd64 /usr/local/bin/loki
chmod +x /usr/local/bin/loki

Для быстрого теста:

mkdir -p /etc/loki
nano /etc/loki/config.yml

Минимальный конфиг (упрощённый локальный режим без S3):

auth_enabled: false

server:
  http_listen_port: 3100

common:
  path_prefix: /tmp/loki
  storage:
    filesystem:
      chunks_directory: /tmp/loki/chunks
      rules_directory: /tmp/loki/rules
  replication_factor: 1
  ring:
    kvstore:
      store: inmemory

schema_config:
  configs:
    - from: 2024-01-01
      store: tsdb
      object_store: filesystem
      schema: v13
      index:
        prefix: index_
        period: 24h

Чтобы работало как сервис:

nano /etc/systemd/system/loki.service
[Unit]
Description=Loki
After=network.target

[Service]
ExecStart=/usr/local/bin/loki -config.file=/etc/loki/config.yml
Restart=on-failure

[Install]
WantedBy=multi-user.target

Потом:

systemctl daemon-reload
systemctl enable --now loki

Проверка:

systemctl status loki

и:

ss -tulpn | grep 3100

http://IP_КОНТЕЙНЕРА:3100

Установка Alloy вручную

1. Скачать бинарник

Для amd64:

cd /tmp

wget https://github.com/grafana/alloy/releases/latest/download/alloy-linux-amd64.zip

2. Распаковать
apt update
apt install -y unzip

unzip alloy-linux-amd64.zip

3. Установить бинарник
chmod +x alloy-linux-amd64

mv alloy-linux-amd64 /usr/local/bin/alloy

Проверка:

alloy --version

Конфиг
mkdir -p /etc/alloy
nano /etc/alloy/config.alloy

Вставь:

 local.file_match "nginx_logs" {
  path_targets = [
    {
      __path__ = "/var/log/nginx-from-dmz/*.log",
      job      = "nginx-dmz",
      app      = "nginx",
      host     = "dmz",
    },
  ]
}

loki.source.file "nginx" {
  targets    = local.file_match.nginx_logs.targets
  forward_to = [loki.process.nginx.receiver]
}

loki.process "nginx" {

  stage.regex {
    expression = "^(?P<remote_addr>\\S+) - - \\[(?P<time>.*?)\\] \"(?P<method>\\S+) (?P<uri>.*?) (?P<protocol>.*?)\" (?P<status>\\d+) (?P<body_bytes_sent>\\d+) \"(?P<referer>.*?)\" \"(?P<user_agent>.*?)\""
  }

  stage.labels {
    values = {
      remote_addr = "",
      method      = "",
      status      = "",
      app         = "",
      host        = "",
    }
  }

  forward_to = [loki.write.default.receiver]
}

loki.write "default" {
  endpoint {
    url = "http://127.0.0.1:3100/loki/api/v1/push"
  }
}

Systemd сервис
nano /etc/systemd/system/alloy.service
[Unit]
Description=Grafana Alloy
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/alloy run /etc/alloy/config.alloy
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Запуск
systemctl daemon-reload
systemctl enable alloy
systemctl start alloy

Проверка:

journalctl -u alloy -f

NGINX Logs Dashboard

Grafana NGINX Logs Dashboard 13639

Что показывает:

  • requests/sec
  • top IP
  • top URLs
  • status codes
  • 404
  • user agents
  • heatmap
  • live logs

Очень хорошо работает с Loki.

Траблшотинг

После импорта дашборда столкнулся с проблемой: данные приходят в Grafana но дашборд пустой.

{job=»nginx-dmz»} — В Grafana Explorer показывает данные, значит проблема с дашбордом.

Проверяем переменную APP, не был выбран Data Source и не указан Label Values

Получаем структурированные логи с возможностью сортировки и фильтрации. Наслаждаемся результатом

События 444 за день

События 200 за сутки

P.S.

После тестирования и перезапусков все атки изменил схему монтировапния папок, потому что после перезапуска контейнеров периодически появлялась проблема с монтированием:

()
run_buffer: 569 Script exited with status 2
lxc_init: 1037 Failed to run lxc.hook.pre-start for container "112"
__lxc_start: 2208 Failed to initialize container "112"
TASK ERROR: startup for container '112' failed

Останавливаем оба контейнера

pct stop 108
pct stop 112

Создаём папку на хосте Proxmox

mkdir -p /mnt/shared-data

Пробрасываем её в оба контейнерa

pct set 112 -mp0 /mnt/nginx-logs,mp=/var/log/nginx-from-dmz,ro=1
pct set 108 -mp0 /mnt/nginx-logs,mp=/var/log/nginx

Включаем по очереди, с задержкой, проверяем

pct start 108
pct start 112

на PVE

ls -lha /mnt/nginx-logs

Для диагностики

pct config 112

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *