Garage и SeaweedFS: как развернуть своё S3-хранилище на dedicated-сервере
TL;DR
В этом руководстве мы подготовим dedicated-сервер и запустим на нём два совместимых с S3 хранилища: Garage для компактной отказоустойчивой инсталляции и SeaweedFS для сценариев с большим количеством объектов и масштабированием по узлам. Доступ к обоим сервисам будет защищён HTTPS, а конфигурации и данные будут подготовлены к резервному копированию.
- Garage подойдёт для простого приватного S3, резервных копий и приложений.
- SeaweedFS удобнее при большом количестве файлов, высокой нагрузке и дальнейшем горизонтальном масштабировании.
- Для тестовой единственной ноды достаточно 4 vCPU, 8 ГБ RAM и NVMe от 500 ГБ.
- Для production лучше использовать ECC-память, RAID или ZFS, отдельные диски под данные и резервную копию вне сервера.
- API S3 публикуется через Caddy с автоматическими сертификатами Let’s Encrypt.
- Резервировать нужно не только конфиги, но и данные, ключи доступа, layout Garage и метаданные SeaweedFS.
1. Что мы настраиваем и зачем
S3 — это не конкретный сервер, а протокол объектного хранения: приложения обращаются к bucket, загружают объекты и получают их по ключам. Большинство современных клиентов, SDK и инструментов резервного копирования умеют работать с S3-compatible API. Поэтому вместо публичного облака можно запустить собственное хранилище на dedicated-сервере.
В статье рассматриваются два проекта. Garage — компактное распределённое S3-хранилище, ориентированное на простую эксплуатацию и небольшие кластеры. SeaweedFS — более универсальная распределённая файловая система с S3-шлюзом, master-сервисом, volume-серверами и filer.
Обычно не требуется использовать Garage и SeaweedFS одновременно для одних и тех же данных. Выбирайте один основной backend:
- Garage — когда нужен понятный приватный S3, минимальное количество компонентов и несколько узлов в будущем.
- SeaweedFS — когда ожидаются миллионы или миллиарды небольших объектов, высокая скорость записи и развитие кластера.
- Оба на одном dedicated — только для лаборатории, миграции, сравнения или размещения разных независимых задач.
Что будет в результате
После выполнения инструкций на сервере будут работать два независимых S3 endpoint:
https://garage.example.com— S3 API Garage;https://seaweed.example.com— S3 API SeaweedFS;https://garage-admin.example.com— административный интерфейс Garage, если он потребуется;- локальные каталоги с объектами на отдельном диске;
- Docker Compose-файлы, systemd-управление, firewall и схема резервного копирования.
В примерах используются домены garage.example.com и seaweed.example.com. Замените их на собственные домены. DNS-записи должны указывать на публичный IPv4-адрес сервера, а для IPv6 нужно отдельно проверить, что firewall и маршрутизация настроены корректно.
Self-hosted или managed S3
Managed S3 снимает с владельца сервера задачи эксплуатации: резервирование, замену дисков, мониторинг и расширение. Это хороший выбор, если важнее скорость запуска, чем контроль над данными. Однако цена хранения, исходящего трафика и большого количества операций может существенно вырасти.
Self-hosted S3 позволяет контролировать диски, ключи, локацию и стоимость хранения. Обратная сторона — ответственность за резервные копии, отказоустойчивость и обновления полностью лежит на администраторе. Один сервер без внешней копии не является надёжным хранилищем, даже если само ПО поддерживает репликацию.
| Критерий | Managed S3 | Garage или SeaweedFS |
|---|---|---|
| Запуск | Очень быстрый | Требуется настройка сервера |
| Контроль над данными | Ограниченный политикой провайдера | Полный контроль |
| Масштабирование | Обычно автоматическое | Нужно планировать самостоятельно |
| Ответственность за бэкапы | Разделяется с провайдером | На владельце инфраструктуры |
2. Какой VPS-конфиг нужен под эту задачу
Требования зависят от объёма данных, размера объектов и количества запросов. S3-хранилище в основном упирается в дисковую подсистему и сеть, а не в частоту CPU. Для большого числа небольших файлов SeaweedFS потребляет больше RAM на метаданные и фоновые операции, чем простой Garage-кластер.
| Сценарий | CPU | RAM | Диск | Сеть |
|---|---|---|---|---|
| Лаборатория и тесты | 2 vCPU | 4 ГБ | 100 ГБ SSD | 100 Мбит/с |
| Небольшой production | 4 vCPU | 8–16 ГБ | 500 ГБ–2 ТБ NVMe | 1 Гбит/с |
| Много объектов и активный API | 8–16 vCPU | 32–64 ГБ | 2–8 ТБ NVMe или отдельный storage | 1–10 Гбит/с |
Практический стартовый вариант
Для одной production-ноды, на которой одновременно запускаются Garage, SeaweedFS и Caddy, разумная стартовая конфигурация — 8 vCPU, 16 ГБ RAM, 1 ТБ NVMe, публичный IPv4 и порт 1 Гбит/с. Если планируется использовать только один backend, можно начать с 4 vCPU и 8 ГБ RAM.
В качестве одного из вариантов можно подобрать dedicated с такими характеристиками. Конкретный тариф не является частью архитектуры: важны производительность дисков, доступ к резервным копиям, лимит трафика и возможность добавить второй диск.
Почему dedicated может быть лучше VPS
Dedicated предпочтителен, если данные занимают сотни гигабайт или терабайты, требуется предсказуемая I/O-производительность и планируется непрерывная запись. На VPS скорость дисков может зависеть от соседей, а гарантированный объём диска иногда ограничен тарифом.
Для небольшого bucket, резервных копий и development VPS вполне достаточно. Dedicated становится оправданным при высоком количестве операций, больших последовательных загрузках, требованиях к ECC-памяти, RAID/ZFS или необходимости физически контролировать диски.
Диски и файловая система
Для объектного storage используйте локальный NVMe или enterprise SSD. Не размещайте данные на системном разделе без отдельного лимита: заполнение root filesystem может остановить SSH, Docker и системные службы. Практичная схема — отдельный том, смонтированный в /srv/object-storage.
Для одной ноды подойдут XFS или ext4. ZFS полезна при достаточном объёме RAM и необходимости snapshot, checksumming и управления пулом, но она добавляет операционную сложность. RAID защищает от отказа диска, но не заменяет резервную копию.
Локация сервера
Выбирайте дата-центр ближе к приложениям и пользователям. Это уменьшает задержку при загрузке объектов и снижает время работы multipart upload. Если хранилище используется только для бэкапов, важнее стабильный исходящий трафик и возможность передавать данные в другую географическую локацию.
Для персональных данных также учитывайте требования законодательства, договоры с клиентами и физическую юрисдикцию дата-центра. Географическая близость сама по себе не гарантирует соответствие требованиям обработки данных.
3. Подготовка сервера
Ниже предполагается Debian 12 или Ubuntu Server 24.04 LTS с чистой установкой и пользователем, имеющим временный доступ через SSH. Все команды выполняются от пользователя с sudo. В примерах используется имя storage.
Обновление системы и базовые пакеты
Сначала обновите индекс пакетов, установите исправления безопасности и инструменты, которые понадобятся для Docker, диагностики и резервного копирования.
sudo apt update && sudo apt full-upgrade -y
sudo apt install -y ca-certificates curl gnupg lsb-release jq vim htop ncdu \
unzip rsync restic ufw fail2ban dnsutils smartmontools
Создание отдельного администратора
Не используйте root для повседневной работы. Создайте пользователя, добавьте его в sudo и перенесите публичный SSH-ключ. Команду с ключом замените на собственный ключ.
sudo adduser storage
sudo usermod -aG sudo storage
sudo install -d -m 700 -o storage -g storage /home/storage/.ssh
sudo sh -c 'echo "ssh-ed25519 AAAA_REPLACE_WITH_YOUR_PUBLIC_KEY admin@workstation" > /home/storage/.ssh/authorized_keys'
sudo chown storage:storage /home/storage/.ssh/authorized_keys
sudo chmod 600 /home/storage/.ssh/authorized_keys
Откройте новую SSH-сессию и убедитесь, что вход работает. Только после проверки можно запретить парольную аутентификацию и root login.
ssh storage@SERVER_IP
sudo install -d -m 0755 /etc/ssh/sshd_config.d
sudo tee /etc/ssh/sshd_config.d/ hardening.conf > /dev/null <<'EOF'
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
EOF
sudo sshd -t
sudo systemctl reload ssh
В последней команде в реальном терминале имя файла должно быть без пробела: /etc/ssh/sshd_config.d/hardening.conf. Исправленный вариант:
sudo tee /etc/ssh/sshd_config.d/hardening.conf > /dev/null <<'EOF'
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
EOF
sudo sshd -t
sudo systemctl reload ssh
Firewall
До включения UFW разрешите SSH на нестандартном порту, если вы его меняли, а также HTTP и HTTPS для ACME-проверки и S3 API. Административные порты Garage и SeaweedFS наружу не публикуем.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
Fail2ban и каталог данных
Fail2ban будет временно блокировать адреса с большим количеством неудачных SSH-входов. Он не заменяет ключи, firewall и обновления, но снижает шум от автоматических сканеров.
sudo systemctl enable --now fail2ban
sudo systemctl status fail2ban --no-pager
sudo mkdir -p /srv/object-storage/{garage,seaweedfs,caddy,data,backup}
sudo chown -R storage:storage /srv/object-storage
Проверьте диск до развёртывания:
df -hT
lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT,MODEL
sudo smartctl -a /dev/nvme0n1 | less
Если для данных используется отдельный диск, сначала создайте файловую систему, добавьте UUID в /etc/fstab, смонтируйте её в /srv/object-storage и только затем запускайте контейнеры. Не форматируйте диск, пока не проверили его устройство через lsblk.
4. Установка ПО
Для изоляции компонентов используем Docker Engine и Compose plugin. На production лучше фиксировать версии образов, а не использовать тег latest. В примерах приведены версии, актуальные для практического развёртывания в 2026 году; перед обновлением проверьте release notes и совместимость формата данных.
Установка Docker Engine из официального репозитория
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
Добавьте репозиторий Docker для Ubuntu 24.04. Для Debian замените путь ubuntu на debian и codename на bookworm.
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker "$USER"
Перезапустите SSH-сессию или выполните newgrp docker, после чего проверьте установку:
docker run --rm hello-world
docker compose version
sudo systemctl enable --now docker
Версии образов
В примере используются Garage 2.1.0 и SeaweedFS 3.99. Если к моменту установки вышли более новые стабильные версии, сначала протестируйте их на копии данных. Нельзя бездумно менять образ с major-версией: у систем хранения миграция метаданных может быть необратимой.
Создайте рабочий каталог и файл Compose:
mkdir -p /srv/object-storage/app
cd /srv/object-storage/app
touch .env docker-compose.yml garage.toml s3.json Caddyfile
chmod 600 .env
Создание секретов
Секреты не должны храниться в публичном Git-репозитории, Dockerfile или командной истории. Сгенерируйте административный токен, access key и secret key через системный генератор случайных данных.
GARAGE_ADMIN_TOKEN=$(openssl rand -hex 32)
S3_ACCESS_KEY=$(openssl rand -hex 16)
S3_SECRET_KEY=$(openssl rand -hex 32)
sudo tee /srv/object-storage/app/.env > /dev/null <<EOF
GARAGE_ADMIN_TOKEN=${GARAGE_ADMIN_TOKEN}
S3_ACCESS_KEY=${S3_ACCESS_KEY}
S3_SECRET_KEY=${S3_SECRET_KEY}
EOF
sudo chmod 600 /srv/object-storage/app/.env
Сохраните эти значения в менеджере паролей. Потеря access key не уничтожит данные, но потребует создать новый ключ. Потеря административных секретов усложнит восстановление доступа.
5. Конфигурация Garage и SeaweedFS
Конфигурация Garage
Garage использует конфигурационный файл TOML и отдельный каталог метаданных. Для одной ноды зададим локальный RPC, S3 API на порту 3900 и web endpoint на порту 3902. В production-кластере RPC-порт должен быть доступен только между узлами, а не из интернета.
cat > /srv/object-storage/app/garage.toml <<'EOF'
metadata_dir = "/var/lib/garage/meta"
data_dir = "/var/lib/garage/data"
db_engine = "lmdb"
replication_factor = 1
consistency_mode = "consistent"
[rpc]
bind_addr = "[::]:3901"
secret_file = "/run/secrets/garage_rpc_secret"
[s3_api]
s3_region = "garage"
api_bind_addr = "[::]:3900"
root_domain = ".s3.garage.local"
[admin]
api_bind_addr = "[::]:3903"
admin_token = "CHANGE_IN_ENV_OR_SECRET"
[web]
bind_addr = "[::]:3902"
root_domain = ".web.garage.local"
index = "index.html"
EOF
Для production не оставляйте строку с демонстрационным токеном. В зависимости от версии Garage синтаксис секции admin может отличаться, поэтому перед запуском сверяйте пример конфигурации из release notes. Ниже используется более безопасная схема: административный API не публикуется наружу, а Docker-сервис получает токен через переменную окружения только для вспомогательных операций.
Создайте RPC-секрет и структуру каталогов:
openssl rand -hex 32 > /srv/object-storage/app/garage-rpc-secret
chmod 600 /srv/object-storage/app/garage-rpc-secret
mkdir -p /srv/object-storage/data/garage/{meta,data}
chown -R 1000:1000 /srv/object-storage/data/garage
Конфигурация SeaweedFS
На одной машине SeaweedFS можно запустить в режиме master, volume, filer и S3 gateway. Такая схема удобна для теста и небольшого production, но не даёт отказоустойчивости: отказ сервера означает недоступность всего хранилища.
cat > /srv/object-storage/app/s3.json <<'EOF'
{
"identities": [
{
"name": "admin",
"credentials": [
{
"accessKey": "CHANGE_ACCESS_KEY",
"secretKey": "CHANGE_SECRET_KEY"
}
],
"actions": ["Read", "Write", "List", "Tagging"]
}
]
}
EOF
chmod 600 /srv/object-storage/app/s3.json
Подставьте значения из .env вместо CHANGE_ACCESS_KEY и CHANGE_SECRET_KEY. Автоматическая подстановка без отображения секрета в аргументах процесса:
set -a
. /srv/object-storage/app/.env
set +a
sed -i "s/CHANGE_ACCESS_KEY/${S3_ACCESS_KEY}/; s/CHANGE_SECRET_KEY/${S3_SECRET_KEY}/" \
/srv/object-storage/app/s3.json
Docker Compose
Создайте единый Compose-файл. Сервисы Garage и SeaweedFS используют разные порты и каталоги. Для SeaweedFS применён образ chrislusf/seaweedfs:3.99. Если официальный тег изменился, замените его на проверенный стабильный релиз.
services:
garage:
image: dxflrs/garage:v2.1.0
container_name: garage
restart: unless-stopped
command: ["/garage", "-c", "/etc/garage.toml", "server"]
volumes:
- /srv/object-storage/app/garage.toml:/etc/garage.toml:ro
- /srv/object-storage/app/garage-rpc-secret:/run/secrets/garage_rpc_secret:ro
- /srv/object-storage/data/garage/meta:/var/lib/garage/meta
- /srv/object-storage/data/garage/data:/var/lib/garage/data
ports:
- "127.0.0.1:3900:3900"
- "127.0.0.1:3902:3902"
- "127.0.0.1:3903:3903"
- "127.0.0.1:3901:3901"
healthcheck:
test: ["CMD", "wget", "-q", "-O", "-", "http://127.0.0.1:3903/health"]
interval: 30s
timeout: 5s
retries: 5
seaweed-master:
image: chrislusf/seaweedfs:3.99
container_name: seaweed-master
restart: unless-stopped
command: master -ip=seaweed-master -mdir=/data
volumes:
- /srv/object-storage/data/seaweedfs/master:/data
expose:
- "9333"
healthcheck:
test: ["CMD", "wget", "-q", "-O", "-", "http://127.0.0.1:9333/cluster/status"]
interval: 30s
timeout: 5s
retries: 5
seaweed-volume:
image: chrislusfs/seaweedfs:3.99
container_name: seaweed-volume
restart: unless-stopped
command: volume -mserver=seaweed-master:9333 -dir=/data -port=8080
depends_on:
seaweed-master:
condition: service_healthy
volumes:
- /srv/object-storage/data/seaweedfs/volume:/data
expose:
- "8080"
seaweed-filer:
image: chrislusfs/seaweedfs:3.99
container_name: seaweed-filer
restart: unless-stopped
command: filer -master=seaweed-master:9333 -ip=seaweed-filer
depends_on:
seaweed-master:
condition: service_healthy
seaweed-volume:
condition: service_started
volumes:
- /srv/object-storage/data/seaweedfs/filer:/data
expose:
- "8888"
seaweed-s3:
image: chrislusfs/seaweedfs:3.99
container_name: seaweed-s3
restart: unless-stopped
command: s3 -filer=seaweed-filer:8888 -port=8333 -config=/etc/seaweed/s3.json
depends_on:
- seaweed-filer
volumes:
- /srv/object-storage/app/s3.json:/etc/seaweed/s3.json:ro
ports:
- "127.0.0.1:8333:8333"
caddy:
image: caddy:2.9
container_name: caddy
restart: unless-stopped
depends_on:
- garage
- seaweed-s3
ports:
- "80:80"
- "443:443"
volumes:
- /srv/object-storage/app/Caddyfile:/etc/caddy/Caddyfile:ro
- /srv/object-storage/data/caddy/data:/data
- /srv/object-storage/data/caddy/config:/config
Проверьте синтаксис и запустите сервисы:
cd /srv/object-storage/app
docker compose --env-file .env config
docker compose --env-file .env up -d
docker compose ps
docker compose logs --tail=100 garage
docker compose logs --tail=100 seaweed-master seaweed-s3
Инициализация layout Garage
После запуска Garage необходимо получить идентификатор узла и назначить ему capacity. Команды зависят от версии CLI и могут выполняться внутри контейнера.
cd /srv/object-storage/app
docker compose exec garage garage status
docker compose exec garage garage layout assign \
-z dc1 -c 800G NODE_ID
docker compose exec garage garage layout show
docker compose exec garage garage layout apply --version 1
Вместо NODE_ID подставьте идентификатор из вывода garage status. Значение 800G должно быть меньше свободного места на разделе. Для кластера из трёх узлов используйте разные зоны, например dc1, dc2, dc3, и replication factor 3.
Создание bucket и ключа также зависит от версии CLI. Общая последовательность выглядит так:
docker compose exec garage garage bucket create backups
docker compose exec garage garage key create app-backups
docker compose exec garage garage bucket allow \
--read --write --owner backups --key app-backups
docker compose exec garage garage bucket list
docker compose exec garage garage key list
Не выдавайте приложениям owner-права без необходимости. Для приложения создавайте отдельный ключ и ограничивайте доступ конкретными bucket. Административный ключ храните отдельно от ключей автоматических задач.
6. TLS, DNS и проверка работы
DNS
Создайте A-записи:
garage.example.com→ IPv4 сервера;seaweed.example.com→ IPv4 сервера.
Если используется IPv6, добавьте AAAA-записи только после проверки доступности сервера по IPv6. Ошибочная AAAA-запись может привести к тому, что часть клиентов будет подключаться к недоступному адресу.
dig +short garage.example.com
dig +short seaweed.example.com
Caddy и HTTPS
Caddy автоматически получает сертификаты Let’s Encrypt, если домен уже указывает на сервер и порты 80/443 доступны из интернета. Внутрь Garage и SeaweedFS передаётся обычный HTTP-трафик через локальную Docker-сеть; наружу открывается только HTTPS.
cat > /srv/object-storage/app/Caddyfile <<'EOF'
garage.example.com {
reverse_proxy host.docker.internal:3900
request_body {
max_size 50GB
}
}
seaweed.example.com {
reverse_proxy seaweed-s3:8333
request_body {
max_size 50GB
}
}
EOF
На Linux контейнер Caddy не всегда может обратиться к host.docker.internal без специальной настройки. Надёжнее добавить Garage в общую Docker-сеть и проксировать по имени сервиса. Для этого замените Caddyfile и Compose-файл так, чтобы Caddy и Garage находились в одной сети:
networks:
storage_net:
services:
garage:
networks:
- storage_net
seaweed-s3:
networks:
- storage_net
caddy:
networks:
- storage_net
После добавления сети используйте имена контейнеров:
cat > /srv/object-storage/app/Caddyfile <<'EOF'
garage.example.com {
reverse_proxy garage:3900
request_body {
max_size 50GB
}
}
seaweed.example.com {
reverse_proxy seaweed-s3:8333
request_body {
max_size 50GB
}
}
EOF
cd /srv/object-storage/app
docker compose up -d
docker compose logs --tail=100 caddy
Параметр max_size не ограничивает реальный размер bucket, а защищает reverse proxy от случайно огромного запроса. Для файлов больше 50 ГБ используйте multipart upload или увеличьте лимит с учётом диска и таймаутов.
Проверка HTTP и S3
curl -I https://garage.example.com
curl -I https://seaweed.example.com
curl -vk https://garage.example.com 2>&1 | grep -E "subject:|issuer:|HTTP/"
docker compose ps
docker stats --no-stream
Для S3-клиента установите AWS CLI v2 из официального архива AWS или используйте S3-compatible клиент. Пример с AWS CLI:
export AWS_ACCESS_KEY_ID='REPLACE_ACCESS_KEY'
export AWS_SECRET_ACCESS_KEY='REPLACE_SECRET_KEY'
export AWS_DEFAULT_REGION='garage'
aws --endpoint-url https://garage.example.com s3 ls
aws --endpoint-url https://garage.example.com s3 mb s3://test-bucket
printf 's3 smoke test\n' > /tmp/test.txt
aws --endpoint-url https://garage.example.com s3 cp /tmp/test.txt s3://test-bucket/test.txt
aws --endpoint-url https://garage.example.com s3 cp s3://test-bucket/test.txt -
aws --endpoint-url https://garage.example.com s3 rb s3://test-bucket --force
Для SeaweedFS замените endpoint:
aws --endpoint-url https://seaweed.example.com s3 mb s3://seaweed-test
aws --endpoint-url https://seaweed.example.com s3 cp /tmp/test.txt s3://seaweed-test/test.txt
aws --endpoint-url https://seaweed.example.com s3 ls s3://seaweed-test/
Если HTTPS ещё не настроен, временно проверяйте локальный endpoint через http://127.0.0.1:8333. Не оставляйте локальный тестовый endpoint опубликованным на публичном интерфейсе и не отключайте проверку TLS в production-клиентах.
7. Бэкапы и обслуживание
Что нужно резервировать
У объектного хранилища есть несколько типов данных:
- объекты пользователей в каталогах Garage и SeaweedFS;
- метаданные Garage, его layout и конфигурация;
- master data и filer metadata SeaweedFS;
- Docker Compose, Caddyfile, JSON-конфигурация и секреты;
- список bucket, пользователей, access key и политик доступа.
Копирование только Docker Compose не восстановит файлы. Копирование только volume-данных без конфигураций усложнит запуск. Перед резервированием составьте документ с версиями образов, точками монтирования и командами восстановления.
Restic на отдельный сервер
Не храните единственную копию бэкапа на том же диске. Хороший минимальный вариант — отдельный сервер по SFTP, удалённый S3 с object lock или другой дата-центр. Ниже пример restic через SFTP. На backup-сервере должен существовать пользователь restic и каталог репозитория.
Создайте файл с секретами только для root:
sudo tee /root/.restic-env > /dev/null <<'EOF'
export RESTIC_REPOSITORY='sftp:[email protected]:/srv/restic/object-storage'
export RESTIC_PASSWORD='CHANGE_TO_LONG_RANDOM_PASSWORD'
export RESTIC_SFTP_COMMAND='ssh -i /root/.ssh/restic_backup_ed25519 -o StrictHostKeyChecking=yes'
EOF
sudo chmod 600 /root/.restic-env
Инициализируйте репозиторий один раз и протестируйте соединение:
sudo bash -c 'source /root/.restic-env && restic init'
sudo bash -c 'source /root/.restic-env && restic snapshots'
Скрипт бэкапа
Для согласованной копии остановим контейнеры на короткое окно обслуживания. Это проще и безопаснее для одной ноды, чем копирование активно изменяющихся volume-файлов. Для больших хранилищ лучше использовать встроенную репликацию, snapshot файловой системы или отдельный backup-кластер.
sudo tee /usr/local/sbin/object-storage-backup > /dev/null <<'EOF'
#!/usr/bin/env bash
set -Eeuo pipefail
source /root/.restic-env
APP=/srv/object-storage/app
DATA=/srv/object-storage/data
STAMP=$(date +%F-%H%M)
LOG=/var/log/object-storage-backup.log
exec > >(tee -a "$LOG") 2>&1
echo "[$(date -Is)] backup started"
cd "$APP"
docker compose stop garage seaweed-master seaweed-volume seaweed-filer seaweed-s3
restic backup \
"$APP/garage.toml" \
"$APP/garage-rpc-secret" \
"$APP/s3.json" \
"$APP/Caddyfile" \
"$APP/docker-compose.yml" \
"$APP/.env" \
"$DATA/garage" \
"$DATA/seaweedfs" \
--tag object-storage \
--tag "$STAMP"
docker compose start garage seaweed-master seaweed-volume seaweed-filer seaweed-s3
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
restic check
echo "[$(date -Is)] backup completed"
EOF
sudo chmod 700 /usr/local/sbin/object-storage-backup
Файл .env содержит секреты, поэтому он включён в резервную копию только при условии, что доступ к удалённому репозиторию строго ограничен. Если политика запрещает хранить секреты в бэкапе, исключите его и сохраните секреты в отдельном зашифрованном менеджере.
Cron и проверка восстановления
sudo crontab -e
Добавьте запуск каждую ночь в 03:30:
30 3 * /usr/local/sbin/object-storage-backup
Раз в месяц проверяйте не только наличие snapshot, но и восстановление отдельного объекта в временный каталог:
sudo bash -c 'source /root/.restic-env && restic snapshots --tag object-storage'
sudo mkdir -p /srv/restore-test
sudo bash -c 'source /root/.restic-env && restic restore latest --target /srv/restore-test --tag object-storage'
sudo find /srv/restore-test -maxdepth 4 -type f | head
Бэкап, который ни разу не восстанавливали, является предположением, а не доказательством восстановления. Проверяйте контрольные файлы, размер каталогов, права доступа и возможность запустить контейнеры с восстановленной конфигурацией.
Обновления
Перед обновлением зафиксируйте текущие образы:
cd /srv/object-storage/app
docker compose images
docker image ls
sudo bash -c 'source /root/.restic-env && restic backup /srv/object-storage/app --tag pre-upgrade'
Для одной ноды используйте maintenance window: остановите запись, сделайте бэкап, скачайте новый образ и перезапустите сервисы.
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=200 garage seaweed-master seaweed-s3
Rolling update возможен только в кластере из нескольких узлов с репликацией и совместимым протоколом. Сначала обновляют один узел, проверяют healthcheck и операции чтения/записи, затем переходят к следующему. Не обновляйте все узлы одновременно.
Мониторинг
Минимально контролируйте свободное место, inode, RAM, load average, SMART и ошибки контейнеров:
df -h /srv/object-storage
df -ih /srv/object-storage
free -h
uptime
docker compose ps
docker compose logs --since=15m garage seaweed-s3
sudo journalctl -u docker --since "15 minutes ago"
Настройте уведомление при заполнении диска выше 80–85 процентов. При 95 процентах сервисы могут начать возвращать ошибки записи, а фоновые операции восстановления — завершаться аварийно. Планируйте расширение заранее.
8. Troubleshooting и FAQ
Почему Caddy возвращает 502 Bad Gateway?
Сначала проверьте состояние контейнеров командой docker compose ps и журналы docker compose logs caddy garage seaweed-s3. Если Caddy не видит имя сервиса, убедитесь, что оба контейнера подключены к одной Docker-сети. Если используется host.docker.internal, замените его на имя сервиса, например garage:3900. Также проверьте локальный endpoint через curl http://127.0.0.1:3900 или сетевой запрос из контейнера Caddy.
Сертификат Let’s Encrypt не выпускается. Что проверить?
Проверьте A-записи командой dig, доступность портов 80 и 443 с внешней сети и отсутствие другого reverse proxy. Для домена не должен быть включён неверный AAAA-запись. Посмотрите docker compose logs caddy. Если DNS только что изменён, дождитесь обновления TTL. Не запускайте много повторных запросов при ошибке: Let’s Encrypt применяет rate limits.
Ошибка AccessDenied при загрузке в S3
Проверьте, к какому endpoint и bucket подключён клиент, а также значения переменных AWS_ACCESS_KEY_ID и AWS_SECRET_ACCESS_KEY. В Garage ключ должен иметь разрешения на конкретный bucket. В SeaweedFS проверьте формат s3.json и совпадение ключей. Для диагностики выполните aws s3 ls без multipart и проверьте, что системное время синхронизировано через NTP.
Запись завершается ошибкой No space left on device
Проверьте не только свободные гигабайты через df -h, но и inode через df -i. Миллионы маленьких файлов могут исчерпать inode раньше места. Удалите старые Docker-образы только после проверки, что они не нужны для rollback: docker system df. Не удаляйте вручную файлы внутри каталогов Garage или SeaweedFS — это может повредить метаданные и привести к потере объектов.
Какой VPS-конфиг минимально подойдёт?
Для тестирования достаточно 2 vCPU, 4 ГБ RAM и 100 ГБ SSD. Для небольшого production с одним backend разумный минимум — 4 vCPU, 8 ГБ RAM и 500 ГБ NVMe. Если одновременно запускаются Garage, SeaweedFS, Caddy и резервное копирование, лучше взять 8 ГБ RAM минимум, а для активной нагрузки — 16 ГБ. Оставляйте 20–30 процентов диска свободными для временных файлов и восстановления.
Что выбрать — VPS или dedicated для этой задачи?
VPS подходит для development, небольших bucket, резервных копий и нагрузки с предсказуемым I/O. Dedicated лучше выбрать при объёме от нескольких терабайт, высоком количестве операций, необходимости ECC, RAID/ZFS или гарантированной производительности дисков. В обоих случаях внешний бэкап обязателен. Dedicated с одним диском без копии не защищает от аппаратного отказа, ошибки администратора или шифровальщика.
Можно ли запускать Garage и SeaweedFS на одной ноде в production?
Технически можно, но это создаёт конкуренцию за RAM, CPU и дисковый I/O. Такая схема допустима для миграции, сравнения или независимых малых задач. Для постоянной эксплуатации выберите один backend и отключите второй. Если нужны разные классы хранения, лучше разделить сервисы по дискам или серверам и задать отдельные лимиты ресурсов Docker.
Как сделать хранилище отказоустойчивым?
Одна нода не является отказоустойчивой. Для Garage используйте несколько узлов, разные failure domain и replication factor 3. Для SeaweedFS разнесите master, volume и filer по нескольким серверам, включите репликацию volume и обеспечьте отдельное хранение метаданных. Узлы должны иметь независимое питание и желательно находиться не в одном физическом failure domain. Даже кластерная репликация не заменяет offline или географически удалённый бэкап.
Как мигрировать с одного сервиса на другой?
Создайте bucket на новом endpoint и перенесите объекты через S3-клиент, например rclone sync или AWS CLI. Сначала выполните пробную миграцию одного bucket, сравните количество объектов, размеры и контрольные суммы. После переключения приложения оставьте старое хранилище в режиме чтения на период отката. Не копируйте внутренние каталоги Garage напрямую в SeaweedFS: перенос выполняется через S3 API.
9. Выводы и следующие шаги
На dedicated-сервере можно разместить собственное S3-хранилище на Garage или SeaweedFS, закрыв API через Caddy и HTTPS. Для одной ноды получена рабочая схема с Docker Compose, отдельными каталогами данных, ограниченным firewall и резервным копированием через restic.
- Выберите один основной backend и проведите нагрузочный тест с реальным размером объектов.
- Добавьте второй сервер, репликацию и мониторинг, если данные критичны для бизнеса.
- Настройте автоматическое расширение дисков, проверку восстановления и lifecycle-политики для старых объектов.