graylog-deploy/README.uk.md
byrsapty 83a3388d41 Silence locale warnings and auto-display/delete the one-time admin password
- export LC_ALL=C.UTF-8 LANG=C.UTF-8 at the top of the script: the
  container inherits LANG=en_US.UTF-8 from pct exec's calling shell but
  never generates that locale, so every apt-get call printed "Setting
  locale failed" warnings from perl/apt-listchanges. C.UTF-8 is glibc-
  builtin, no locale-gen needed.
- The final summary now prints the generated admin user/password directly
  and deletes .admin_credentials_ONE_TIME right after, instead of just
  pointing at the file and leaving it for the operator to read and clean
  up by hand.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 22:29:31 +03:00

48 KiB
Raw Blame History

Централізований лог-сервер 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-правами на pct create/set/start/stop/exec/status/list та pveam update/list/download (root не потрібен — весь цей інструментарій побудований і протестований саме в такому обмеженому обсязі прав).
  • Storage на хості Proxmox з увімкненим content type vztmpl, де вже є (або можна завантажити) template debian-12-standard — типова назва за замовчуванням: local-btrfs. Перевірте:
    sudo pveam list local-btrfs   # замініть на назву вашого storage
    
    Якщо помилка "storage is disabled" чи "does not exist" — знайдіть правильний (sudo 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. Прочитати креденшели адміністратора

install-graylog.sh сам виводить згенерований логін/пароль адміна один раз, наприкінці успішного свіжого прогону, і одразу видаляє одноразовий файл — більше нічого робити не треба. Якщо пропустили (або прогін впав раніше цього моменту), файл усе ще лежить у /opt/graylog/.admin_credentials_ONE_TIME, поки не прочитаєте:

ssh claude-deploy@<proxmox-host> "sudo pct exec 210 -- cat /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. Перевірити, що дані реально надходять

  1. Web UI → System → Inputs: кожен input показує живий "Traffic Last Minute". Якщо залишається 0 після того, як пристрій мав щось надіслати — проблема в мережі/firewall, а не в Graylog — підтвердіть через tcpdump всередині контейнера, перш ніж чіпати налаштування Graylog:
    sudo pct exec 210 -- tcpdump -i eth0 -n udp port 514
    
  2. Web UI → Search, розширте часовий діапазон (вгорі зліва), клікніть на будь-яке повідомлення, щоб розгорнути. Якщо поля vendor / event_type заповнені — спрацювало pipeline rule. Якщо повідомлення прийшло, але ці поля порожні — воно дійшло до Graylog нормально, але жодне правило поки не розпізнає його формат — це ознака, що новому пристрою/вендору потрібне нове правило (див. "Що ще НЕ реалізовано" нижче щодо процесу).
  3. Web UI → Streams: колонка "Throughput" показує живий msg/s по кожному стріму.

8. (Опційно) Викликати реальний тестовий алерт

Див. "Перевірка тестового алерту" нижче — надішліть один із відомих критичних рядків логу через logger зсередини контейнера і подивіться, як він приходить у Discord протягом приблизно хвилини.

9. (Опційно) Налаштувати Forgejo Actions CI

Якщо ви пушите цей проєкт на інстанс Forgejo, setup-forgejo-runner.sh реєструє self-hosted Actions runner прямо всередині контейнера, тож .forgejo/workflows/deploy.yml може повторно запускати install-graylog.sh на вимогу прямо з git, замість ручного scp файлів. Див. "CI/CD через Forgejo Actions" нижче — що саме робить workflow і як його запускати; цей крок — лише одноразове налаштування runner'а.

  1. У веб-інтерфейсі Forgejo: репо → Settings → Actions → Runners → "Create new Runner", щоб отримати токен реєстрації (одноразове значення, не вгадуйте й не перевикористовуйте з іншого репо).
  2. Запустіть скрипт налаштування всередині контейнера:
    pct exec <VMID> -- env \
      FORGEJO_URL="https://ваш-forgejo-інстанс" \
      FORGEJO_RUNNER_TOKEN="<токен з кроку 1>" \
      bash /opt/graylog-deploy/setup-forgejo-runner.sh
    
  3. Додайте два секрети, які потребує workflow (репо → Settings → Actions → Secrets): GRAYLOG_ADMIN_PASSWORD та DISCORD_WEBHOOK_URL. Третє значення, secrets.GITHUB_TOKEN, створювати не треба — Forgejo видає його автоматично на кожен job, прив'язаним лише до цього репо.

Параметри

Кожен флаг має відповідник у вигляді змінної середовища (див. початок 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 не підтримує цю опцію (перевірено: --features покриває nesting/keyctl/mount/fuse/mknod/ force_rw_sys, нічого для AppArmor), тож автоматизувати це в межах звичайних прав claude-deploy (pct create/set/start/stop/exec/status/list) неможливо. Якщо зіткнетесь із цим, виконайте під root на хості:

    echo "lxc.apparmor.profile: unconfined" >> /etc/pve/lxc/<VMID>.conf
    pct reboot <VMID>
    

    а потім перезапустіть create-graylog-lxc.sh (ідемпотентний, продовжить з цього місця).

    Автоматизація цього фіксу (опційно, одноразово на кожен Proxmox-хост): запустіть bootstrap-host.sh один раз від root. Він встановлює fix-lxc-apparmor.sh у /usr/local/sbin/ (власник root, chmod 700 — недоступний для запису claude-deploy) плюс вузьке sudoers-правило, прив'язане саме до цього скрипта й діапазону VMID 200-299:

    claude-deploy ALL=(root) NOPASSWD: /usr/local/sbin/fix-lxc-apparmor.sh 2[0-9][0-9]
    

    Навмисно не ширше правило типу tee -a /etc/pve/lxc/2[0-9][0-9].conf чи sh -c '...': sudoers обмежує лише власні аргументи команди, а не вміст stdin/heredoc, тож будь-який з цих варіантів дозволив би дописати довільні рядки в конфіг будь-якого контейнера 200-299 (наприклад, lxc.mount.entry для монтування хостових шляхів усередину) — набагато ширший доступ, ніж потрібно. Саме фіксація точного рядка всередині незмінного, root-owned скрипта тримає ефект sudoers-правила таким же вузьким, як і виглядає його патерн. Після bootstrap create-graylog-lxc.sh сам викликає sudo -n fix-lxc-apparmor.sh і повертається до ручної інструкції вище лише якщо цього sudoers-правила ще немає.

    Відкликання назад: запустіть cleanup-host.sh від root, коли деплой завершено — він видаляє і sudoers-правило, і скрипт, тож підвищений грант існує лише на час активного деплою, а не постійно. create-graylog-lxc.sh коректно повертається до ручного fallback, якщо запуститься без цієї автоматизації; запускайте bootstrap-host.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:13 UTC). 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, тобто змінна оточення Docker GRAYLOG_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_idAcct-Session-Id accel-ppp, стабільний для всієї сесії
  • calling_station_id — MAC-адреса абонента
  • radius_username — логін абонента (User-Name accel-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-канал.

CI/CD через Forgejo Actions

Forgejo-репозиторій проєкту (git.zotac.keenetic.link/zotac/graylog-deploy) має вручну-запускний деплой-workflow: .forgejo/workflows/deploy.yml. Його запуск клонує репо наново й повторно запускає install-graylog.sh — той самий ідемпотентний скрипт, описаний по всьому цьому README, просто автоматизований замість ручного scp. Одноразове налаштування — setup-forgejo-runner.sh (див. крок 9 вище); цей розділ описує, що робить workflow і як його запускати щодня.

Як запускати: репо → вкладка ActionsDeploy Graylog config у лівому меню → кнопка Run workflow → підтвердити. Прогрес і логи кожного кроку з'являються одразу під списком запусків.

  • Тригер навмисно ручний (тільки workflow_dispatch, без on: push) — це репо керує продакшн-системою моніторингу, тож людина переглядає диф і сама натискає "Run workflow", а не кожен push тихо передеплоює систему.
  • Раннер живе прямо всередині контейнера Graylog (VMID 200, лейбл self-hosted:host, встановлений у /usr/local/bin/forgejo-runner, працює як systemd-сервіс). Нових SSH-ключів чи міжхостового доступу не знадобилося — workflow просто клонує репо в /tmp/graylog-deploy-ci і запускає скрипт локально, точно як це робив би оператор вручну.
  • Без actions/checkout — ця дія вимагає Node.js, якого немає (і не повинно бути) на цьому appliance-контейнері лише заради CI. Замість неї workflow робить звичайний git clone --depth 1.
  • Clone автентифікований через secrets.GITHUB_TOKEN — короткоживучий токен, який Forgejo сама створює на початок кожного запуску workflow і знищує по завершенню, прив'язаний тільки до цього репо (підтверджено офіційною документацією Forgejo: працює навіть коли репо приватне, а спроба використати його проти іншого репо повертає 404). Нічого налаштовувати — це видає платформа, а не секрет, яким керує цей проєкт. Саме це не дає clone зламатись, якщо репо пізніше зробити приватним.
  • Секрети (GRAYLOG_ADMIN_PASSWORD, DISCORD_WEBHOOK_URL) зберігаються як repo-level секрети Forgejo Actions, а не в жодному відстежуваному файлі. GRAYLOG_EXTERNAL_URI не є секретом (це публічна адреса Web UI), тож вказаний прямо у workflow.

Що ще НЕ реалізовано

  • Парсинг 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