The alert5 description and both READMEs said "~4,600-4,800 msgs/10min" as the calibrated baseline - a transcription error, a digit short of the real figure (~46,000-48,000/10min, matching the ~278k/hour number right next to it, which was correct). The configured threshold (150,000) was actually computed from the correct number, so no behavior changed - only the description text was wrong. Also documents a real event from today: EX-NAS-1-2's first-ever Vector startup tripped this alert (430,982 msgs/10min) - Vector's file source replays existing log content from the beginning with no checkpoint yet, so a busy log's backlog shows up as a single burst rather than a real ongoing issue. Confirmed the sample messages were all routine DHCP/session churn timestamped around the same moment. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
766 lines
60 KiB
Markdown
766 lines
60 KiB
Markdown
# Централізований лог-сервер 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 і
|
||
в кінці виводить дані адміністратора.
|
||
|
||
Обидва скрипти ідемпотентні — повторний запуск після збою (або для
|
||
оновлення) продовжує з того місця, де зупинився, а не дублює роботу.
|
||
|
||
## Варіанти запуску
|
||
|
||
Чотири способи застосувати цей проєкт — від "ввести одну команду" до
|
||
"натиснути кнопку в браузері". Обирайте залежно від того, що змінилося і
|
||
скільки налаштування готові зробити наперед.
|
||
|
||
| Спосіб | Звідки запускається | Що робить | Одноразове налаштування | Коли використовувати |
|
||
|---|---|---|---|---|
|
||
| **Вручну, повний** | термінал на хості Proxmox | `create-graylog-lxc.sh` → створює/ремонтує LXC-контейнер, встановлює все | немає | перший деплой, або коли хочете бачити процес наживо |
|
||
| **Вручну, тільки конфіг** | термінал, `pct exec` у контейнер | повторний запуск `install-graylog.sh` напряму | немає | швидкий фікс конфігурації без git |
|
||
| **CI, тільки конфіг** | веб-інтерфейс Forgejo | workflow `deploy.yml` → перезапускає `install-graylog.sh` з останнього коміту | `setup-forgejo-runner.sh` **всередині контейнера** (див. крок 9) | запушили нове правило/алерт/дашборд, контейнер уже існує |
|
||
| **CI, повний** | веб-інтерфейс Forgejo | workflow `deploy-from-scratch.yml` → запускає `create-graylog-lxc.sh` з останнього коміту | `setup-forgejo-runner.sh` **на хості Proxmox** (див. крок 9) | одна кнопка для пересоздання після ручного `pct destroy`, без терміналу |
|
||
|
||
Усі чотири варіанти виконують ті самі ідемпотентні скрипти — жодної
|
||
"CI-only" поведінки, яка б відрізнялась від того, що ввели б вручну.
|
||
Жоден з CI-workflow не виконує `pct destroy` — це завжди свідомий ручний
|
||
крок (дивіться "CI/CD через Forgejo Actions" нижче, чому саме так).
|
||
|
||
## Покрокове розгортання з нуля
|
||
|
||
### 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. Прочитати креденшели адміністратора
|
||
|
||
`install-graylog.sh` сам виводить згенерований логін/пароль адміна один
|
||
раз, наприкінці успішного свіжого прогону, і одразу видаляє одноразовий
|
||
файл — більше нічого робити не треба. Якщо пропустили (або прогін впав
|
||
раніше цього моменту), файл усе ще лежить у
|
||
`/opt/graylog/.admin_credentials_ONE_TIME`, поки не прочитаєте:
|
||
```bash
|
||
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:
|
||
```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 протягом приблизно хвилини.
|
||
|
||
### 9. (Опційно) Налаштувати Forgejo Actions CI
|
||
|
||
`setup-forgejo-runner.sh` реєструє self-hosted Actions runner — він може
|
||
працювати у двох різних місцях, один скрипт для обох, просто різні
|
||
параметри. Див. "CI/CD через Forgejo Actions" нижче, що саме робить кожен
|
||
workflow; цей крок — лише одноразове налаштування раннера. Обидва
|
||
опційні й незалежні — налаштуйте один, жодного чи обидва.
|
||
|
||
**Спільний перший крок:** у веб-інтерфейсі Forgejo: репо → Settings →
|
||
Actions → Runners → "Create new Runner", щоб отримати токен реєстрації
|
||
(одноразове значення, не вгадуйте й не перевикористовуйте з іншого репо
|
||
— беріть новий токен для кожного раннера, який реєструєте).
|
||
|
||
**9a. Раннер усередині контейнера** (для `deploy.yml` — редеплой тільки
|
||
конфігурації):
|
||
```bash
|
||
pct exec <VMID> -- env \
|
||
FORGEJO_URL="https://ваш-forgejo-інстанс" \
|
||
FORGEJO_RUNNER_TOKEN="<токен>" \
|
||
bash /opt/graylog-deploy/setup-forgejo-runner.sh
|
||
```
|
||
|
||
**9b. Раннер на самому хості Proxmox** (для `deploy-from-scratch.yml` —
|
||
повне пересоздання). Спочатку на хості має бути встановлений `git`
|
||
(`apt-get install -y git` — у контейнері він уже є, на голому хості
|
||
зазвичай нема). `RUNNER_USER=claude-deploy` тут обов'язковий, не
|
||
опційний: без нього systemd-сервіс працює від root, видаючи кожному
|
||
CI-job необмежений root на хості замість вузько-скоупленого sudo
|
||
`claude-deploy`:
|
||
```bash
|
||
sudo env FORGEJO_URL="https://ваш-forgejo-інстанс" FORGEJO_RUNNER_TOKEN="<токен>" RUNNER_NAME="proxmox-host-runner" RUNNER_LABEL="proxmox-host:host" RUNNER_DIR="/opt/forgejo-runner-host" SERVICE_NAME="forgejo-runner-host" RUNNER_USER="claude-deploy" bash /home/claude-deploy/graylog-deploy/setup-forgejo-runner.sh
|
||
```
|
||
(Одним рядком, без `\` — у деяких терміналах багаторядкова вставка з
|
||
переносами губила продовження, і `sudo env` виконувався зовсім без
|
||
аргументів і без жодної встановленої змінної.)
|
||
|
||
**9c. Секрети**, які потребують workflow (репо → Settings → Actions →
|
||
Secrets): `GRAYLOG_ADMIN_PASSWORD` та `DISCORD_WEBHOOK_URL`. Третє
|
||
значення, `secrets.GITHUB_TOKEN`, створювати не треба — Forgejo видає
|
||
його автоматично на кожен job, прив'язаним лише до цього репо.
|
||
|
||
**Видалення раннера пізніше** (наприклад, host-level, якщо не хочете
|
||
тримати його зареєстрованим, коли активно не деплоїте):
|
||
```bash
|
||
sudo systemctl disable --now forgejo-runner-host
|
||
sudo rm -f /etc/systemd/system/forgejo-runner-host.service
|
||
sudo systemctl daemon-reload
|
||
sudo rm -rf /opt/forgejo-runner-host
|
||
```
|
||
а потім видаліть його й у UI: репо → Settings → Actions → Runners.
|
||
Повторна реєстрація пізніше потребує нового токена з тієї ж сторінки —
|
||
старий автоматично не перевикористається.
|
||
|
||
## Параметри
|
||
|
||
Кожен флаг має відповідник у вигляді змінної середовища (див. початок
|
||
`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 на хості:
|
||
```bash
|
||
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 сервер стабільно слав **~46 000-48 000
|
||
повідомлень за 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 кожного
|
||
сервера.
|
||
|
||
**Перший запуск Vector на новому сервері може спрацювати цей алерт один
|
||
раз, безпечно** — підтверджено наживо 2026-07-23, коли другий NAS-сервер
|
||
(EX-NAS-1-2) вийшов онлайн: file-джерело Vector при свіжому запуску (без
|
||
чекпоінта) читає наявний вміст логу від самого початку, тож завантажений
|
||
accel-ppp-лог із годинами рутинного DHCP/session-трафіку відтворюється
|
||
одним сплеском — у тому випадку 430 982 повідомлення за одне 10-хвилинне
|
||
вікно, усі звичайнісінькі (`DHCPv4 Ack`, `ipoe stop/remove`), усі з
|
||
міткою часу приблизно в один момент. Не цикл, не помилка конфігурації —
|
||
просто відтворення накопиченого при холодному старті. Перевірте, чи
|
||
вляглося, тим самим запитом по джерелу нижче; якщо обсяг лишається
|
||
підвищеним довше одного-двох вікон — тоді вже трактуйте як реальний
|
||
інцидент.
|
||
|
||
Перевірити поточний обсяг по джерелу можна будь-коли запитом
|
||
на кшталт:
|
||
```
|
||
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-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` надійніший, бо береться з самого
|
||
пакета, а не з даних, які надає пристрій.
|
||
|
||
### Перевірка тестового алерту
|
||
|
||
```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-канал.
|
||
|
||
## CI/CD через Forgejo Actions
|
||
|
||
Forgejo-репозиторій проєкту (`git.zotac.keenetic.link/zotac/graylog-deploy`)
|
||
має два вручну-запускні workflow, кожен зі своїм раннером (див. крок 9
|
||
вище про налаштування). Обидва просто автоматизують скрипти, вже описані
|
||
в іншому місці цього README — жодної CI-специфічної поведінки немає.
|
||
|
||
### `deploy.yml` — редеплой тільки конфігурації
|
||
|
||
Клонує репо наново й повторно запускає `install-graylog.sh` усередині
|
||
контейнера. Використовуйте після пушу нового правила/алерту/дашборду/
|
||
pipeline на вже існуючий контейнер.
|
||
|
||
**Як запускати:** репо → вкладка **Actions** → **Deploy Graylog config** у
|
||
лівому меню → кнопка **Run workflow** → підтвердити.
|
||
|
||
- **Раннер живе прямо всередині контейнера Graylog** (VMID 200, лейбл
|
||
`self-hosted:host`, встановлений у `/usr/local/bin/forgejo-runner`,
|
||
працює як systemd-сервіс). Нових SSH-ключів чи міжхостового доступу не
|
||
знадобилося — workflow просто клонує репо в `/tmp/graylog-deploy-ci` і
|
||
запускає скрипт локально, точно як це робив би оператор вручну.
|
||
|
||
### `deploy-from-scratch.yml` — повне пересоздання
|
||
|
||
Клонує репо наново й повторно запускає `create-graylog-lxc.sh` **на
|
||
хості Proxmox**. Використовуйте після ручного `pct destroy` (чи на
|
||
контейнері, якого ще ніколи не було), коли хочете просто натиснути
|
||
кнопку замість вводу повної команди `create-graylog-lxc.sh`.
|
||
|
||
**Як запускати:** репо → вкладка **Actions** → **Deploy Graylog from
|
||
scratch (host-level)** у лівому меню → кнопка **Run workflow** → з'явиться
|
||
поле `cores` (за замовчуванням `4`) — змініть або лишіть як є →
|
||
підтвердити. Спрацьовує лише коли контейнер реально створюється заново;
|
||
на вже існуючому — no-op (те саме правило, що й для `--cores` всюди в
|
||
цьому проєкті — див. "Параметри" вище).
|
||
|
||
- **Раннер живе прямо на хості Proxmox**, працює від `claude-deploy`
|
||
(не root — див. крок 9b вище, чому це важливо), зареєстрований з
|
||
окремим лейблом (`proxmox-host:host`), тож підхоплює лише job'и для
|
||
цього workflow, не для `deploy.yml`.
|
||
- **`pct destroy` навмисно НЕ входить у цей workflow.** Автоматичне
|
||
пересоздання інфраструктури безпечне, бо `create-graylog-lxc.sh`
|
||
ідемпотентний (ремонтує на місці, якщо контейнер уже існує, створює
|
||
заново, якщо ні); знищення — незворотна втрата даних і залишається
|
||
свідомим набором команди людиною. Кнопка в CI, яка може знищити
|
||
продакшн одним випадковим кліком, розглядалась і була відхилена.
|
||
`pct destroy` навіть не входить у sudo-скоуп `claude-deploy` — свідомо.
|
||
- **На голому хості має бути встановлений `git`** — на відміну від
|
||
контейнера (де він є з шаблону Debian 12), свіжий хост Proxmox зазвичай
|
||
його не має (`apt-get install -y git`, одноразово, root).
|
||
|
||
### Спільне для обох workflow
|
||
|
||
- **Тригер навмисно ручний** (тільки `workflow_dispatch`, без
|
||
`on: push`) — вони керують продакшн-системою моніторингу, тож людина
|
||
переглядає диф і сама натискає "Run workflow", а не кожен push тихо
|
||
передеплоює систему.
|
||
- **Без `actions/checkout`** — ця дія вимагає Node.js, якого немає в
|
||
жодного раннера й не повинно бути лише заради 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 - створює/ремонтує LXC-контейнер
|
||
install-graylog.sh # усередині контейнера (запускається автоматично)
|
||
bootstrap-host.sh # одноразово, root, хост Proxmox - автоматизує AppArmor-фікс
|
||
cleanup-host.sh # відкочує bootstrap-host.sh
|
||
fix-lxc-apparmor.sh # встановлюється bootstrap-host.sh, напряму не запускати
|
||
setup-forgejo-runner.sh # реєструє CI-раннер - у контейнері чи на хості
|
||
docker-compose.yml # опис стеку MongoDB + OpenSearch + Graylog
|
||
nftables.conf # firewall-правила, що застосовуються всередині контейнера
|
||
rules/*.json # визначення Graylog Pipeline Rule (імпортуються через API)
|
||
pipelines/*.json # визначення Graylog Pipeline, що посилаються на правила
|
||
streams/*.json # визначення Graylog Stream (маршрутизація за портом input)
|
||
alerts/*.json # Event Definitions + шаблон Discord-нотифікації
|
||
dashboards/search_*.json # об'єкти пошуку Views API, пара на кожен дашборд
|
||
dashboards/view_*.json # макет дашборду/віджетів Views API, у парі з search_*.json
|
||
.forgejo/workflows/deploy.yml # CI: редеплой тільки конфігурації (раннер у контейнері)
|
||
.forgejo/workflows/deploy-from-scratch.yml # CI: повне пересоздання (раннер на хості)
|
||
```
|
||
|
||
Всередині контейнера все лежить у `/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
|
||
```
|
||
|
||
**Сам пароль адміністратора** за замовчуванням випадковий, але
|
||
`GRAYLOG_ADMIN_PASSWORD` (змінна середовища, та сама, яку прокидує
|
||
`create-graylog-lxc.sh`) може зафіксувати його на відоме значення замість
|
||
випадкового — заданий при свіжому встановленні, він стає справжнім
|
||
паролем, а не лише резервним значенням. Дві різні ролі однієї змінної
|
||
залежно від того, коли вона задана:
|
||
- **При свіжому встановленні** (`.env` ще не існує): якщо задано — стає
|
||
реальним паролем замість випадкового. Якщо ні — випадковий, як і раніше.
|
||
- **При будь-якому подальшому повторному запуску** (`.env` вже існує,
|
||
одноразовий файл уже спожитий попереднім успішним прогоном):
|
||
обов'язково, має збігатися з паролем, який уже налаштований — інакше
|
||
скрипту нема як автентифікуватись до вже запущеного інстансу.
|
||
|
||
Це найважливіше саме для CI: зафіксуйте пароль один раз через Forgejo
|
||
секрет `GRAYLOG_ADMIN_PASSWORD`, і він ніколи не застаріє — те саме
|
||
значення правильне і при створенні, і при кожному подальшому повторному
|
||
запуску, замість того, щоб помічати, що свіже встановлення згенерувало
|
||
новий випадковий пароль, і оновлювати секрет вручну (перевірено наживо:
|
||
повторний запуск `deploy-from-scratch.yml` проти вже ініціалізованого
|
||
Graylog зі застарілим секретом падав на `resolve_admin_password` з
|
||
"Cannot find admin password").
|