Логи — це чорний ящик вашого сервера 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.