32 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 у скрипт всередині контейнера |
Дашборд
Дашборд "Network & RADIUS Monitoring" створюється автоматично (Dashboards → Network & RADIUS Monitoring), з п'ятьма віджетами за замовчуванням на 7-денному вікні:
- Messages Over Time by Stream — накопичувальна стовпчикова діаграма, щоб одним поглядом бачити обсяг мережевого обладнання vs. серверів
- Vendor Breakdown — кругова діаграма за полем
vendor, яке проставляють pipeline rules - Top Event Types — таблиця з підрахунком по
event_type - Critical Events by Type — те саме, але відфільтроване по
severity_tag:critical— тобто саме те, чим переймаються три алерти вище - Top Sources — які пристрої/сервери генерують найбільше обсягу
Побудований через Views API (dashboards/search.json + dashboards/view.json),
а не через власний конструктор віджетів Graylog у браузері — цей
конструктор виявився складно керованим надійно через браузерну
автоматизацію (React combobox-віджети, які не реагують на прості
keyboard/click-події без одночасного тригера внутрішнього React-стану),
тоді як REST API прийняв ту саму структуру чисто з першої спроби, щойно
формат був реконструйований із JSON існуючого дашборду. Якщо хочете додати
віджет — або скористайтесь Graylog UI напряму (людина з мишкою не
натикається на проблему автоматизації), а потім за бажанням перенесіть
результат назад у ці два JSON-файли, або розширте
dashboards/search.json/view.json вручну — кожен віджет потребує
відповідного запису в search_types (у search.json) та
widgets + widget_mapping + positions + titles.widget (у
view.json) з однаковим 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 |
Третій алерт і є тим самим "універсальним" покриттям проблем мережевого обладнання: він не залежить від знання формату повідомлень конкретного вендора — лише від стандартного рівня severity syslog, який шле будь-який притомний пристрій.
Також парситься (доступне для пошуку, але без алерту — це рутинний обсяг, а не інцидент сам по собі):
- 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)
Щоб підключити Discord, передайте --discord-webhook (або встановіть
DISCORD_WEBHOOK_URL) при запуску create-graylog-lxc.sh. Під капотом
використовується вбудований тип нотифікації Graylog Slack, спрямований
на <ваш-webhook-url>/slack — Slack-сумісний ендпоінт Discord — тож окремий
конвертер не потрібен. Шаблон повідомлення показує заголовок/опис події
плюс джерело, IP відправника і повний текст кожного повідомлення, що
спрацювало:
*${event_definition_title}*
${event_definition_description}
${if backlog}${foreach backlog message}• `${message.source}` (IP: ${message.fields.gl2_remote_ip}): ${message.message}
${end}${end}
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