Логи — це чорний ящик вашого сервера Linux. Якщо збережете їх в Amazon S3 із ротацією та шифруванням, будь-який інцидент розслідувати набагато легше. Тут я покажу, як зробити простий і надійний експортер для systemd-journald, налаштувати ротацію, включити серверне шифрування KMS і акуратно відновлювати логи при потребі 🚀 Це гайд для тих, хто працює з log-файли Linux, дбає про резервне копіювання системи, керує сервер Linux і любить автоматизацію через cron та systemd timers.
Що і навіщо ми будемо робити
Мета — періодично пакувати оригінальні бінарні журнали journald з /var/log/journal, стискати їх, шифрувати на стороні S3 (SSE-KMS) і завантажувати в бакет. Це зручно, бо:
- зберігаємо оригінальний формат journald — легко читати через
journalctl --fileі перевіряти підпис/цілісність; - швидке відновлення без сторонніх інструментів;
- контрольована ротація локально та в S3 через життєвий цикл.
Основний How-to
Крок 1. Підготовка S3 та AWS CLI
Потрібні: AWS обліковий запис, бакет S3, KMS-ключ (опційно, але дуже бажано). Встановіть AWS CLI і залогіньтесь:
# Встановлення AWS CLI (Debian/Ubuntu)
sudo apt update && sudo apt install -y awscli
# Налаштування облікових даних
aws configure
# Вкажіть AWS Access Key ID, Secret, region (наприклад, eu-central-1) та output (json)
# (Опц.) Увімкніть шифрування бакета SSE-KMS за замовчуванням
BUCKET="my-journald-archive"
KMS_KEY_ID="arn:aws:kms:eu-central-1:123456789012:key/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
aws s3api put-bucket-encryption \
--bucket "$BUCKET" \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "'"$KMS_KEY_ID"'"
}
}]
}'
# (Опц.) Життєвий цикл: архів через 30 днів, видалення через 180
cat > lifecycle.json <<'JSON'
{
"Rules": [
{
"ID": "journal-archive-lifecycle",
"Prefix": "journald/",
"Status": "Enabled",
"Transitions": [{
"Days": 30,
"StorageClass": "GLACIER"
}],
"Expiration": { "Days": 180 }
}
]
}
JSON
aws s3api put-bucket-lifecycle-configuration --bucket "$BUCKET" --lifecycle-configuration file://lifecycle.json
Крок 2. Налаштування ротації journald
Задайте розумні межі розміру/часу та навчіться вакуумувати старе. Приклад:
sudo sed -i 's/^#\?SystemMaxUse=.*/SystemMaxUse=500M/' /etc/systemd/journald.conf
sudo sed -i 's/^#\?SystemMaxFileSize=.*/SystemMaxFileSize=50M/' /etc/systemd/journald.conf
sudo sed -i 's/^#\?MaxRetentionSec=.*/MaxRetentionSec=14day/' /etc/systemd/journald.conf
sudo systemctl restart systemd-journald
# Разово упорядкувати та прибрати зайве
sudo journalctl --rotate
sudo journalctl --vacuum-time=14d
Крок 3. Bash-експортер journald → S3 з SSE-KMS
Скрипт створює архів бінарних журналів, завантажує в S3 з KMS-шифруванням і чистить локальні архіви. Простий і надійний 🛡️
sudo install -d -m 0750 /var/log/journal-archive
sudo tee /usr/local/bin/journald-to-s3.sh >/dev/null <<'BASH'
#!/usr/bin/env bash
set -euo pipefail
BUCKET="my-journald-archive"
PREFIX="journald"
REGION="eu-central-1"
KMS_KEY_ID="arn:aws:kms:eu-central-1:123456789012:key/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
HOST="$(hostname -f || hostname)"
TS="$(date -u +%Y%m%dT%H%M%SZ)"
ARCHIVE="/var/log/journal-archive/journald-${HOST}-${TS}.tar.gz"
# Зафіксувати дані на диск і створити свіже ротаційне файло
journalctl --flush || true
journalctl --rotate || true
sleep 2
# Архівуємо оригінальні бінарні журнали
# Важливо: беремо всю теку, щоби зберегти структуру і метадані
sudo tar --xattrs --acls -C / -czf "${ARCHIVE}" var/log/journal
# Завантажуємо в S3 з SSE-KMS та позначками
S3_URI="s3://${BUCKET}/${PREFIX}/${HOST}/$(basename "${ARCHIVE}")"
aws s3 cp "${ARCHIVE}" "${S3_URI}" \
--region "${REGION}" \
--sse aws:kms \
--sse-kms-key-id "${KMS_KEY_ID}" \
--storage-class STANDARD_IA
# (Опц.) Додаємо теги до об'єкта
aws s3api put-object-tagging \
--bucket "${BUCKET}" \
--key "${PREFIX}/${HOST}/$(basename "${ARCHIVE}")" \
--tagging 'TagSet=[{Key=host,Value='"${HOST}"'}]'
# Прості локальні прибирання
find /var/log/journal-archive -type f -mtime +14 -name 'journald-*.tar.gz' -delete || true
journalctl --vacuum-time=14d || true
BASH
sudo chmod 0750 /usr/local/bin/journald-to-s3.sh
Крок 4. Автоматизація через systemd timers
Запускатимемо експортер кожні 15 хвилин. Це надійніше за cron, бо systemd відпрацює навіть після даунтайму.
sudo tee /etc/systemd/system/journald-to-s3.service >/dev/null <<'UNIT'
[Unit]
Description=Export systemd-journald to S3 (SSE-KMS)
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/journald-to-s3.sh
Nice=10
IOSchedulingClass=best-effort
ProtectSystem=full
ProtectHome=true
PrivateTmp=true
CapabilityBoundingSet=CAP_DAC_READ_SEARCH CAP_FOWNER CAP_CHOWN
ReadWritePaths=/var/log/journal-archive
UNIT
sudo tee /etc/systemd/system/journald-to-s3.timer >/dev/null <<'UNIT'
[Unit]
Description=Run journald-to-s3 periodically
[Timer]
OnCalendar=*:0/15
Persistent=true
RandomizedDelaySec=60
[Install]
WantedBy=timers.target
UNIT
sudo systemctl daemon-reload
sudo systemctl enable --now journald-to-s3.timer
sudo systemctl start journald-to-s3.service
sudo systemctl status journald-to-s3.service --no-pager
Альтернативні способи
Fluent Bit (input: systemd → output: s3)
Підходить, якщо потрібен стрімінг майже в реальному часі і гнучкий буфер. Приклад мінімальної конфігурації:
# Встановлення (Ubuntu)
sudo apt-get install -y fluent-bit || {
curl https://packages.fluentbit.io/fluentbit.key | sudo gpg --dearmor -o /usr/share/keyrings/fluentbit.gpg
echo "deb [signed-by=/usr/share/keyrings/fluentbit.gpg] https://packages.fluentbit.io/ubuntu/ $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/fluent-bit.list
sudo apt update && sudo apt install -y fluent-bit
}
# Конфіг із SSE-KMS
echo > /etc/fluent-bit/fluent-bit.conf <<'CFG'
[SERVICE]
Flush 5
Daemon Off
Log_Level info
[INPUT]
Name systemd
Tag host.*
Systemd_Filter _SYSTEMD_UNIT=sshd.service
[OUTPUT]
Name s3
Match host.*
bucket my-journald-archive
region eu-central-1
total_file_size 50M
upload_timeout 5m
use_put_object On
s3_server_side_encryption kms
s3_kms_key_id arn:aws:kms:eu-central-1:123456789012:key/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
compression gzip
CFG
sudo systemctl enable --now fluent-bit
Плюси: доставка майже онлайн, парсинг, фільтри. Мінуси: відновлення в рідний формат journald не передбачається.
GUI-спосіб (де доречно)
Через AWS Console: створіть бакет, увімкніть Default Encryption (SSE-KMS), додайте Lifecycle (перехід у Glacier і видалення), створіть KMS-ключ і надайте доступ ролі/користувачу. Далі наш експортер працюватиме без змін — усе керується з веб-інтерфейсу.
Відновлення логів із S3
Оскільки ми зберігаємо бінарні файли journald, їх можна читати напряму:
# Завантажити потрібний архів
aws s3 cp s3://my-journald-archive/journald/$(hostname)/journald-$(hostname)-20240101T000000Z.tar.gz /tmp/
# Розпакувати в тимчасову теку
mkdir -p /tmp/journal-restore
sudo tar -xzf /tmp/journald-$(hostname)-20240101T000000Z.tar.gz -C /tmp/journal-restore
# Перегляд через journalctl (без імпорту в живу систему)
journalctl --file=/tmp/journal-restore/var/log/journal/*/*.journal | less
# Фільтри та часові рамки
journalctl --file=/tmp/journal-restore/var/log/journal/*/*.journal -u sshd --since "2024-01-01" --until "2024-01-02"
За потреби можете скопіювати відновлені файли в окрему теку та читати їх паралельно з поточними журналами системи. Повертати їх до живого каталогу /var/log/journal не потрібно.
FAQ
Це безпечно? SSE-S3 vs SSE-KMS
SSE-S3 простіше, але SSE-KMS дає контроль доступу й аудит ключів. Якщо у вас суворі вимоги — використовуйте KMS.
Чи можна замінити systemd timer на cron?
Можна, але systemd timers надійніші й мають Persistent=true — завдання не «загубиться» після простою.
Експортер дублює дані — архів щоразу великий. Це ок?
Так, це спрощує відновлення. Компенсуйте вартість сховища політикою Lifecycle (Glacier + видалення). Або перейдіть на Fluent Bit/Vector для інкрементального стрімінгу.
Як передавати через проксі/обмежену мережу?
Налаштуйте змінні середовища HTTPS_PROXY/NO_PROXY для AWS CLI та дайте вихід тільки до S3 і KMS через VPC endpoints або egress proxy.
Чи можна експортувати в JSON?
Так: journalctl -o json. Але тоді «рідне» відновлення в journald не вийде — JSON підходить для аналітики/ELK, не для бінарного перегляду.
Архіви дуже великі. Що робити?
Зменшіть SystemMaxUse/SystemMaxFileSize, стискайте zstd замість gzip, збільшіть частоту таймера, використовуйте STANDARD_IA/Glacier.
Порада від Kernelka
Позначайте архіви тегами S3: env=prod, team=platform, host=$(hostname). Це допоможе швидко знаходити потрібний журнал і налаштовувати окремі політики життєвого циклу для різних середовищ 🙂
Підсумок
- Налаштували бакет S3 з SSE-KMS і Lifecycle.
- Оптимізували ротацію journald і прибирання старих логів.
- Зробили простий bash-експортер і автоматизували його через systemd timer.
- Показали альтернативу через Fluent Bit для потокової доставки.
- Навчилися відновлювати бінарні журнали і читати їх
journalctl --file.

Прокоментувати
На сайті відображається лише твоє ім'я та коментар. Електронна пошта зберігається виключно для зв'язку з тобою за потреби та в жодному разі не передається стороннім особам.