Rsync + lsyncd: синхронизация 2 ТБ между дата-центрами в реальном времени

Задача клиента

Медиакомпания «МедиаГрупп» хранит 2 ТБ видеоматериалов, графики и проектных файлов на файловом сервере в основном ЦОД (Москва). Бизнес-требование: все файлы должны быть доступны в резервном ЦОД (Санкт-Петербург) с задержкой не более 5 минут после изменения. Причины:

  • DR (Disaster Recovery) — при отказе основного ЦОД сотрудники переключаются на резервный за минуты, а не за часы восстановления из бэкапа.
  • Распределённые команды — видеомонтажёры в Петербурге работают с теми же файлами, что и московский офис.
  • Канал — между ЦОД есть выделенный линк 1 Гбит/с, но его нужно делить с другим трафиком.

Предыдущее решение — ночной rsync по cron — создавало окно потери данных до 24 часов и нагружало канал на 100% в течение 3–4 часов ночью.

Rsync: продвинутые опции для больших объёмов

Прежде чем настраивать real-time синхронизацию, мы оптимизировали сам rsync. Базовая команда rsync -avz недостаточна для 2 ТБ — нужны продвинутые опции:

rsync -aHAXS \
  --delete \
  --delete-after \
  --partial \
  --partial-dir=.rsync-partial \
  --compress \
  --compress-level=2 \
  --bwlimit=500M \
  --timeout=300 \
  --contimeout=30 \
  --info=progress2,stats2 \
  --exclude='.Thumbs.db' \
  --exclude='*.tmp' \
  --exclude='.DS_Store' \
  -e 'ssh -c aes128-gcm@openssh.com -o Compression=no -T' \
  /data/media/ \
  rsync-user@spb-fs01.meganet.internal:/data/media/

Разберём каждую опцию:

  • -aHAXS — archive + hardlinks + ACLs + xattrs + sparse files. Полное сохранение метаданных.
  • --delete-after — удаляет файлы на приёмнике только после полной синхронизации (безопаснее, чем --delete-before).
  • --partial --partial-dir — недокачанные файлы сохраняются в отдельной директории и докачиваются при следующем запуске вместо начала с нуля.
  • --compress-level=2 — минимальное сжатие. Видеофайлы уже сжаты, и zlib на уровне 6+ только тратит CPU. Уровень 2 эффективен для текстовых и XML-файлов проектов.
  • --bwlimit=500M — ограничение полосы до 500 Мбит/с, чтобы оставить канал для другого трафика.
  • ssh -c aes128-gcm@openssh.com -o Compression=no -T — быстрый шифр AES-GCM с аппаратным ускорением (AES-NI), отключаем SSH-сжатие (rsync сжимает сам), -T отключает аллокацию pseudo-terminal.

SSH-туннель и безопасность

Синхронизация идёт через SSH — это шифрование из коробки. Но для автоматизации нужен беспарольный доступ с минимальными привилегиями:

# На источнике (Москва) генерируем ключ
ssh-keygen -t ed25519 -f /root/.ssh/rsync_key -N '' -C 'rsync-media-sync'

# На приёмнике (СПб) создаём ограниченного пользователя
useradd -r -s /bin/bash -d /data rsync-user
mkdir -p /data/.ssh

# Добавляем ключ с ограничениями
cat >> /data/.ssh/authorized_keys << 'EOF'
command="/usr/bin/rrsync -wo /data/media/",restrict ssh-ed25519 AAAA... rsync-media-sync
EOF

Ключевой элемент — rrsync (restricted rsync). Эта обёртка позволяет SSH-ключу выполнять только rsync-операции в указанной директории. Даже если ключ скомпрометирован, злоумышленник не получит shell-доступ.

Опция -wo (write-only) разрешает только запись — приёмник не может читать данные с источника. Для двусторонней синхронизации используется -rw.

Дополнительно настраиваем firewall на приёмнике:

# Только SSH с IP источника
nft add rule inet filter input ip saddr 185.100.50.10 tcp dport 22 accept
nft add rule inet filter input tcp dport 22 drop

Lsyncd: real-time синхронизация через inotify

Lsyncd (Live Syncing Daemon) — демон, который мониторит файловую систему через inotify и запускает rsync при каждом изменении. Это даёт нам задержку 1–15 секунд вместо ожидания следующего cron-запуска.

# Установка
apt install lsyncd

# Конфигурация
mkdir -p /etc/lsyncd /var/log/lsyncd
-- /etc/lsyncd/lsyncd.conf.lua
settings {
    logfile    = "/var/log/lsyncd/lsyncd.log",
    statusFile = "/var/log/lsyncd/lsyncd.status",
    statusInterval = 10,
    insist = true,        -- продолжать при ошибках
    maxProcesses = 4,     -- параллельные rsync-процессы
    maxDelays = 500,       -- группировать до 500 изменений в один rsync
}

