graylog-deploy/README.uk.md

28 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. Прочитати креденшели адміністратора

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. Перевірити, що дані реально надходять

  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 протягом приблизно хвилини.

Параметри

Кожен флаг має відповідник у вигляді змінної середовища (див. початок 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 — не довіряйте кешованій документації більше, ніж тому, що фактично повідомляє запущений сервер).

  • 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

Третій алерт і є тим самим "універсальним" покриттям проблем мережевого обладнання: він не залежить від знання формату повідомлень конкретного вендора — лише від стандартного рівня 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 — тож окремий конвертер не потрібен. Шаблон повідомлення показує заголовок/опис події плюс джерело і повний текст кожного повідомлення, що спрацювало:

*${event_definition_title}*
${event_definition_description}
${if backlog}${foreach backlog message}• `${message.source}`: ${message.message}
${end}${end}

Перевірка тестового алерту

# зсередини контейнера, імітуючи кожен тригер:
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