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. Усунення несправностей і 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-політики для старих об’єктів.