- Two flood-detection alerts (per-source message volume, calibrated live against real traffic) grouped by gl2_remote_ip - Session correlation: accelppp_interface fallback tagging plus radius_session_id/calling_station_id/radius_username extraction, so a subscriber's full session lifecycle is searchable by one key - Replace the single combined dashboard with three focused ones (Overview & Alerts, Network Equipment, Servers & Sessions) - Propagate GRAYLOG_ROOT_TIMEZONE and IP-in-alerts fixes into the reusable install script and templates - Add a Forgejo Actions workflow (manual trigger) that re-runs install-graylog.sh on a self-hosted runner living in the container, automating the deploy step this project has done by hand all along Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
40 KiB
Централізований лог-сервер Graylog — скрипти розгортання
Розгортає Debian 12 LXC-контейнер на Proxmox VE зі стеком Docker Compose (MongoDB + OpenSearch + Graylog) для збору syslog з мережевого обладнання (Juniper, ZTE OLT, D-Link) та серверів (RADIUS, accel-ppp).
Швидкий старт
Виконайте на хості Proxmox під користувачем claude-deploy (або будь-яким
іншим користувачем з такими самими обмеженими правами sudo на pct/pveam
для VMID 200-299):
./create-graylog-lxc.sh \
--ip 10.254.254.202/24 \
--gw 10.254.254.235 \
--vlan 1254 \
--external-uri http://93.171.241.5:9000/ \
--discord-webhook "https://discord.com/api/webhooks/xxx/yyy"
Це єдина команда, яка потрібна. Вона створює контейнер, встановлює Docker, застосовує firewall (nftables: відкриті лише 9000/tcp, 514+1514/udp, 5140/udp), піднімає стек, створює Syslog inputs, імпортує pipeline rules і в кінці виводить дані адміністратора.
Обидва скрипти ідемпотентні — повторний запуск після збою (або для оновлення) продовжує з того місця, де зупинився, а не дублює роботу.
Покрокове розгортання з нуля
0. Передумови
- SSH-доступ до хоста Proxmox під користувачем з sudo-правами на
pctcreate/set/start/stop/exec/status/list таpveamupdate/list/download (root не потрібен — весь цей інструментарій побудований і протестований саме в такому обмеженому обсязі прав). - Storage на хості Proxmox з увімкненим content type
vztmpl, де вже є (або можна завантажити) templatedebian-12-standard— типова назва за замовчуванням:local-btrfs. Перевірте:
Якщо помилка "storage is disabled" чи "does not exist" — знайдіть правильний (sudo pveam list local-btrfs # замініть на назву вашого storagesudo pveam list <name>для кандидатів) і передайте через--template-storage. - Storage для кореневої файлової системи контейнера (типово:
EX-Ceph) з достатнім вільним місцем під--disk(за замовчуванням 50GB). - IP-адреса, шлюз і VLAN-тег (якщо є) мережі, де житиме контейнер — тобто та сама мережа, звідки ваші RADIUS/NAS-сервери й мережеве обладнання зможуть до нього достукатись.
- Якщо потрібен доступ до Web UI ззовні цієї мережі — вільний зовнішній
порт для DNAT на
9000/tcpконтейнера (див. примітку "доступ до Web UI ззовні" нижче — порт 9000 під час тестування виявився вже зайнятий сторонім ClickHouse, тож не покладайтесь, що якийсь конкретний порт вільний). - (Опційно) Discord webhook URL, якщо хочете отримувати критичні алерти в канал.
1. Перенести скрипти на хост Proxmox
З вашої робочої машини:
tar czf - -C /шлях/до/graylog-deploy . | ssh claude-deploy@<proxmox-host> \
"mkdir -p ~/graylog-deploy && tar xzf - -C ~/graylog-deploy && chmod +x ~/graylog-deploy/*.sh"
(Або git clone/scp теки, якщо тримаєте її в репозиторії — підійде
будь-який спосіб, що перенесе всю теку, включно з rules/, pipelines/,
streams/, alerts/.)
2. Визначитись із параметрами
Заздалегідь виберіть:
| Потрібно | Приклад | Навіщо |
|---|---|---|
| Вільний VMID у 200-299 | 210 |
або пропустіть --vmid, щоб скрипт сам обрав перший вільний |
| IP контейнера + CIDR у мережі керування | 10.254.254.220/24 |
має бути досяжним з кожного пристрою, що шле syslog |
| Шлюз у цій мережі | 10.254.254.235 |
|
| VLAN-тег (якщо міст транкований) | 1254 |
пропустіть --vlan, якщо без тегу |
| Публічна URL для Web UI | http://<публічний-ip>:<порт>/ |
використовується і в конфігу Graylog, і в посиланнях Discord-алертів |
| Discord webhook (опційно) | https://discord.com/api/webhooks/.../... |
не вказуйте, щоб пропустити Discord повністю |
Спершу перевірте, що IP реально вільний — дубльований IP у цій мережі
непомітно спричиняє ARP flapping і плутану, важко діагностовану
мережеву поведінку (саме так сталось під час першого розгортання: .201
виявився вже зайнятим, і трафік випадково потрапляв не на той хост).
Швидка перевірка без жодних змін:
ssh claude-deploy@<proxmox-host> "sudo /usr/sbin/pct exec <будь-який-запущений-vmid> -- ping -c2 -W1 <кандидат-ip>"
Якщо є відповіді — ця IP зайнята, оберіть іншу.
3. Запустити create-graylog-lxc.sh
ssh claude-deploy@<proxmox-host>
cd ~/graylog-deploy
./create-graylog-lxc.sh \
--vmid 210 \
--ip 10.254.254.220/24 \
--gw 10.254.254.235 \
--vlan 1254 \
--external-uri http://<публічний-ip>:<порт>/ \
--discord-webhook "https://discord.com/api/webhooks/xxx/yyy"
Що відбувається по черзі: перевірка/завантаження template → pct create
→ старт контейнера → очікування мережі → копіювання всієї цієї теки в
контейнер за шляхом /opt/graylog-deploy/ → запуск install-graylog.sh
всередині нього, який встановлює Docker, застосовує firewall, піднімає
MongoDB/OpenSearch/Graylog, чекає, поки стек стане healthy, створює Syslog
inputs, імпортує pipeline rules/pipelines/streams, і (якщо переданий
webhook) створює Discord-notification та три alert definitions.
Якщо скрипт зупиняється з помилкою AppArmor (open sysctl net.ipv4.ip_unprivileged_port_start: permission denied) — це очікувано на
деяких Proxmox-налаштуваннях і потребує одного ручного кроку від root на
хості — див. "Docker-in-unprivileged-LXC AppArmor block" нижче. Зробіть
це, а потім просто запустіть ту саму команду ще раз; усе вже зроблене
автоматично пропускається.
Загальний час чистого прогону: кілька хвилин, здебільшого очікування завантаження образів і поки Graylog повідомить healthy.
4. Прочитати креденшели адміністратора
ssh claude-deploy@<proxmox-host> "sudo pct exec 210 -- cat /opt/graylog/.admin_credentials_ONE_TIME"
Скопіюйте пароль у менеджер паролів, потім видаліть файл:
ssh claude-deploy@<proxmox-host> "sudo pct exec 210 -- rm /opt/graylog/.admin_credentials_ONE_TIME"
Зайдіть на http://<публічний-ip>:<порт>/ під користувачем admin з цим
паролем.
5. Доступ до Web UI ззовні мережі керування (якщо потрібно)
Якщо IP контейнера не досяжна напряму звідти, звідки ви заходите в
браузер, додайте DNAT-правило на вашому edge-роутері/файрволі для
9000/tcp:
-A PREROUTING -d <публічний-ip>/32 -p tcp -m tcp --dport <порт> -j DNAT --to-destination <ip-контейнера>:9000
Оберіть порт, який справді вільний — під час першого розгортання порт
9000 виявився вже зайнятий ClickHouse на тій самій публічній IP, і
відповідь (Port 9000 is for clickhouse-client program...) якийсь час
виглядала як проблема Graylog, поки причину не знайшли. Якщо на обраному
порту приходить підозріла/неочікувана відповідь — спершу підозрюйте
вже існуючий сервіс на цьому порту, а не Graylog.
6. Направити реальне обладнання на сервер
| Джерело | Порт | Примітка |
|---|---|---|
| Мережеве обладнання (свічі, OLT, роутери) | 514/udp | стандартний syslog-порт — майже жодне обладнання не дозволяє обрати інший (logging <host> на типовому свічі завжди йде на 514) |
| Мережеве обладнання, яке може задати кастомний порт | 1514/udp | залишений як другорядний input, той самий стрім, що й 514 |
| Сервери (RADIUS, accel-ppp, conntrack/kernel-повідомлення) | 5140/udp |
На кожному пристрої це зазвичай один рядок конфігу (наприклад, на
Cisco-подібному CLI: logging <ip-контейнера>). Жодних налаштувань з боку
Graylog під кожен пристрій не потрібно — inputs і стріми вже слухають на
всіх трьох портах.
7. Перевірити, що дані реально надходять
- Web UI → System → Inputs: кожен input показує живий "Traffic Last
Minute". Якщо залишається 0 після того, як пристрій мав щось надіслати
— проблема в мережі/firewall, а не в Graylog — підтвердіть через
tcpdumpвсередині контейнера, перш ніж чіпати налаштування Graylog:sudo pct exec 210 -- tcpdump -i eth0 -n udp port 514 - Web UI → Search, розширте часовий діапазон (вгорі зліва), клікніть
на будь-яке повідомлення, щоб розгорнути. Якщо поля
vendor/event_typeзаповнені — спрацювало pipeline rule. Якщо повідомлення прийшло, але ці поля порожні — воно дійшло до Graylog нормально, але жодне правило поки не розпізнає його формат — це ознака, що новому пристрою/вендору потрібне нове правило (див. "Що ще НЕ реалізовано" нижче щодо процесу). - Web UI → Streams: колонка "Throughput" показує живий msg/s по кожному стріму.
8. (Опційно) Викликати реальний тестовий алерт
Див. "Перевірка тестового алерту" нижче — надішліть один із відомих
критичних рядків логу через logger зсередини контейнера і подивіться,
як він приходить у Discord протягом приблизно хвилини.
Параметри
Кожен флаг має відповідник у вигляді змінної середовища (див. початок
create-graylog-lxc.sh), тож можна також робити export MEMORY_MB=16384
тощо замість передачі флагів.
| Флаг | За замовчуванням | Примітка |
|---|---|---|
--ip |
(обов'язково) | Статична IP + CIDR для контейнера |
--gw |
(обов'язково) | IP шлюзу |
--external-uri |
(обов'язково) | Публічна URL-адреса Web UI Graylog (використовується в GRAYLOG_HTTP_EXTERNAL_URI) |
--vmid |
перший вільний 200-299 | |
--hostname |
graylog |
|
--cores |
4 |
|
--memory |
8192 (МБ) |
|
--swap |
512 (МБ) |
|
--disk |
50 (ГБ) |
розмір rootfs на --rootfs-storage |
--bridge |
vmbr0 |
|
--vlan |
(немає = без тегу) | |
--nameserver |
1.1.1.1 |
|
--searchdomain |
(немає) | |
--timezone |
Europe/Kyiv |
|
--template-storage |
local-btrfs |
має мати увімкнений content type vztmpl |
--rootfs-storage |
EX-Ceph |
сховище для диска контейнера |
--discord-webhook |
(немає) | передається як DISCORD_WEBHOOK_URL у скрипт всередині контейнера |
Дашборди
Замість одного комбінованого дашборду автоматично створюються три фокусовані — кожен під конкретного читача, тож відкривши Graylog, одразу бачиш релевантне, а не одну велику сторінку зі змішаними мережею, серверами й алертами:
- Overview & Alerts (Dashboards → Overview & Alerts) — стартова сторінка. Recent Alerts (останні 24г, витягнуто прямо зі стріму "All events", тож видно самі спрацьовані алерти, а не лише лічильники), Message Volume by Source за останню годину (ловить флуд візуально ще до того, як спрацюють flood-алерти), Events by Priority (24г) та Critical Events by Type (24г).
- Network Equipment (Dashboards → Network Equipment) — тільки стрім Network Equipment, 7-денне вікно: обсяг у часі по event_type, розбивка по вендору (Juniper vs. BDCOM), таблиця event type, топ пристроїв за обсягом.
- Servers & Sessions (Dashboards → Servers & Sessions) — тільки стрім Servers, 7-денне вікно: обсяг у часі по event_type, таблиця event type, топ серверів за обсягом (той самий зріз, під який каліброві flood-алерти), і розбивка RADIUS accounting статусів (Start/Alive/Stop).
Кожен побудований через Views API (пари dashboards/search_<назва>.json +
dashboards/view_<назва>.json, по одній парі на дашборд), а не через
власний конструктор віджетів Graylog у браузері — цей конструктор виявився
складно керованим надійно через браузерну автоматизацію (React
combobox-віджети, які не реагують на прості keyboard/click-події без
одночасного тригера внутрішнього React-стану), тоді як REST API прийняв ту
саму структуру чисто, щойно формат був реконструйований із JSON існуючого
дашборду. Єдина частина, що не є простим aggregation pivot — віджет Recent
Alerts, type: "messages" замість type: "aggregation" — потребувала
власної реконструкції: поле sort на рівні віджета для message-list
віджету має бути [], а не об'єктом з парою field/order, інакше
Graylog відхиляє його з помилкою Jackson-поліморфізму
(missing type id property 'type' — DTO сортування для message-віджетів
взагалі не має зареєстрованих підтипів у цій версії Graylog).
Якщо хочете додати віджет — або скористайтесь Graylog UI напряму (людина з
мишкою не натикається на проблему автоматизації), а потім за бажанням
перенесіть результат назад у ці JSON-файли, або розширте пару
search_<назва>.json/view_<назва>.json вручну — кожен віджет потребує
відповідного запису в search_types (у search_<назва>.json) та
widgets + widget_mapping + positions + titles.widget (у
view_<назва>.json) з однаковим ID. step_dashboard() в
install-graylog.sh сам підхоплює будь-яку пару
search_*.json/view_*.json (за збігом імені файлу), тож нова пара
просто має існувати в директорії dashboards/ — жодних змін у скрипті не
потрібно. Плейсхолдери __NETWORK_STREAM_ID__ та __SERVERS_STREAM_ID__
підставляються так само, як і в шаблонах алертів.
Особливості середовища, які скрипт обходить
-
Блокування Docker-в-unprivileged-LXC через AppArmor: контейнери падають з помилкою
open sysctl net.ipv4.ip_unprivileged_port_start: permission denied, якщо адміністратор хоста не додасть сирий рядок конфігурації LXC.pct setне підтримує цю опцію, тож автоматизувати це в межах правclaude-deployнеможливо. Якщо зіткнетесь із цим, виконайте під root на хості:echo "lxc.apparmor.profile: unconfined" >> /etc/pve/lxc/<VMID>.conf pct reboot <VMID>а потім перезапустіть
create-graylog-lxc.sh(ідемпотентний, продовжить з цього місця). -
vm.max_map_count: OpenSearch вимагає >= 262144. Це загальносистемний параметр ядра хоста, не прив'язаний до конкретного LXC, тож встановити його зсередини контейнера теж неможливо.install-graylog.shлише перевіряє значення і завершується з інструкціями, якщо воно замале — у цьому розгортанні воно вже було 262144 за замовчуванням, тож нічого робити не довелось. -
TLS Docker Hub через IPv6: у цій мережі шляхи IPv6 до
registry-1.docker.ioперіодично перехоплюються і повертають невідповідний сертифікат (*.docker.com). Скрипт встановлення вимикає IPv6 всередині контейнера, щоб форсувати вихід тільки через IPv4. Завантаження образів також повторюється до 5 разів, бо навіть IPv4 інколи потрапляє на проблемний вузол. -
Версія MongoDB: Graylog 7.1 вимагає MongoDB >= 7.0 (деяка застаріла документація досі згадує 6.0.x — не довіряйте кешованій документації більше, ніж тому, що фактично повідомляє запущений сервер).
-
У RFC3164 syslog немає часового поясу — Graylog за замовчуванням вважає це UTC: більшість мережевого обладнання і accel-ppp шлють класичний RFC3164 syslog (
Jul 22 09:15:13, без року, без зсуву). Без явного налаштуванняtimezoneна input Graylog зберігає цей "голий" час так, ніби він уже в UTC — тож пристрій, що пише за київським часом (UTC+3), в Graylog виглядає так, ніби події відбулись на 3 години в майбутньому. Перевірено наживо: тестовий пакет ізJul 22 09:15:13зберігався як09:15:13Z(неправильно), поки в конфігурації кожного Syslog UDP input не виставилиtimezoneна часовий пояс самого контейнера (Europe/Kyiv); після цього він коректно зберігається як06:15:13Z(09:15:13за Києвом =06:15:13UTC).step_inputs()уinstall-graylog.shвиставляє це автоматично з/etc/timezoneдля кожного input, який створює, і сам виправляє це на вже існуючих input, створених до цього фіксу. -
Часовий пояс відображення для вбудованого користувача
admin— це окреме налаштування, не те саме, що фікс вище: навіть після фіксу RFC3164 веб-інтерфейс міг далі показувати час в UTC для вбудованого read-only акаунтаadmin. Часовий пояс цього акаунта не можна змінити черезPUT /api/users/admin(перевірено наживо — падає з"state should be: hexString has 24 characters", оскільки цей ендпоінт не призначений для спеціального вбудованого акаунта). Він налаштовується лише на рівні сервера — опціяroot_timezone, тобто змінна оточення DockerGRAYLOG_ROOT_TIMEZONE.step_compose_files()виставляє її з/etc/timezoneдля нових інсталяцій і сам додає її в уже існуючий.env, створений до цього фіксу;docker-compose.ymlпрокидає її в сервісgraylog. Повторний запускdocker compose up -dпідхоплює зміну і сам перестворює контейнер. -
Retention індексів звужений навмисно: фабричний дефолт Graylog 7.1 зберігає 30-40 днів даних у до 20 індексах — прийнятно загалом, але ризиковано на малому диску (це розгортання: 50GB) у поєднанні з неперевіреним реальним обсягом логів (деякі джерела, наприклад accel-ppp на debug-рівні, можуть бути дуже "балакучими"). Звужено до вікна 14-21 день / 15 індексів для більшого запасу безпеки. Перегляньте це рішення, коли назбирається кілька тижнів реального продакшн-обсягу.
-
Мережеве обладнання шле syslog на порт 514, а не на кастомний порт: більшість комутаторів/OLT (перевірено наживо на BDCOM S5612) підтримують лише
logging <host>, що завжди використовує стандартний UDP/514, без можливості вказати інший порт. Тому створено два input для "мережевого обладнання" — 514 (те, що реально використовують пристрої) і 1514 (залишений для обладнання, яке може слати на кастомний порт), і обидва ведуть у той самий стрім "Network Equipment" (matching_type: OR). Якщо додасте нове обладнання, і воно не з'являється — перевіртеtcpdump -i eth0 udp port 514всередині контейнера, перш ніж припускати, що проблема в pipeline rules. -
nftables ніколи не повинен робити
flush ruleset: рання версія цього кроку firewall використовувалаflush ruleset, що також знищує власні таблиці Docker у iptables-nft (DOCKER,DOCKER-USERтощо), ламаючи публікацію портів контейнера при наступномуdocker compose up(перевірено наживо — довелось відновлювати через повнийdocker compose down && up).nftables.confтут робить лишеadd table inet filter+flush table inet filter, що торкається лише цієї однієї таблиці і безпечне незалежно від порядку запуску відносноdocker.service.
Алерти та Discord-нотифікації
Шість алертів працюють одразу з коробки (все інше навмисно мовчить — рутинні auth-fail, поодинокі розриви сесій тощо парсяться й доступні для пошуку, але нікого не турбують сповіщенням):
| Алерт | Спрацьовує на | Пріоритет |
|---|---|---|
| RADIUS server unreachable | radius: server(N) not responding або radius: no available servers (перевірені рядки з вихідного коду accel-ppp, radius/req.c) |
High |
| conntrack table full (packet loss) | nf_conntrack: table full, dropping packet (стандартне повідомлення ядра Linux — активна втрата пакетів прямо зараз) |
High |
| Unrecognized critical-severity syslog | Будь-яке повідомлення (будь-який вендор, будь-який стрім) із syslog-severity Emergency/Alert/Critical (0-2) за RFC5424/3164, яке не класифікувало жодне спеціальне правило | Medium |
| Juniper chassis hardware alarm | CHASSISD_SNMP_TRAP/CHASSISD_SNMP_TRAP6 (перегрів, вентилятор, блок живлення тощо) — перевірено наживо на реальному шасі VC.MYRONIVKA |
High |
| Аномальний обсяг повідомлень від одного сервера | Один сервер у стрімі Servers шле понад 150 000 повідомлень за 10-хвилинне вікно — див. "Алерти на обсяг (flood)" нижче | Medium |
| Аномальний обсяг syslog від мережевого обладнання | Один пристрій у стрімі Network Equipment шле понад 500 повідомлень за 5-хвилинне вікно — див. "Алерти на обсяг (flood)" нижче | Medium |
Третій алерт і є тим самим "універсальним" покриттям проблем мережевого обладнання: він не залежить від знання формату повідомлень конкретного вендора — лише від стандартного рівня severity syslog, який шле будь-який притомний пристрій.
Алерти на обсяг (flood)
Останні два алерти захищають від ситуації, коли одне джерело тихо забиває
диск у вікні retention — цикл у логах, шторм ретраїв, чи випадково
залишений debug-рівень. Вони групуються за gl2_remote_ip
(aggregation-v1, count() > поріг), тож кожне джерело порівнюється зі
своїм власним обсягом, а не із сумою по всьому стріму.
Пороги не вигадані — вони каліброві наживо 2026-07-22 на реальному
трафіку через pivot-пошук Views API, згрупований за gl2_remote_ip:
- Один активний accel-ppp/RADIUS сервер стабільно слав ~4 600-4 800 повідомлень за 10 хвилин (~278 тис/год) за нормального навантаження. Поріг для стріму Servers (150 000/10хв) дає приблизно 3x запасу над цим.
- Один активний мережевий пристрій стабільно слав ~30-60 повідомлень за 5 хвилин (~360/год). Поріг для Network Equipment (500/5хв) дає приблизно 10x запасу над цим.
Це стартові значення на основі часткового розгортання (1 сервер + 1 пристрій були активні на момент калібрування). Переглядайте обидва пороги по мірі підключення решти запланованих ~10-15 серверів і ~10-20 комутаторів/OLT — те, що сьогодні виглядає як 3x запасу, може виявитись затісним або занадто вільним, коли буде відомий власний baseline кожного сервера. Перевірити поточний обсяг по джерелу можна будь-коли запитом на кшталт:
gl2_remote_ip:<ip>
за фіксований проміжок часу на сторінці Search, або тим самим
pivot-пошуком (групування за gl2_remote_ip, серія count()) через Views
API, якщо потрібні точні числа замість погляду на графік.
Також парситься (доступне для пошуку, але без алерту — це рутинний обсяг, а не інцидент сам по собі):
- accel-ppp: PPP authentication failed (
ppp_auth.c) - Окремий FreeRADIUS:
Auth: (n) Login OK: [user] (from client X port P)/Auth: Login incorrect: [user] (from client X port P)(типовий форматauth_log)
Кореляція сесій (абонентські сесії accel-ppp)
Кожен рядок логу accel-ppp для конкретної сесії абонента — RADIUS
Access-Request (спроба авторизації), Accounting-Request
(start/interim/stop), DHCP discover/offer/request/ack, ipoe session
create/start/finish/terminate — тепер позначається тим самим полем
accelppp_interface (ім'я інтерфейсу vlanNNNN.NNN, яке сам accel-ppp
використовує для кожного абонента). Це працює навіть для типів
повідомлень без жодних інших структурованих полів — завдяки fallback
правилу (accelppp_interface_tag), яке позначає лише ті рядки, які ще не
класифікувало жодне спеціальне правило.
Щоб побачити повний життєвий цикл сесії абонента одним запитом, шукайте:
accelppp_interface:"vlan1779.124"
відсортовано за часом (за замовчуванням). Це показує DHCP-хендшейк, RADIUS auth/accounting обмін і кінцеве завершення сесії як один хронологічний список — замість пошуку імені інтерфейсу вручну по сирому тексту.
Рядки RADIUS Access-Request та Accounting-Request додатково отримують три багатших поля кореляції, витягнуті прямо з RADIUS AVP:
radius_session_id—Acct-Session-Idaccel-ppp, стабільний для всієї сесіїcalling_station_id— MAC-адреса абонентаradius_username— логін абонента (User-Nameaccel-ppp, формат<vlan>:<qinq>у цьому розгортанні)
Це корисно, коли відправна точка — тікет з MAC-адресою чи логіном, а не ім'ям інтерфейсу, наприклад:
calling_station_id:"48:8f:5a:a4:f9:ba"
Перевірено наживо 2026-07-22 на реальному трафіку EX-NAS-1-1: одне
значення accelppp_interface коректно об'єднало DHCPv4 Ack, DHCPv4
Request, RADIUS Accounting-Response та RADIUS Accounting-Request, що
належали одній і тій самій сесії абонента.
Щоб підключити Discord, передайте --discord-webhook (або встановіть
DISCORD_WEBHOOK_URL) при запуску create-graylog-lxc.sh. Під капотом
використовується вбудований тип нотифікації Graylog Slack, спрямований
на <ваш-webhook-url>/slack — Slack-сумісний ендпоінт Discord — тож окремий
конвертер не потрібен. Шаблон повідомлення показує заголовок/опис події
плюс джерело, IP відправника і повний текст кожного повідомлення, що
спрацювало:
*${event_definition_title}*
${event_definition_description}
${event.message}
${if backlog}${foreach backlog message}• `${message.source}` (IP: ${message.fields.gl2_remote_ip}): ${message.message}
${end}${end}
Рядок ${event.message} показує автоматично згенероване Graylog резюме
події — для звичайних critical-алертів це просто дублює заголовок, а для
двох flood-алертів (групованих за gl2_remote_ip) саме тут з'являється
конкретна IP-адреса джерела і фактичне значення count(), наприклад
WARNING: ...: 93.171.243.4 - count()=278474.0.
gl2_remote_ip — поле, яке Graylog проставляє автоматично для кожного
повідомлення на основі реальної адреси відправника UDP-пакета, незалежно
від того, яке ім'я хоста заявляє сам пристрій у полі syslog source.
Пошук за IP-адресою
Кожне повідомлення можна знайти за реальною IP-адресою пристрою через те
саме поле gl2_remote_ip, у рядку пошуку на сторінці Search:
gl2_remote_ip:93.171.243.4
source:<значення> теж працює, але лише якщо пристрій сам надсилає свою
IP-адресу як ім'я хоста (деяке обладнання так робить, інше шле
налаштоване ім'я) — gl2_remote_ip надійніший, бо береться з самого
пакета, а не з даних, які надає пристрій.
Перевірка тестового алерту
# зсередини контейнера, імітуючи кожен тригер:
docker exec -it graylog-server bash
logger -n 127.0.0.1 -P 5140 -d 'radius: server(1) not responding'
logger -n 127.0.0.1 -P 5140 -d 'kernel: nf_conntrack: table full, dropping packet'
Планувальник перевіряє раз на 60с, тож повідомлення в Discord може прийти із затримкою до хвилини. Перевірте сторінку Alerts у Graylog та відповідний Discord-канал.
Що ще НЕ реалізовано
- Парсинг D-Link switch — не було зразків логів.
- ZTE OLT ONU online/offline + оптичні аларми — не було зразків логів (надані були лише BDCOM OLT-подібні CLI-логи: privilege-mode/logout/ ARP-move/config-write, які вже парсяться).
- Чистий RADIUS Access-Accept/Reject із реального розгортання — правило для окремого FreeRADIUS вище побудоване на задокументованому типовому форматі логів FreeRADIUS, а не на зразку з реальних RADIUS-серверів цієї мережі (не було SSH-доступу до них); формат добре встановлений у проєкті, але варто звірити з реальним рядком логу, коли він з'явиться.
Щоб закрити ці прогалини: дайте кілька реальних сирих рядків логів по
кожному джерелу (D-Link syslog, ZTE OLT ONU up/down + оптичні аларми) — і
відповідні rules/*.json та alert definitions можна буде додати так само,
як були побудовані наявні.
Структура файлів
create-graylog-lxc.sh # запускається на хості Proxmox
install-graylog.sh # запускається всередині контейнера (автоматично)
docker-compose.yml # опис стеку MongoDB + OpenSearch + Graylog
nftables.conf # firewall-правила, що застосовуються всередині контейнера
rules/*.json # визначення Graylog Pipeline Rule (імпортуються через API)
pipelines/*.json # визначення Graylog Pipeline, що посилаються на правила
streams/*.json # визначення Graylog Stream (маршрутизація за портом input)
Всередині контейнера все лежить у /opt/graylog/ (docker-compose.yml,
.env із секретами) та /opt/graylog-deploy/ (копія цього репозиторію,
використовується для ідемпотентних повторних запусків).
Креденшели
install-graylog.sh генерує GRAYLOG_PASSWORD_SECRET та випадковий
пароль адміністратора під час першого запуску, одноразово записуючи
пароль адміністратора у /opt/graylog/.admin_credentials_ONE_TIME
всередині контейнера — прочитайте його, збережіть у менеджері паролів,
а потім видаліть файл:
pct exec <VMID> -- cat /opt/graylog/.admin_credentials_ONE_TIME
pct exec <VMID> -- rm /opt/graylog/.admin_credentials_ONE_TIME