Централизованный сбор логов со всех серверов по SSH
Когда в инфраструктуре 3 и более серверов, необходимость собирать логи в одно место становится актуальной. Не всем необходимо разворачивать полноценный ELK/Graylog. Может быть достаточно скрипта, который собирает логи по SSH и приводит их к единому формату.
Сегодня покажу, как сделать легкую систему централизованного логирования на
Допустим:
Каждый наш сервер умеет отдавать свои логи через:
Мы создаем на главном сервере каталог:
И напрягаем bash забирать туда свежие логи по расписанию.
Что будем собирать:
1) systemd-журналы:
2) классические логи:
🛠 Скрипт централизованного сбора логов
-a - сохраняет структуру, права
-z - сжатие
--delete - удаляет локальные файлы, которых уже нет на сервере (чтобы каталог не рос бесконечно)
Структура в итоге выглядит так:
▪️ Версия с датой. Добавим архивирование и метку времени:
Можно затем сделать cron-задачу:
BashTex📱 #bash
Когда в инфраструктуре 3 и более серверов, необходимость собирать логи в одно место становится актуальной. Не всем необходимо разворачивать полноценный ELK/Graylog. Может быть достаточно скрипта, который собирает логи по SSH и приводит их к единому формату.
Сегодня покажу, как сделать легкую систему централизованного логирования на
rsync + journalctl, без агентов, демонов и тяжелых сервисов.Допустим:
Каждый наш сервер умеет отдавать свои логи через:
journalctl --since --until - системные логи/var/log/ - классические текстовые логиМы создаем на главном сервере каталог:
/var/log/central/
├── server1/
├── server2/
├── server3/
└── ...
И напрягаем bash забирать туда свежие логи по расписанию.
Что будем собирать:
1) systemd-журналы:
journalctl --since "1 hour ago" --output=short-iso
2) классические логи:
/var/log/*.log
/var/log/nginx/
/var/log/syslog*
/var/log/auth.log*
#!/usr/bin/env bash
set -euo pipefail
# Список серверов
SERVERS=("srv1" "srv2" "srv3")
USER="logcollector"
DEST="/var/log/central"
SINCE="1 hour ago"
mkdir -p "$DEST"
for host in "${SERVERS[@]}"; do
echo "[+] Сбор логов с $host"
HOST_DIR="$DEST/$host"
mkdir -p "$HOST_DIR"
# 1) journalctl → локальный файл
ssh "$USER@$host" \
"journalctl --since \"$SINCE\" --output=short-iso" \
> "$HOST_DIR/journal.log"
# 2) rsync логов
rsync -az --delete \
"$USER@$host:/var/log/" \
"$HOST_DIR/textlogs/"
done
ssh "journalctl …" - Мы вызываем journalctl прямо на удаленной машине и как итог лог формируется быстро и передается по SSH. Формат short-iso нужен, чтобы было удобно анализировать.rsync -az --delete-a - сохраняет структуру, права
-z - сжатие
--delete - удаляет локальные файлы, которых уже нет на сервере (чтобы каталог не рос бесконечно)
Структура в итоге выглядит так:
/var/log/central/srv1/
├── journal.log
└── textlogs/
├── syslog
├── auth.log
├── nginx/access.log
└── ...
TS=$(date +%F_%H-%M)
ssh "$USER@$host" \
"journalctl --since \"$SINCE\" --output=short-iso" \
> "$HOST_DIR/journal_$TS.log"
rsync -az "$USER@$host:/var/log/" "$HOST_DIR/textlogs_$TS/"
Можно затем сделать cron-задачу:
0 * * * * /usr/local/bin/collect-logs.sh
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
Используй предстоящие новогодние выходные наилучшим образом! Изучай новые технологии или закрой пробелы в знаниях по своему стеку.
Стань экспертом в следующих направлениях:
• Системное администрирование
• Информационная безопасность
• Сетевое администрирование
• Этичный хакинг
Ссылка для своих: https://news.1rj.ru/str/admbooks
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Локальные снапшоты каталога
Одна из интересных и полезных идей для реализации: сделать машину времени для каталога т.е. реализовать сценарий при котором возможно вернуться к состоянию вчера/час назад/неделю назад (без btrfs, zfs или lvm)
Решение: rsync + hardlinks
Это ближе всех к btrfs/ZFS-снапшотам в мире обычных файловых систем (ext4/xfs).
▪️ Структура:
Каждый каталог - полноценная копия состояния.
🛠 Мини-скрипт
▪️ Восстановление состояния каталога. Чтобы вернуть старую версию:
Либо наоборот: перейти в каталог снапшота и увидеть все как было.
▪️ Автоматизация. Например, снимок каждый час:
▪️ Полезные дополнения
1️⃣ Автопургатор (ретеншн-политика)
Удалить снапшоты старше 30 дней.
2️⃣ Игнорирование временных файлов. Создай
И вызывай rsync так:
BashTex📱 #bash #utils
Одна из интересных и полезных идей для реализации: сделать машину времени для каталога т.е. реализовать сценарий при котором возможно вернуться к состоянию вчера/час назад/неделю назад (без btrfs, zfs или lvm)
Решение: rsync + hardlinks
Это ближе всех к btrfs/ZFS-снапшотам в мире обычных файловых систем (ext4/xfs).
/backups/
2025-12-05-12:00/
2025-12-05-13:00/
2025-12-05-14:00/
Каждый каталог - полноценная копия состояния.
#!/usr/bin/env bash
set -e
SRC="/home/user/project"
DST="/backups"
TS=$(date +"%Y-%m-%d-%H:%M")
LAST="$DST/latest"
NEW="$DST/$TS"
mkdir -p "$DST"
if [[ -d "$LAST" ]]; then
echo "[i] Using hardlinks from: $LAST"
rsync -a --delete --link-dest="$LAST" "$SRC/" "$NEW/"
else
echo "[i] First snapshot — no link-dest"
rsync -a --delete "$SRC/" "$NEW/"
fi
# Update the "latest" symlink
rm -f "$LAST"
ln -s "$(basename "$NEW")" "$LAST"
echo "[i] Snapshot created: $NEW"
rsync -a —link-dest=... - команда говорит сравни каталог с предыдущим снапшотом и все неизмененные файлы сделай через hardlink. Это не копия, а именно вторая ссылка на тот же inode. Место: +0 байт.--delete - удаленные файлы тоже исчезнут из новой точки — snapshot становится точным состоянием дерева в данный момент.Symlink latest - позволяет быстро создавать следующую точку, не перебирая каталоги.
rsync -a /backups/2025-12-05-12:00/ /home/user/project/
Либо наоборот: перейти в каталог снапшота и увидеть все как было.
0 * * * * /usr/local/bin/snapshot.sh
find /backups -maxdepth 1 -type d -mtime +30 -exec rm -rf {} \;
Удалить снапшоты старше 30 дней.
.rsync-filter:
- *.tmp
- .cache/
И вызывай rsync так:
rsync -a --filter='. .rsync-filter' ...
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
Генерация unit файлов из шаблонов
Если у вас десятки сервисов, работающих по схожей схеме (например, несколько python или node-приложений), руками плодить unit-файлы в
▪️ Простейший шаблон. Создаем
▪️ Генерация через bash
▪️ Массовая генерация из списка. Храним параметры сервисов в CSV:
А bash все развернет:
BashTex📱 #bash #utils
Если у вас десятки сервисов, работающих по схожей схеме (например, несколько python или node-приложений), руками плодить unit-файлы в
/etc/systemd/system - сомнительное удовольствие. Правильнее и логичнее будет автоматизировать путем генерирации юнитов из шаблонов через bash.service.tpl:
[Unit]
Denoscription={{NAME}} service
After=network.target
[Service]
Type=simple
User={{USER}}
WorkingDirectory={{WORKDIR}}
ExecStart={{EXEC}}
Restart=always
[Install]
WantedBy=multi-user.target
#!/bin/bash
set -euo pipefail
TEMPLATE="./service.tpl"
OUTPUT_DIR="/etc/systemd/system"
generate_unit() {
local name="$1" user="$2" workdir="$3" exec="$4"
local outfile="$OUTPUT_DIR/${name}.service"
sed \
-e "s|{{NAME}}|$name|g" \
-e "s|{{USER}}|$user|g" \
-e "s|{{WORKDIR}}|$workdir|g" \
-e "s|{{EXEC}}|$exec|g" \
"$TEMPLATE" > "$outfile"
echo "Сервис $name создан: $outfile"
}
# Пример использования
generate_unit "myapp" "deploy" "/opt/myapp" "/opt/myapp/venv/bin/python app.py"
systemctl daemon-reload
systemctl enable myapp --now
name,user,workdir,exec
app1,deploy,/opt/app1,/opt/app1/start.sh
app2,deploy,/opt/app2,/usr/bin/python3 /opt/app2/run.py
А bash все развернет:
#!/bin/bash
while IFS=, read -r name user workdir exec; do
[[ "$name" == "name" ]] && continue # пропускаем заголовок
generate_unit "$name" "$user" "$workdir" "$exec"
done < services.csv
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🤨1
Резервное копирование Docker volumes без остановки контейнеров
Когда в контейнере крутится прод, остановка ради бэкапа будет невозможной роскошью. Но docker volumes можно сохранять, без остановки, если правильно защититься от: открытых файлов, неконсистентных записей и гонок при чтении.
1️⃣ Проблема горячих бэкапов
Если контейнер пишет в volume во время архивации:
файлы могут быть разорваны,
часть транзакций попадет в архив, а часть нет,
каталог может измениться, пока его читает tar.
Чтобы избежать этого, нужен мягкий снапшот: заморозить структуру, дождаться закрытия файлов и архивировать.
Мы это реализуем через:
проверку открытых дескрипторов lsof
копию структуры через freeze-режим
tar с фиксированным списком файлов
2️⃣ Определяем volume и контейнер
3️⃣ Ждем, пока контейнер перестанет писать. Контейнер может продолжать работать, но нам важно дождаться момента тишины.
Это не блокирует процесс, просто ждёт микропаузу между транзакциями.
4️⃣ Создаем снапшот список файлов
Важно: tar должен использовать фиксированный список, а не динамическое дерево.
Так мы замораживаем текущее состояние структуры.
5️⃣ Архивация без остановки контейнера
Файлы читаются быстрее, чем контейнер успевает их поменять, а вероятность гонки почти нулевая, потому что мы:
дождались отсутствия открытых дескрипторов
зафиксировали список файлов заранее
используем tar с указанием точных путей
6️⃣ Опционально: перезапрос тишины перед чтением больших файлов
Для больших БД/логов:
И внутри цикла архивации:
Это максимально приближает бэкап к снапшот-принципам.
7️⃣ Очистка
8️⃣ Пример полностью готового скрипта. Минимальный файл backup_volume.sh:
BashTex📱 #bash
Когда в контейнере крутится прод, остановка ради бэкапа будет невозможной роскошью. Но docker volumes можно сохранять, без остановки, если правильно защититься от: открытых файлов, неконсистентных записей и гонок при чтении.
Если контейнер пишет в volume во время архивации:
файлы могут быть разорваны,
часть транзакций попадет в архив, а часть нет,
каталог может измениться, пока его читает tar.
Чтобы избежать этого, нужен мягкий снапшот: заморозить структуру, дождаться закрытия файлов и архивировать.
Мы это реализуем через:
проверку открытых дескрипторов lsof
копию структуры через freeze-режим
tar с фиксированным списком файлов
VOLUME="mydata"
BACKUP_DIR="/backups"
STAMP=$(date +%Y%m%d-%H%M)
TARGET="$BACKUP_DIR/$VOLUME-$STAMP.tar.gz"
MOUNTPOINT=$(docker volume inspect "$VOLUME" -f '{{.Mountpoint}}')
wait_until_quiet() {
local path="$1"
local delay=${2:-1}
while lsof +D "$path" >/dev/null 2>&1; do
echo "[*] Volume busy, waiting..."
sleep "$delay"
done
}
Это не блокирует процесс, просто ждёт микропаузу между транзакциями.
Важно: tar должен использовать фиксированный список, а не динамическое дерево.
SNAPLIST=$(mktemp)
find "$MOUNTPOINT" -type f -o -type d | sed "s#^$MOUNTPOINT/##" > "$SNAPLIST"
Так мы замораживаем текущее состояние структуры.
echo "[*] Waiting for files to become quiet..."
wait_until_quiet "$MOUNTPOINT"
echo "[*] Creating hot-backup: $TARGET"
tar -czf "$TARGET" \
-C "$MOUNTPOINT" \
-T "$SNAPLIST"
Файлы читаются быстрее, чем контейнер успевает их поменять, а вероятность гонки почти нулевая, потому что мы:
дождались отсутствия открытых дескрипторов
зафиксировали список файлов заранее
используем tar с указанием точных путей
Для больших БД/логов:
pause_if_lock() {
local file="$1"
while lsof "$file" >/dev/null 2>&1; do
echo "[*] File still in use: $file"
sleep 0.5
done
}
И внутри цикла архивации:
while read -r f; do
pause_if_lock "$MOUNTPOINT/$f"
done < "$SNAPLIST"
Это максимально приближает бэкап к снапшот-принципам.
rm -f "$SNAPLIST"
echo "[+] Backup completed: $TARGET"
#!/usr/bin/env bash
set -euo pipefail
VOLUME="$1"
BACKUP_DIR="/backups"
STAMP=$(date +%Y%m%d-%H%M)
TARGET="$BACKUP_DIR/$VOLUME-$STAMP.tar.gz"
MOUNTPOINT=$(docker volume inspect "$VOLUME" -f '{{.Mountpoint}}')
wait_until_quiet() {
while lsof +D "$1" >/dev/null 2>&1; do
sleep 1
done
}
echo "[*] Volume mountpoint: $MOUNTPOINT"
SNAPLIST=$(mktemp)
find "$MOUNTPOINT" -mindepth 1 -printf '%P\n' > "$SNAPLIST"
echo "[*] Waiting for quiet state..."
wait_until_quiet "$MOUNTPOINT"
echo "[*] Archiving..."
tar -czf "$TARGET" -C "$MOUNTPOINT" -T "$SNAPLIST"
rm -f "$SNAPLIST"
echo "[+] Done: $TARGET"
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Please open Telegram to view this post
VIEW IN TELEGRAM
😁13
Сбор данных с нескольких серверов
Конструкция позволяет сравнивать состояние двух (или нескольких) машин без временных файлов, параллельно и максимально быстро.
Process substitution создает виртуальные файлы, в которые пишется вывод команд. SSH-запросы выполняются параллельно, а diff думает, что сравнивает два обычных файла:
▪️ Лучшие практические примеры
▪️ Сравнение пакетов
▪️ Конфиги (sshd_config)
▪️ Сетевые соединения
▪️ Docker-контейнеры
▪️ Мини-функция
Использование:
BashTex📱 #bash #utils
<(ssh host1 cmd) vs <(ssh host2 cmd)
Конструкция позволяет сравнивать состояние двух (или нескольких) машин без временных файлов, параллельно и максимально быстро.
Process substitution создает виртуальные файлы, в которые пишется вывод команд. SSH-запросы выполняются параллельно, а diff думает, что сравнивает два обычных файла:
diff <(ssh h1 cmd) <(ssh h2 cmd)
diff <(ssh h1 "dpkg -l | sort") \
<(ssh h2 "dpkg -l | sort")
diff -u <(ssh h1 "grep -v '^#' /etc/ssh/sshd_config") \
<(ssh h2 "grep -v '^#' /etc/ssh/sshd_config")
diff <(ssh h1 "ss -tuna | sort") \
<(ssh h2 "ss -tuna | sort")
diff <(ssh h1 "docker ps --format '{{.Names}} {{.Image}}' | sort") \
<(ssh h2 "docker ps --format '{{.Names}} {{.Image}}' | sort")
compare_hosts() {
diff <(ssh "$1" "$3") <(ssh "$2" "$3")
}
Использование:
compare_hosts node1 node2 "df -h"
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11
Сравнение authorized_keys между серверами
Когда в кластере несколько серверов, важно, чтобы доступ имели одни и те же пользователи с одинаковыми ключами. Несогласованность в authorized_keys будет частой точка входа для проблем.
▪️ Базовый прием: сравнение двух серверов
Сразу видно какие ключи есть только на h1 и какие есть только на h2. Строки отличаются полностью, поэтому легко понять лишние.
▪️ Сравнение директорий для всех пользователей
Использование:
▪️ Мультисерверная проверка списка хостов
Покажет отличия между эталоном и всеми остальными.
▪️ Поиск лишних ключей
Покажет ключи, присутствующие только на h1.
BashTex📱 #bash #utils
Когда в кластере несколько серверов, важно, чтобы доступ имели одни и те же пользователи с одинаковыми ключами. Несогласованность в authorized_keys будет частой точка входа для проблем.
diff <(ssh h1 "sort ~/.ssh/authorized_keys") \
<(ssh h2 "sort ~/.ssh/authorized_keys")
Сразу видно какие ключи есть только на h1 и какие есть только на h2. Строки отличаются полностью, поэтому легко понять лишние.
compare_keys() {
user="$1"
diff <(ssh "$2" "sort /home/$user/.ssh/authorized_keys 2>/dev/null") \
<(ssh "$3" "sort /home/$user/.ssh/authorized_keys 2>/dev/null")
}
Использование:
compare_keys deploy h1 h2
compare_keys admin h1 h2
hosts=(h1 h2 h3)
base="h1"
for h in "${hosts[@]:1}"; do
echo "=== $base vs $h ==="
diff <(ssh "$base" "sort ~/.ssh/authorized_keys") \
<(ssh "$h" "sort ~/.ssh/authorized_keys")
done
Покажет отличия между эталоном и всеми остальными.
ssh h1 "sort ~/.ssh/authorized_keys" \
| grep -vFf <(ssh h2 "sort ~/.ssh/authorized_keys")
Покажет ключи, присутствующие только на h1.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Сложные пайпы в bash
Иногда нужно одновременно: подавать данные в программу, анализировать ее вывод, логировать поток, фильтровать часть данных и все это без временных файлов. Такое легко делается через tee + process substitution.
▪️ Базовый шаблон
и при этом нет temp-файлов, все идёт по fd
▪️ Пример 1: анализ, логирование и фильтрация сразу. Извлекаем ошибки в отдельный файл, а общий вывод обрабатываем:
▪️ Пример 2: split-поток для алертов и статистики
▪️ Пример 3: читаем и пишем в интерактивный процесс. Двунаправленная работа с программой:
Работает как мини-оркестратор для интерактивного ввода/вывода.
BashTex📱 #bash #utils
Иногда нужно одновременно: подавать данные в программу, анализировать ее вывод, логировать поток, фильтровать часть данных и все это без временных файлов. Такое легко делается через tee + process substitution.
cat input.log \
| cmd \
| tee >(grep "ERROR" > errors.log) \
| awk '{print $1, $3}'
cmd принимает stdin от cat;tee дублирует поток: в файл-поток >(grep ...) и дальше в awk;grep работает параллельно, не мешая основному пайпу;и при этом нет temp-файлов, все идёт по fd
journalctl -u nginx \
| tee >(grep 500 >500.log) \
| awk '/request/ {print $NF}'
tail -F app.log \
| tee >(grep CRITICAL | notify-send "ALERT!") \
| awk '{count[$5]++} END {for (i in count) print i, count[i]}'
coproc APP { cmd --interactive; }
echo "status" >&"${APP[1]}"
cat <&"${APP[0]}" \
| tee >(grep ALERT >alerts.log) \
| awk '{print "OUT:", $0}'
Работает как мини-оркестратор для интерактивного ввода/вывода.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
Использование socat как универсального канала между скриптами
▪️ Bash через UNIX-сокет. Мини-IPC без tmp-файлов и named pipes:
Сервер:
Клиент:
Отлично подходит для локальных API между скриптами.
2️⃣ Передача данных между серверами (TCP)
Сервер:
Клиент:
Можно в реальном времени стримить логи или метрики.
3️⃣ Замена сложных пайпов. Напрямую соединяем процессы, даже если они не умеют работать с stdin/stdout:
Аналог cmd1 | cmd2, но без ограничений пайпа (например, поддержка bidirectional).
4️⃣ Двунаправленный канал в скрипте. Например, общение с интерактивным сервисом:
Можно строить собственные протоколы поверх TCP.
5️⃣ Быстрый импровизированный RPC
Сервер:
Клиент:
BashTex📱 #bash #utils
socat позволяет связать любой источник данных с любым приемником. Bash-скрипты с ним превращаются в гибкие системы обмена сообщениями.Сервер:
socat UNIX-LISTEN:/tmp/app.sock,fork SYSTEM:'bash handler.sh'
Клиент:
echo "PING" | socat - UNIX-CONNECT:/tmp/app.sock
Отлично подходит для локальных API между скриптами.
Сервер:
socat TCP-LISTEN:9000,fork FILE:/var/log/app.log
Клиент:
socat - TCP:server:9000
Можно в реальном времени стримить логи или метрики.
socat EXEC:"cmd1" EXEC:"cmd2"
Аналог cmd1 | cmd2, но без ограничений пайпа (например, поддержка bidirectional).
socat - TCP:localhost:8080 | while read l; do
echo ">> $l"
done
Можно строить собственные протоколы поверх TCP.
Сервер:
socat TCP-LISTEN:7000,reuseaddr,fork SYSTEM:'bash rpc-handler.sh'
Клиент:
echo '{"cmd":"status"}' | socat - TCP:host:7000
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4👍2
Распределенный журнал событий через SSH fan-out
Иногда нужно собрать события с группы серверов и посмотреть их как единый хронологический поток: кто что делал, когда, и на каком хосте. Можно сделать это чисто на bash + SSH.
Для этого:
По SSH запускаем команду на каждом сервере (journalctl, tail, свой лог).
Добавляем timestamp + hostname.
Объединяем и сортируем и получаем общий timeline.
▪️ Fan-out по серверам
▪️ Централизованное объединение и сортировка
Теперь
BashTex📱 #bash
Иногда нужно собрать события с группы серверов и посмотреть их как единый хронологический поток: кто что делал, когда, и на каком хосте. Можно сделать это чисто на bash + SSH.
Для этого:
По SSH запускаем команду на каждом сервере (journalctl, tail, свой лог).
Добавляем timestamp + hostname.
Объединяем и сортируем и получаем общий timeline.
HOSTS=(srv1 srv2 srv3)
for h in "${HOSTS[@]}"; do
ssh "$h" "journalctl -n 50 --no-pager" | \
awk -v host="$h" '{print strftime("%s"), host, $0}' &
done
wait
{
for h in "${HOSTS[@]}"; do
ssh "$h" "journalctl -n 200 --no-pager" \
| awk -v host="$h" '{print strftime("%s"), host, $0}'
done
} | sort -n > timeline.log
Теперь
timeline.log выглядит так:
1712409123 srv1 sshd[1023]: Accepted password for admin
1712409124 srv3 systemd[1]: Started backup.
1712409125 srv2 docker[532]: Container restarted.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4