Synology Lab · high-load

Docker на Synology: от простого к High-Load

Архитектурное руководство по сети, ресурсам и отказоустойчивости, когда на одном NAS живёт уже не два контейнера, а полноценный стек сервисов.

Зачем уходить от GUI к Docker Compose

Штатный интерфейс Container Manager отлично подходит, пока на NAS крутится два-три сервиса, которые вы запустили однажды и не трогаете. Проблема начинается, когда сервисов становится десять, часть из них зависит друг от друга по сети, а после каждого обновления DSM хочется быть уверенным, что вся конфигурация — не разрозненные клики в GUI, а один файл, который можно открыть в текстовом редакторе, положить в git и поднять заново на другом NAS за пять минут.

Дальше — про то, как выстроить эту конфигурацию так, чтобы она не разваливалась при росте числа контейнеров: правильная сеть, честные лимиты ресурсов, наблюдаемость и разделение на изолированные проекты.

◆ важная оговорка про скриншоты
Интерфейс DSM регулярно меняется между версиями, а сами формы — собственность Synology, поэтому вместо копий их интерфейса ниже — авторские схемы, которые показывают ровно те же поля и логику настройки. Названия полей соответствуют актуальному Container Manager в DSM 7.2.

Шаг 1 — подготовка окружения

01Устанавливаем Container Manager и включаем SSH

В Package Center установите Container Manager (в DSM 7.2 он заменил старый Docker-пакет). Затем в Панели управления → Терминал и SNMP включите службу SSH — большая часть того, что описано дальше, быстрее и надёжнее делать через терминал, чем через вложенные меню GUI.

ssh · admin@nas
mkdir -p /volume1/docker/{web,shared-net,monitoring}
mkdir -p /volume1/docker/web/html
sudo synogroup --add docker-users admin
docker --version
docker compose version

Структура каталогов на этом этапе — не формальность. Один каталог на проект в /volume1/docker/ означает, что бэкап всей инфраструктуры контейнеров — это бэкап одной родительской папки через Hyper Backup, а не поиск раскиданных томов по всему разделу.

Шаг 2 — сетевой стек: bridge против macvlan

Стандартный режим Bridge, который DSM создаёт по умолчанию, отдаёт всем контейнерам общий IP хоста и требует пробрасывать каждый порт вручную через -p. Работает, но плохо масштабируется: два веб-сервиса на 80-м порту конфликтуют, а список проброшенных портов быстро превращается в таблицу, которую никто не помнит наизусть.

bridge · общий ip хоста 192.168.1.10 (nas) :8080 → app_a :8081 → app_b macvlan · свой ip у каждого app_a · 192.168.1.51 app_b · 192.168.1.52 db · 192.168.1.53 каждый — как отдельное устройство в вашей сети

Macvlan решает это иначе: контейнер получает собственный MAC- и IP-адрес прямо в вашей локальной подсети, минуя NAT хоста. Он виден в роутере и в остальной сети как самостоятельное устройство — со своим DNS-именем, без пробросов портов и без риска конфликта портов между сервисами.

⚠ ограничение, о котором часто забывают
По умолчанию сам NAS не может обращаться к контейнерам в режиме macvlan напрямую — драйвер изолирует хост от собственных дочерних интерфейсов. Если нужен доступ с самого NAS (например, health-check из DSM), поднимайте дополнительный macvlan-шим интерфейс на хосте — это описано в шаге 3.

Шаг 3 — настройка macvlan на практике

02Создаём сеть с привязкой к физическому интерфейсу

Прежде чем описывать сеть в compose-файле, создайте её на уровне Docker с привязкой к реальному сетевому адаптеру NAS (обычно eth0, уточните командой ip addr):

ssh · admin@nas
docker network create -d macvlan \
  --subnet=192.168.1.0/24 \
  --gateway=192.168.1.1 \
  --ip-range=192.168.1.240/28 \
  -o parent=eth0 \
  macvlan_lan

Параметр --ip-range здесь принципиален: он резервирует под контейнеры узкий диапазон адресов (в примере — 16 адресов, 192.168.1.240–255), который не пересекается с пулом DHCP на вашем роутере. Настройте на роутере статическую резервацию или исключение этого диапазона из DHCP заранее, иначе рано или поздно роутер выдаст тот же адрес обычному устройству.

03Возвращаем хосту доступ к контейнерам (macvlan-шим)

Чтобы сам NAS видел контейнеры в этой сети — например, для мониторинга или health-check из DSM — поднимите на хосте отдельный virtual-интерфейс в той же подсети:

ssh · admin@nas
sudo ip link add macvlan-shim link eth0 type macvlan mode bridge
sudo ip addr add 192.168.1.254/32 dev macvlan-shim
sudo ip link set macvlan-shim up
sudo ip route add 192.168.1.240/28 dev macvlan-shim
✓ совет
Эти команды не переживают перезагрузку NAS. Оформите их отдельным скриптом и повесьте на «Триггерные события» → «Загрузка» в Панели управления DSM, чтобы шим поднимался автоматически при каждом старте системы.

Ниже — как выглядит логика настройки сети в самом Container Manager, если собирать сеть через графический интерфейс, а не командой выше:

