Docker на Synology: от простого к High-Load
Архитектурное руководство по сети, ресурсам и отказоустойчивости, когда на одном NAS живёт уже не два контейнера, а полноценный стек сервисов.
- Зачем уходить от GUI к Docker Compose
- Шаг 1 — подготовка окружения
- Шаг 2 — сетевой стек: bridge против macvlan
- Шаг 3 — настройка macvlan на практике
- Шаг 4 — Docker Compose для продакшена
- Шаг 5 — лимиты ресурсов и здоровье контейнеров
- Шаг 6 — логи, метрики и автообновления
- High-load паттерны на одном NAS
- Безопасность и хранение данных
- Частые проблемы
Зачем уходить от GUI к Docker Compose
Штатный интерфейс Container Manager отлично подходит, пока на NAS крутится два-три сервиса, которые вы запустили однажды и не трогаете. Проблема начинается, когда сервисов становится десять, часть из них зависит друг от друга по сети, а после каждого обновления DSM хочется быть уверенным, что вся конфигурация — не разрозненные клики в GUI, а один файл, который можно открыть в текстовом редакторе, положить в git и поднять заново на другом NAS за пять минут.
Дальше — про то, как выстроить эту конфигурацию так, чтобы она не разваливалась при росте числа контейнеров: правильная сеть, честные лимиты ресурсов, наблюдаемость и разделение на изолированные проекты.
Шаг 1 — подготовка окружения
01Устанавливаем Container Manager и включаем SSH
В Package Center установите Container Manager (в DSM 7.2 он заменил старый Docker-пакет). Затем в Панели управления → Терминал и SNMP включите службу SSH — большая часть того, что описано дальше, быстрее и надёжнее делать через терминал, чем через вложенные меню GUI.
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-м порту конфликтуют, а список проброшенных портов быстро превращается в таблицу, которую никто не помнит наизусть.
Macvlan решает это иначе: контейнер получает собственный MAC- и IP-адрес прямо в вашей локальной подсети, минуя NAT хоста. Он виден в роутере и в остальной сети как самостоятельное устройство — со своим DNS-именем, без пробросов портов и без риска конфликта портов между сервисами.
Шаг 3 — настройка macvlan на практике
02Создаём сеть с привязкой к физическому интерфейсу
Прежде чем описывать сеть в compose-файле, создайте её на уровне Docker с привязкой к реальному сетевому адаптеру NAS (обычно eth0, уточните командой ip addr):
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-интерфейс в той же подсети:
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
Ниже — как выглядит логика настройки сети в самом Container Manager, если собирать сеть через графический интерфейс, а не командой выше:
Шаг 4 — Docker Compose для продакшена
04Собираем стек с фиксированными адресами
Теперь сеть можно подключить в compose-файле как внешнюю и назначить контейнерам конкретные адреса из зарезервированного диапазона:
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-файлы из интернета:
- Volumes только для чтения (
:ro) там, где контейнеру не нужно писать — так статическая раздача nginx не может случайно испортить исходники при компрометации контейнера. healthcheck— без него Docker считает контейнер живым, даже если процесс внутри завис. Реверс-прокси и оркестрация перезапуска опираются именно на этот статус.- Ротация логов — без
max-sizeлог-файл контейнера может незаметно вырасти до нескольких гигабайт и забить том за месяцы работы.
Шаг 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 — лёгкий веб-просмотрщик логов всех контейнеров без базы данных и почти без потребления ресурсов:
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"
Быстрый снимок состояния стека без веб-интерфейса — прямо в терминале:
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 и оставьте себе окно уведомлений перед обновлением:
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
- Разделяйте стек на отдельные compose-проекты по каталогам (
web/,monitoring/,data/) — так обновление одного проекта не требует пересборки остальных и не тянет за собой лишние рестарты. - Общая сеть между проектами — заведите одну
externalсеть (какmacvlan_lanвыше) и подключайте её из разных compose-файлов, вместо того чтобы плодить изолированные bridge-сети и городить проброс между ними. - Восходящий реверс-прокси — один NPM или Traefik перед всеми веб-сервисами, а не отдельный порт на каждый наружу.
- ulimits для БД-контейнеров — PostgreSQL и подобные сервисы под нагрузкой упираются в лимит открытых файловых дескрипторов на уровне DSM раньше, чем в CPU. Поднимите
nofileявно в compose. - Раздельные тома для холодных и горячих данных — если в NAS есть NVMe-кэш или SSD-том, положите на него базы данных, а архивные/статические файлы держите на HDD-массиве.
Безопасность и хранение данных
Базовое правило остаётся тем же, что и в исходной версии этой статьи: никогда не храните изменяемые данные внутри слоя контейнера — только в bind mount на отказоустойчивый том NAS вроде /volume1/docker/, чтобы Hyper Backup покрывал их автоматически. К этому стоит добавить ещё три пункта, которые обычно всплывают уже после инцидента, а не до:
- Не мапьте docker.sock без необходимости — доступ к сокету Docker внутри контейнера фактически равен root-доступу к хосту. Давайте его только тем сервисам, которым он реально нужен (Dozzle, Watchtower, Portainer).
read_only: trueдля контейнеров, которым не нужно ничего писать в собственную файловую систему, с отдельнымtmpfsпод временные файлы, если приложение того требует.- Отдельная учётная запись
docker-usersбез прав администратора DSM для SSH-доступа к обслуживанию контейнеров, вместо постоянной работы под встроенным admin.
Частые проблемы
| Симптом | Вероятная причина |
|---|---|
| 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.