graylog-deploy/README.uk.md

400 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Централізований лог-сервер 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):
```bash
./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`. Перевірте:
```bash
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
З вашої робочої машини:
```bash
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`
виявився вже зайнятим, і трафік випадково потрапляв не на той хост).
Швидка перевірка без жодних змін:
```bash
ssh claude-deploy@<proxmox-host> "sudo /usr/sbin/pct exec <будь-який-запущений-vmid> -- ping -c2 -W1 <кандидат-ip>"
```
Якщо є відповіді — ця IP зайнята, оберіть іншу.
### 3. Запустити `create-graylog-lxc.sh`
```bash
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. Прочитати креденшели адміністратора
```bash
ssh claude-deploy@<proxmox-host> "sudo pct exec 210 -- cat /opt/graylog/.admin_credentials_ONE_TIME"
```
Скопіюйте пароль у менеджер паролів, потім видаліть файл:
```bash
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:
```bash
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 на хості:
```bash
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}
```
### Перевірка тестового алерту
```bash
# зсередини контейнера, імітуючи кожен тригер:
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`
всередині контейнера — прочитайте його, збережіть у менеджері паролів,
а потім видаліть файл:
```bash
pct exec <VMID> -- cat /opt/graylog/.admin_credentials_ONE_TIME
pct exec <VMID> -- rm /opt/graylog/.admin_credentials_ONE_TIME
```