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