sync {
    default.rsync,
    source = "/data/media/",
    target = "rsync-user@spb-fs01.meganet.internal:/data/media/",
    delay  = 5,            -- ждём 5 секунд после изменения (группировка)

    exclude = {
        '.Thumbs.db',
        '*.tmp',
        '.DS_Store',
        '.rsync-partial/',
    },

    rsync = {
        binary   = "/usr/bin/rsync",
        archive  = true,
        hard_links = true,
        acls     = true,
        xattrs   = true,
        sparse   = true,
        compress = true,
        _extra   = {
            "--compress-level=2",
            "--partial",
            "--partial-dir=.rsync-partial",
            "--bwlimit=500M",
            "--timeout=300",
            "--delete-after",
        },
        rsh = "ssh -c aes128-gcm@openssh.com -o Compression=no -T -i /root/.ssh/rsync_key",
    },
}

Параметр delay = 5 и maxDelays = 500 — ключевые для производительности. Lsyncd не запускает rsync на каждый изменённый файл, а группирует изменения за 5-секундное окно (до 500 файлов) в один вызов rsync. Это критически важно при массовой записи — например, когда видеоредактор сохраняет проект из 200 файлов.

Тюнинг inotify для больших директорий

По умолчанию Linux позволяет мониторить 8192 директории через inotify. При 2 ТБ файлов и сложной структуре каталогов этого катастрофически мало. Lsyncd просто не увидит изменения в глубоких поддиректориях.

# Проверяем текущие лимиты
cat /proc/sys/fs/inotify/max_user_watches
# 8192 — слишком мало

cat /proc/sys/fs/inotify/max_queued_events
# 16384

# Считаем реальное количество директорий
find /data/media -type d | wc -l
# 47832 — нужно минимум 48000 watches

# Устанавливаем с запасом (2x)
cat >> /etc/sysctl.d/90-lsyncd.conf << 'EOF'
fs.inotify.max_user_watches = 131072
fs.inotify.max_queued_events = 65536
fs.inotify.max_user_instances = 512
EOF

sysctl --system

# Проверяем
cat /proc/sys/fs/inotify/max_user_watches
# 131072

Важный нюанс: каждый watch занимает ~1 КБ ядерной памяти. 131 072 watches — это ~128 МБ. Для сервера с 32+ ГБ RAM это незначительно, но на VPS с 2 ГБ стоит учитывать.

Также увеличиваем лимит открытых файлов для процесса lsyncd:

# /etc/security/limits.d/lsyncd.conf
root    soft    nofile    65536
root    hard    nofile    131072

Systemd-интеграция и автоматический перезапуск

Lsyncd должен работать как надёжный системный сервис с автоматическим перезапуском при сбоях:

# /etc/systemd/system/lsyncd.service
[Unit]
Description=Lsyncd - Live Syncing Daemon
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/bin/lsyncd -nodaemon /etc/lsyncd/lsyncd.conf.lua
Restart=always
RestartSec=10
WatchdogSec=300

# Безопасность
NoNewPrivileges=no
ProtectSystem=false
PrivateTmp=true

# Лимиты
LimitNOFILE=131072

# Логирование
StandardOutput=journal
StandardError=journal
SyslogIdentifier=lsyncd

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now lsyncd

# Проверяем статус
systemctl status lsyncd
# ● lsyncd.service - Lsyncd - Live Syncing Daemon
#   Active: active (running) since ...

# Смотрим логи
journalctl -u lsyncd -f --no-pager

WatchdogSec=300 — если lsyncd не отвечает 5 минут (завис), systemd перезапустит его. RestartSec=10 — при сбое ждём 10 секунд перед перезапуском, чтобы не создавать storm при постоянных ошибках (например, недоступен приёмник).

Windows-интеграция через cwRsync и cdc_rsync

В «МедиаГрупп» часть дизайнеров работает на Windows. Им тоже нужна синхронизация рабочих папок с файловым сервером. Мы рассмотрели два варианта:

Вариант 1: cwRsync — порт rsync для Windows с библиотекой Cygwin:

:: C:\scripts\sync-media.bat
@echo off
set CWRSYNC=C:\cwrsync\bin
set PATH=%CWRSYNC%;%PATH%

rsync -aHvz --progress ^
  --partial ^
  --bwlimit=200M ^
  --exclude="Thumbs.db" ^
  --exclude="*.tmp" ^
  -e "ssh -i /cygdrive/c/cwrsync/keys/rsync_key -o StrictHostKeyChecking=no" ^
  /cygdrive/c/Projects/Media/ ^
  rsync-user@msk-fs01.meganet.internal:/data/media/designers/%USERNAME%/