container manager → сеть → создать драйвер сети macvlan родительский интерфейс eth0 подсеть 192.168.1.0/24 шлюз 192.168.1.1 диапазон ip для контейнеров 192.168.1.240 – 192.168.1.255

Шаг 4 — Docker Compose для продакшена

04Собираем стек с фиксированными адресами

Теперь сеть можно подключить в compose-файле как внешнюю и назначить контейнерам конкретные адреса из зарезервированного диапазона:

docker-compose.yml
services:
  app:
    image: nginx:1.27-alpine
    container_name: production_web
    restart: unless-stopped
    volumes:
      - /volume1/docker/web/html:/usr/share/nginx/html:ro
    networks:
      macvlan_lan:
        ipv4_address: 192.168.1.241
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost"]
      interval: 30s
      timeout: 5s
      retries: 3
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
    mem_limit: 256m
    cpus: 0.5

networks:
  macvlan_lan:
    external: true

Три детали, которые часто упускают, копируя типовые compose-файлы из интернета:

Шаг 5 — лимиты ресурсов и здоровье контейнеров

На NAS с фиксированным объёмом RAM и без свопа под контейнеры "по умолчанию" — самый частый сценарий деградации всего стека: один прожорливый контейнер (обычно — база данных под нагрузкой или медиасервер во время транскодирования) выедает всю доступную память, и OOM killer начинает произвольно убивать соседние процессы, включая сам DSM.

Тип сервисаРекомендуемый лимит RAMРекомендуемый лимит CPU
Лёгкий прокси / статика (nginx, NPM)128–256 МБ0.25–0.5 ядра
Менеджер паролей / API-сервис256–512 МБ0.5 ядра
PostgreSQL / MySQL под умеренной нагрузкой512 МБ – 1 ГБ1 ядро
Медиасервер с транскодированием1–2 ГБ2+ ядра (или квота, равная свободным)

Задавайте лимиты через mem_limit и cpus прямо на уровне сервиса — это работает в обычном docker compose up без Swarm-режима, в отличие от секции deploy.resources, которая на голом Compose без Swarm в части версий попросту игнорируется.

Лимит ресурсов — это не про то, чтобы урезать контейнер. Это про то, чтобы один упавший в цикл сервис не положил все остальные.

Шаг 6 — логи, метрики и автообновления

05Смотрим, что происходит со стеком в реальном времени

Встроенная вкладка логов в Container Manager неудобна для стека из десятка сервисов. Проще поднять Dozzle — лёгкий веб-просмотрщик логов всех контейнеров без базы данных и почти без потребления ресурсов:

docker-compose.yml · monitoring
services:
  dozzle:
    image: amir20/dozzle:latest
    container_name: dozzle
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    ports:
      - "127.0.0.1:9999:8080"

Быстрый снимок состояния стека без веб-интерфейса — прямо в терминале:

ssh · admin@nas
docker stats --no-stream --format \
  "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"

NAME                CPU %     MEM USAGE / LIMIT
production_web      0.42%     18.4MiB / 256MiB
vaultwarden          0.11%     31.2MiB / 256MiB
nginx-proxy-manager  0.87%     64.5MiB / 256MiB
postgres             2.15%     212MiB / 512MiB

06Автообновление образов — осторожно

Watchtower умеет сам подтягивать новые версии образов, но на проде с фиксированными тегами (как в шаге 4) это скорее риск, чем удобство: минорное обновление образа может незаметно сломать совместимость. Если всё же используете его — ограничьте область labels и оставьте себе окно уведомлений перед обновлением:

docker-compose.yml · watchtower
services:
  watchtower:
    image: containrrr/watchtower
    container_name: watchtower
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    command: --schedule "0 0 4 * * *" --label-enable --cleanup

Флаг --label-enable означает, что Watchtower обновит только контейнеры с явной меткой com.centurylinklabs.watchtower.enable=true — так вы решаете для каждого сервиса отдельно, доверяете ли вы автообновлению.

High-load паттерны на одном NAS

Безопасность и хранение данных

Базовое правило остаётся тем же, что и в исходной версии этой статьи: никогда не храните изменяемые данные внутри слоя контейнера — только в bind mount на отказоустойчивый том NAS вроде /volume1/docker/, чтобы Hyper Backup покрывал их автоматически. К этому стоит добавить ещё три пункта, которые обычно всплывают уже после инцидента, а не до:

Частые проблемы

СимптомВероятная причина
NAS не пингует контейнер в macvlanне поднят macvlan-шим интерфейс на хосте (шаг 3)
Контейнер получил "чужой" IP из DHCP роутерадиапазон --ip-range не исключён из DHCP-пула на роутере
DSM внезапно завис или перезагрузилсяотсутствие mem_limit у одного из сервисов — OOM killer задел систему
Логи контейнера съели весь том за пару месяцевне настроена ротация логов (options.max-size)
Изменения ресурсов в deploy.resources не применяютсяключ deploy работает только в Swarm-режиме — используйте mem_limit/cpus

Собранные вместе, эти шесть шагов — не разовая настройка, а рабочий чек-лист: при добавлении каждого нового сервиса в стек стоит пройтись по нему заново — сеть, лимиты, health-check, логи — прежде чем считать сервис готовым к продакшену на вашем NAS.