cwRsync работает надёжно, но медленно: Cygwin-прослойка добавляет 20–30% накладных расходов на большие дельты.

Вариант 2: cdc_rsync от Google — использует Content Defined Chunking (CDC) вместо фиксированных блоков rsync. Блоки переменного размера определяются по содержимому файла, что решает проблему сдвига границ при вставках:

:: Синхронизация Windows → Linux
cdc_rsync C:\Projects\Media\* rsync-user@msk-fs01.meganet.internal:/data/media/designers/ -vr

:: Синхронизация Linux → Windows
cdc_rsync rsync-user@msk-fs01.meganet.internal:/data/media/shared/ C:\Projects\Shared\ -vr

В наших тестах на видеофайлах (40 ГБ билд) cdc_rsync работал в 3 раза быстрее cwRsync благодаря оптимизированному алгоритму разбиения на блоки.

Для Windows-клиентов мы настроили Task Scheduler с запуском каждые 10 минут — полноценный inotify на Windows через lsyncd невозможен, но 10-минутный интервал покрывает требования дизайнеров.

Мониторинг и результаты

Для мониторинга синхронизации мы написали простой Prometheus-экспортёр, который парсит lsyncd status file:

#!/usr/bin/env python3
# /opt/monitoring/lsyncd_exporter.py
import re, time
from prometheus_client import start_http_server, Gauge

LSYNCD_STATUS = '/var/log/lsyncd/lsyncd.status'

watches = Gauge('lsyncd_inotify_watches', 'Number of active inotify watches')
pending = Gauge('lsyncd_events_pending', 'Events waiting to sync')
last_sync = Gauge('lsyncd_last_sync_timestamp', 'Last successful sync time')

def collect():
    try:
        with open(LSYNCD_STATUS) as f:
            content = f.read()
        m = re.search(r'Inotify watching (\d+)', content)
        if m: watches.set(int(m.group(1)))
        m = re.search(r'There are (\d+) delays', content)
        if m: pending.set(int(m.group(1)))
        last_sync.set(time.time())
    except Exception:
        pass

start_http_server(9199)
while True:
    collect()
    time.sleep(15)

Alertmanager уведомляет в Telegram, если pending events > 1000 (затор синхронизации) или если lsyncd не обновлял status file более 5 минут (демон завис).

Результаты:

МетрикаДо (ночной cron)После (lsyncd)
Задержка синхронизацииДо 24 часов5–15 секунд
Окно потери данных (RPO)24 часа15 секунд
Нагрузка на канал100% × 3–4 часа ночью5–15% постоянно
Время переключения на DR3–4 часа (восстановление)5 минут (DNS failover)
Синхронизация WindowsРучное копированиеАвтоматическая, каждые 10 мин

Часто задаваемые вопросы

Lsyncd использует inotify для мгновенного обнаружения изменений и запускает rsync только для изменённых файлов. Cron-rsync сканирует всю файловую систему при каждом запуске, что при 2 ТБ занимает 10–15 минут только на построение списка файлов. Lsyncd обеспечивает задержку 5–15 секунд при точечной нагрузке на канал.
Используйте опцию --bwlimit. Значение указывается в KB/s (по умолчанию) или с суффиксом: --bwlimit=500M для 500 Мбит/с. Для lsyncd добавьте в секцию rsync._extra. Также полезно настроить QoS (tc) на уровне ОС для более точного управления приоритетами трафика.
С --delete-after файлы удаляются на приёмнике только после успешной передачи всех новых файлов — это безопаснее, чем --delete-before. Для дополнительной защиты используйте --backup и --backup-dir, чтобы удалённые файлы перемещались в отдельную директорию вместо полного удаления.
Два основных варианта: cwRsync (порт rsync с Cygwin, стабильный, но на 20–30% медленнее) и cdc_rsync от Google (использует Content Defined Chunking, в 3 раза быстрее на больших бинарных файлах). Для автоматизации на Windows используйте Task Scheduler, так как inotify-аналог на Windows через lsyncd не работает.
Проверьте три вещи: 1) лимиты inotify — fs.inotify.max_user_watches должен быть больше числа директорий; 2) параметр maxProcesses в lsyncd — увеличьте до 8 для параллельной синхронизации; 3) полосу пропускания — возможно, bwlimit слишком низкий для объёма изменений. Мониторьте pending events через status file.

Нужна помощь с проектом?

Специалисты АйТи Фреш помогут с архитектурой, DevOps, безопасностью и разработкой — 15+ лет опыта

📞 Связаться с нами
#rsync#lsyncd#синхронизация данных#inotify#cwrsync#ssh tunnel#datacenter sync#real-time replication
Комментарии 0

Оставить комментарий

загрузка...