Как говорят у меня на родине: корпоративная жадность — двигатель миграций. И именно это мы сейчас можем наблюдать на примере MinIO — некогда любимого инструмента DevOps-инженеров для развёртывания S3-совместимого хранилища. В 2021 году они втихушку сменили лицензию на AGPL v3, а в 2025 году и вовсе выпилили веб-интерфейс из бесплатной версии. Ну и, наверное, можно подумать, что за такой удобный инструмент можно и заплатить. Но тогда встаёт вопрос: какова цена коммерческой лицензии? От $96 000 в год)
В этой статье мы разберём, чем можно заменить MinIO, сравним альтернативы в разных сценариях и, конечно же, развернём их руками — потому что теория без практики, как вайбкодер без гпт.
Что случилось с MinIO?
Давайте сначала разберёмся, что вообще нас побудило искать альтернативы. История MinIO — классический пример того, как популярный open-source проект превращается в ловушку для пользователей.
Хронология событий
Дата | Событие |
|---|---|
2015 | MinIO основан, лицензия Apache 2.0 |
Октябрь 2019 | Начало перехода на AGPL v3 (operator и другие компоненты) |
Май 2021 | Полный переход MinIO Server на AGPL v3 |
2022 | Публичное обвинение Nutanix в нарушении лицензии |
2023 | Отзыв лицензии у Weka «без предупреждения» |
Июнь 2025 | Удаление веб-консоли из бесплатной версии |
Что такое AGPL v3 и почему это проблема?
AGPL v3 (GNU Affero General Public License) — это «открытая лицензия», но у неё есть одна очень интересная ключевая особенность — network copyleft. Если вы используете AGPL-софт и предоставляете к нему доступ через сеть (даже если пользователи не скачивают ваше ПО), вы обязаны открыть исходный код всего приложения под той же лицензией.
Практические последствия для бизнеса:
SaaS-продукты — если ваш сервис использует MinIO для хранения данных клиентов, вам нужно либо открыть весь код, либо купить коммерческую лицензию.
Внутренние продукты — даже если вы используете MinIO только внутри компании, но к нему есть сетевой доступ у сотрудников, формально это может считаться «предоставлением через сеть».
Встраиваемые решения — если вы продаёте железо или софт с MinIO внутри, клиенты теоретически могут потребовать исходный код вашего продукта.
Однако это не было бы серьёзным звоночком, так как на самом деле множество продуктов используют эту лицензию. Например, MongoDB, Grafana. Основная цель — отпугиватель для конкурентов-облаков.
Ценовая политика MinIO
Посмотрим на цены коммерческой версии (данные с официального сайта):
Объём | Цена в год |
|---|---|
До 1TB | $96,000 |
1PB | $244,032 |
10PB | $2,440,320 |
Для сравнения: за $96,000 в год можно арендовать ~800TB в Backblaze B2 или ~200TB в AWS S3 Standard. Или купить собственные серверы с дисками и нанять DevOps-инженера для их обслуживания.
Удаление фич из бесплатной версии
В июне 2025 года MinIO выпустили PR под названием «Implemented AGPL MinIO Object Browser simplified Console», который на практике означал:
Удалено из бесплатной версии:
Полноценный веб-интерфейс управления.
Графическое управление пользователями и политиками.
Визуализация метрик.
Управление репликацией через UI.
Осталось:
CLI-инструмент
mc admin.Базовый API.
Prometheus-метрики (но без UI).
Комментарий разработчика MinIO на вопрос про использование UI: «No for UI based administrative use-cases move to https://min.io/download?view=aistor-custom or use mc as a community member».
Обзор альтернатив
Итак, в результате мы там, где мы есть. И нам надо что-то делать, так что предлагаю рассмотреть четыре основных кандидата на замену MinIO:
Решение | Язык | Лицензия | Сложность | Лучший сценарий |
|---|---|---|---|---|
SeaweedFS | Go | Apache 2.0 | Средняя | Миллиарды мелких файлов, CDN |
Garage | Rust | AGPL v3 | Низкая | Edge, ограниченные ресурсы, геораспределение |
Ceph RGW | C++ | LGPL 2.1/3.0 | Высокая | Enterprise, петабайты данных, unified storage |
OpenMaxIO | Go | Apache 2.0 | Низкая | Drop-in замена MinIO |
Да, Garage тоже на AGPL, но есть важный нюанс — это некоммерческий проект от французской организации Deuxfleurs, защищающей цифровые права. У них нет армии юристов и коварных планов преследований пользователей.
SeaweedFS: когда у вас миллиарды файлов
SeaweedFS — это быстрое распределённое хранилище, изначально вдохновлённое статьёй Facebook* о Haystack (2010). Его главная фишка — O(1) поиск на диске для blob-объектов. Это достигается за счёт того, что метаданные файлов хранятся не централизованно, а распределены по volume-серверам.
Архитектура SeaweedFS
┌─────────────────────────────────────────────────────────────────┐ │ Клиенты │ │ (S3 API / Filer API / FUSE / WebDAV / Hadoop) │ └───────────────────────────┬─────────────────────────────────────┘ │ ┌───────────────────────────▼─────────────────────────────────────┐ │ S3 Gateway (опционально) │ │ Трансляция S3 API → Filer API │ └───────────────────────────┬─────────────────────────────────────┘ │ ┌───────────────────────────▼─────────────────────────────────────┐ │ Filer (опционально) │ │ Метаданные файловой системы │ │ Бэкенды: LevelDB / MySQL / PostgreSQL / Redis / etcd │ │ Поддержка: directories, symlinks, xattr │ └───────────────────────────┬─────────────────────────────────────┘ │ ┌───────────────────────────▼─────────────────────────���───────────┐ │ Master Servers │ │ (управление топологией) │ │ Raft consensus для HA (минимум 3 для кворума) │ │ Хранит: volume locations, cluster topology, free space │ └───────────────────────────┬─────────────────────────────────────┘ │ ┌─────────────┬─────────────┴─────────────┬───────────────────────┐ │ Volume 1 │ Volume 2 │ Volume N │ │ Server │ Server │ Server │ │ DC1/Rack1 │ DC1/Rack2 │ DC2/Rack1 │ │ (SSD) │ (HDD) │ (HDD) │ └─────────────┴───────────────────────────┴───────────────────────┘
Ключевые концепции:
Volume — единица хранения, файл размером до 30GB по умолчанию. Внутри хранятся needle.
Needle — конкретный файл внутри volume. Адресуется как
volume_id, needle_id, cookie. Название — отсылка к идиоме «needle in a haystack» (иголка в стоге сена) из треда на Facebook*.Collection — логическая группировка volumes (аналог bucket в S3).
Data Center / Rack — топология для умного размещения реплик.
Практика: разворачиваем SeaweedFS в Docker
Простой вариант для разработки
version: '3.9' services: seaweedfs: image: chrislusf/seaweedfs:latest ports: - "9333:9333" # Master - "8080:8080" # Volume - "8888:8888" # Filer - "8333:8333" # S3 command: 'server -s3 -dir=/data' volumes: - ./data:/data restart: unless-stopped
Этот вариант запускает все компоненты в одном контейнере — удобно для тестов.
Production-ready кластер
version: '3.9' networks: seaweedfs: driver: bridge services: # =============== MASTER SERVERS (минимум 3 для HA) =============== master1: image: chrislusf/seaweedfs:latest networks: - seaweedfs ports: - "9333:9333" - "19333:19333" command: > master -ip=master1 -ip.bind=0.0.0.0 -port=9333 -mdir=/data -peers=master1:9333,master2:9333,master3:9333 -volumeSizeLimitMB=1024 -defaultReplication=001 -garbageThreshold=0.3 -metricsPort=9324 volumes: - ./data/master1:/data restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9333/cluster/status"] interval: 30s timeout: 10s retries: 3 master2: image: chrislusf/seaweedfs:latest networks: - seaweedfs ports: - "9334:9333" - "19334:19333" command: > master -ip=master2 -ip.bind=0.0.0.0 -port=9333 -mdir=/data -peers=master1:9333,master2:9333,master3:9333 -volumeSizeLimitMB=1024 -defaultReplication=001 -garbageThreshold=0.3 -metricsPort=9324 volumes: - ./data/master2:/data restart: unless-stopped master3: image: chrislusf/seaweedfs:latest networks: - seaweedfs ports: - "9335:9333" - "19335:19333" command: > master -ip=master3 -ip.bind=0.0.0.0 -port=9333 -mdir=/data -peers=master1:9333,master2:9333,master3:9333 -volumeSizeLimitMB=1024 -defaultReplication=001 -garbageThreshold=0.3 -metricsPort=9324 volumes: - ./data/master3:/data restart: unless-stopped # =============== VOLUME SERVERS =============== volume1: image: chrislusf/seaweedfs:latest networks: - seaweedfs ports: - "8080:8080" - "18080:18080" command: > volume -mserver="master1:9333,master2:9333,master3:9333" -ip.bind=0.0.0.0 -port=8080 -dir=/data -max=300 -dataCenter=dc1 -rack=rack1 -compactionMBps=50 -fileSizeLimitMB=256 -metricsPort=9325 volumes: - ./data/volume1:/data depends_on: - master1 - master2 - master3 restart: unless-stopped volume2: image: chrislusf/seaweedfs:latest networks: - seaweedfs ports: - "8081:8080" - "18081:18080" command: > volume -mserver="master1:9333,master2:9333,master3:9333" -ip.bind=0.0.0.0 -port=8080 -dir=/data -max=300 -dataCenter=dc1 -rack=rack2 -compactionMBps=50 -fileSizeLimitMB=256 -metricsPort=9325 volumes: - ./data/volume2:/data depends_on: - master1 restart: unless-stopped volume3: image: chrislusf/seaweedfs:latest networks: - seaweedfs ports: - "8082:8080" - "18082:18080" command: > volume -mserver="master1:9333,master2:9333,master3:9333" -ip.bind=0.0.0.0 -port=8080 -dir=/data -max=300 -dataCenter=dc1 -rack=rack3 -compactionMBps=50 -fileSizeLimitMB=256 -metricsPort=9325 volumes: - ./data/volume3:/data depends_on: - master1 restart: unless-stopped # =============== FILER (для S3 API и файловой семантики) =============== filer: image: chrislusf/seaweedfs:latest networks: - seaweedfs ports: - "8888:8888" - "18888:18888" - "9326:9326" command: > filer -master="master1:9333,master2:9333,master3:9333" -ip.bind=0.0.0.0 -port=8888 -defaultReplicaPlacement=001 -metricsPort=9326 volumes: - ./config/filer.toml:/etc/seaweedfs/filer.toml:ro depends_on: - master1 - volume1 - volume2 - volume3 restart: unless-stopped # =============== S3 GATEWAY =============== s3: image: chrislusf/seaweedfs:latest networks: - seaweedfs ports: - "8333:8333" command: > s3 -filer="filer:8888" -filer.path=/buckets -ip.bind=0.0.0.0 -port=8333 -config=/etc/seaweedfs/s3.json -allowEmptyFolder=false -metricsPort=9327 volumes: - ./config/s3.json:/etc/seaweedfs/s3.json:ro depends_on: - filer restart: unless-stopped
Зачем 3 мастера? Raft consensus требует большинство. С тремя мастерами:
2 из 3 живы → кластер работает;
1 из 3 жив → кластер read-only или недоступен.
Конфигурационные файлы
config/filer.toml — настройки Filer (бэкенд метаданных):
[filer.options] recursive_delete = false max_file_name_length = 512 # Выбираем бэкенд для метаданных # Для production рекомендуется PostgreSQL или MySQL # Вариант 1: встроенный LevelDB (для небольших инсталляций, например dev или test) [leveldb2] enabled = true dir = "/data/filerldb2" # Вариант 2: PostgreSQL (рекомендуется для prod) [postgres2] enabled = false createTable = """ CREATE TABLE IF NOT EXISTS "%s" ( dirhash BIGINT, name VARCHAR(65535), directory VARCHAR(65535), meta bytea, PRIMARY KEY (dirhash, name) ) """ hostname = "postgres" port = 5432 username = "seaweedfs" password = "seaweedfs_password" database = "seaweedfs" sslmode = "disable" # Вариант 3: MySQL [mysql2] enabled = false hostname = "mysql" port = 3306 username = "seaweedfs" password = "seaweedfs_password" database = "seaweedfs"
config/s3.json — настройки S3 API:
{ "identities": [ { "name": "admin", "credentials": [ { "accessKey": "BESTACCESSKEY", "secretKey": "WriterSecretKeyChangeInProduction12345678" } ], "actions": [ "Admin", "Read", "Write", "List", "Tagging" ] }, { "name": "app-readonly", "credentials": [ { "accessKey": "BESTACCESSKEY", "secretKey": "WriterSecretKeyChangeInProduction12345678" } ], "actions": [ "Read", "List" ] }, { "name": "app-writer", "credentials": [ { "accessKey": "BESTACCESSKEY", "secretKey": "WriterSecretKeyChangeInProduction12345678" } ], "actions": [ "Read", "Write", "List" ] }, { "name": "anonymous", "actions": [ "Read:public-*" ] } ] }
Запуск и проверка
# Создаём директории mkdir -p data/{master1,master2,master3,volume1,volume2,volume3} config # Создаём конфиги (см. выше) # Запускаем docker compose -f docker-compose.production.yml up -d # Ждём инициализации (30-60 секунд) и проверяем статус кластера curl -s http://localhost:9333/cluster/status | jq . # Ожидаемый вывод: # { # "IsLeader": true, # "Leader": "master1:9333", # "Peers": ["master2:9333", "master3:9333"] # } # Проверяем топологию curl -s http://localhost:9333/dir/status | jq . # Проверяем volume servers curl -s http://localhost:9333/vol/status | jq '.Volumes | length'
Работа с S3 API
# Устанавливаем AWS CLI pip install awscli # Настраиваем профиль aws configure --profile seaweedfs # AWS Access Key ID: BESTACCESSKEY # AWS Secret Access Key: WriterSecretKeyChangeInProduction12345678 # Default region name: us-east-1 # Default output format: json # Создаём bucket aws --profile seaweedfs --endpoint-url http://localhost:8333 \ s3 mb s3://test-bucket # Загружаем файл echo "Hello, SeaweedFS!" > test.txt aws --profile seaweedfs --endpoint-url http://localhost:8333 \ s3 cp test.txt s3://test-bucket/ # Загружаем директорию рекурсивно aws --profile seaweedfs --endpoint-url http://localhost:8333 \ s3 sync ./local-folder s3://test-bucket/backup/ --recursive # Листинг aws --profile seaweedfs --endpoint-url http://localhost:8333 \ s3 ls s3://test-bucket/ # Скачиваем aws --profile seaweedfs --endpoint-url http://localhost:8333 \ s3 cp s3://test-bucket/test.txt ./downloaded.txt # Удаляем aws --profile seaweedfs --endpoint-url http://localhost:8333 \ s3 rm s3://test-bucket/test.txt # Presigned URL (временная ссылка на скачивание) aws --profile seaweedfs --endpoint-url http://localhost:8333 \ s3 presign s3://test-bucket/test.txt --expires-in 3600
Репликация и отказоустойчивость
SeaweedFS поддерживает гибкую настройку репликации через параметр -defaultReplication:
Значение | Описание |
|---|---|
| Без репликации (1 копия) |
| 1 реплика на другом volume в том же rack |
| 1 реплика в другом rack того же DC |
| 1 реплика в другом DC |
| 2 реплики в разных DC |
| 1 в другом rack + 1 в другом DC |
# Создание collection с конкретной репликацией curl "http://localhost:9333/dir/assign?collection=critical-data&replication=110" # Проверка репликации конкретного volume curl -s http://localhost:9333/vol/status | jq '.Volumes[] | select(.ReplicaPlacement != "000")'
FUSE-монтирование
SeaweedFS можно смонтировать как обычную файловую систему:
# Устанавливаем weed mount wget https://github.com/seaweedfs/seaweedfs/releases/latest/download/linux_amd64_full.tar.gz tar -xzf linux_amd64_full.tar.gz # Монтируем mkdir -p /mnt/seaweedfs ./weed mount -filer=localhost:8888 -dir=/mnt/seaweedfs -filer.path=/
Встроенный бенчмарк
# Запускаем бенчмарк напрямую docker run --rm --network host chrislusf/seaweedfs \ weed benchmark -master=localhost:9333 -c 16 -n 100000 # Параметры: # -c 16 — 16 параллельных клиентов # -n 100000 — 100,000 операций # -size 1024 — размер файла в байтах (по умолчанию 1KB) # Пример вывода: # ------------ Writing Benchmark ---------- # Completed 100000 of 100000 requests, 99.8% # Total time: 15.234 seconds # Throughput: 6565.1 files/sec # Transfer rate: 6.41 MB/sec # # ------------ Reading Benchmark ---------- # Completed 100000 of 100000 requests, 100.0% # Total time: 8.127 seconds # Throughput: 12305.7 files/sec # Transfer rate: 12.02 MB/sec
Мониторинг SeaweedFS
SeaweedFS экспортирует метрики в формате Prometheus. Добавляем в docker-compose:
prometheus: image: prom/prometheus:latest networks: - seaweedfs ports: - "9090:9090" volumes: - ./config/prometheus.yml:/etc/prometheus/prometheus.yml:ro restart: unless-stopped grafana: image: grafana/grafana:latest networks: - seaweedfs ports: - "3000:3000" environment: - GF_SECURITY_ADMIN_PASSWORD=admin volumes: - grafana-data:/var/lib/grafana restart: unless-stopped volumes: grafana-data:
config/prometheus.yml:
global: scrape_interval: 15s scrape_configs: - job_name: 'seaweedfs-master' static_configs: - targets: ['master1:9324', 'master2:9324', 'master3:9324'] - job_name: 'seaweedfs-volume' static_configs: - targets: ['volume1:9325', 'volume2:9325', 'volume3:9325'] - job_name: 'seaweedfs-filer' static_configs: - targets: ['filer:9326'] - job_name: 'seaweedfs-s3' static_configs: - targets: ['s3:9327']
Ключевые метрики для мониторинга:
Метрика | Описание | Alert threshold |
|---|---|---|
| Общий размер дисков | — |
| Использовано | > 80% |
| Запросы к filer | — |
| Латентность | p99 > 100ms |
| S3-запросы | — |
| S3-латентность | p99 > 200ms |
Garbage Collection и Compaction
SeaweedFS не удаляет данные мгновенно — они помечаются как удалённые, а физическое освобождение происходит при compaction:
# Через weed shell docker exec -it seaweedfs weed shell # В shell: > volume.vacuum -garbageThreshold 0.3 # Запустит compaction для volumes с >30% мусора # Или через HTTP API curl -X POST "http://localhost:9333/vol/vacuum?garbageThreshold=0.3"
Когда выбирать SeaweedFS?
✅ Да:
Миллиарды мелких файлов (картинки, аватары, документы).
Нужен POSIX-доступ через FUSE.
Cloud tiering (горячие данные локально, холодные в облаке).
Apache 2.0 лицензия критична.
CDN/медиахранилище.
❌ Нет:
Нужна максимальная S3-совместимость (нет versioning, lifecycle policies).
Меньше 3 серверов для production.
Garage: лёгкое решение для edge и геораспределения
Garage — детище французской некоммерческой организации Deuxfleurs. Реализован на Rust, что даёт минимальное потребление ресурсов и высокую надёжность. Главная фича — встроенная георепликация с учётом зон.
Философия Garage
Garage проектировали под конкретную задачу: объединить разные узлы в разных местах в одно хранилище, даже если связь между ними не самого хорошего качества. Хорошо подходит для:
Несколько Raspberry Pi в разных городах/квартирах.
Серверы, разбросанные по разным точкам.
Домашние серверы энтузиастов, объединённые через интернет.
Бэкапы, которые должны лежать в разных географических местах.
Архитектура Garage
В отличие от классической master-slave архитектуры, Garage использует полностью распределённый подход:
┌─────────────────────────────────────┐ │ Клиенты │ │ (S3 API / K2V API) │ └────────────────┬────────────────────┘ │ ┌─────────────────────────────┼─────────────────────────────┐ │ │ │ ▼ ▼ ▼ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Node A │◄─────────────►│ Node B │◄─────────────►│ Node C │ │ Paris │ │ Berlin │ │ London │ │ Zone: EU │ │ Zone: EU │ │ Zone: EU │ │ 1TB SSD │ │ 2TB HDD │ │ 500GB SSD │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ Gossip Protocol (RPC) │ └─────────────────────────────┴─────────────────────────────┘ Каждый узел: ├── Принимает S3-запросы ├── Хранит данные ├── Участвует в репликации └── Равноправен с другими
Ключевые концепции:
Zone — логическая группа узлов, которые могут отказать вместе (rack, DC, город).
Replication factor — сколько копий данных хранится (по умолчанию 3).
Layout — карта распределения данных по узлам.
Partition — единица распределения данных (2^partition_bits партиций).
Практика: разворачиваем Garage
Single-node для разработки
# Генерируем секреты RPC_SECRET=$(openssl rand -hex 32) ADMIN_TOKEN=$(openssl rand -base64 32) METRICS_TOKEN=$(openssl rand -base64 32) # Создаём директории mkdir -p ~/garage/{data,meta} # Создаём конфигурацию cat > ~/garage/garage.toml << EOF metadata_dir = "/var/lib/garage/meta" data_dir = "/var/lib/garage/data" db_engine = "sqlite" # Для single-node, в production используйте lmdb replication_factor = 1 # Для single-node rpc_bind_addr = "[::]:3901" rpc_public_addr = "127.0.0.1:3901" rpc_secret = "${RPC_SECRET}" [s3_api] s3_region = "garage" api_bind_addr = "[::]:3900" root_domain = ".s3.garage.localhost" [s3_web] bind_addr = "[::]:3902" root_domain = ".web.garage.localhost" index = "index.html" [k2v_api] api_bind_addr = "[::]:3904" [admin] api_bind_addr = "[::]:3903" admin_token = "${ADMIN_TOKEN}" metrics_token = "${METRICS_TOKEN}" EOF # Запускаем через Docker docker run -d \ --name garage \ -p 3900:3900 \ -p 3901:3901 \ -p 3902:3902 \ -p 3903:3903 \ -p 3904:3904 \ -v ~/garage/garage.toml:/etc/garage.toml:ro \ -v ~/garage/data:/var/lib/garage/data \ -v ~/garage/meta:/var/lib/garage/meta \ dxflrs/garage:v2.1.0 # Проверяем статус docker exec garage /garage status
Multi-node кластер (3 узла)
Для production-кластера нам нужно минимум 3 узла в разных зонах. Создаём docker-compose для каждого узла:
docker-compose.node1.yml:
version: '3.9' services: garage: image: dxflrs/garage:v2.1.0 container_name: garage-node1 network_mode: host volumes: - ./garage-node1.toml:/etc/garage.toml:ro - ./node1/data:/var/lib/garage/data - ./node1/meta:/var/lib/garage/meta restart: unless-stopped
garage-node1.toml:
metadata_dir = "/var/lib/garage/meta" data_dir = "/var/lib/garage/data" db_engine = "lmdb" metadata_auto_snapshot_interval = "6h" replication_factor = 3 compression_level = 2 rpc_bind_addr = "[::]:3901" rpc_public_addr = "NODE1_PUBLIC_IP:3901" rpc_secret = "SAME_SECRET_FOR_ALL_NODES_32_HEX_CHARS" bootstrap_peers = [ "NODE2_PUBLIC_IP:3901", "NODE3_PUBLIC_IP:3901" ] [s3_api] s3_region = "garage" api_bind_addr = "[::]:3900" root_domain = ".s3.garage.example.com" [s3_web] bind_addr = "[::]:3902" root_domain = ".web.garage.example.com" index = "index.html" [k2v_api] api_bind_addr = "[::]:3904" [admin] api_bind_addr = "[::]:3903" admin_token = "YOUR_ADMIN_TOKEN" metrics_token = "YOUR_METRICS_TOKEN"
Аналогично создаём конфиги для node2 и node3, меняя rpc_public_addr.
Инициализация кластера
# На каждом узле получаем Node ID docker exec garage-node1 /garage node id # Вывод: 5a7c4f3b9e@192.168.1.10:3901 # Соединяем узлы (на любом из них) docker exec garage-node1 /garage node connect 7b8d5e2a1f@192.168.1.11:3901 docker exec garage-node1 /garage node connect 9c6a3d8e4b@192.168.1.12:3901 # Проверяем статус docker exec garage-node1 /garage status # Все узлы должны быть видны # Создаём layout docker exec garage-node1 /garage layout assign \ --zone paris \ --capacity 500G \ 5a7c4f3b9e docker exec garage-node1 /garage layout assign \ --zone berlin \ --capacity 1T \ 7b8d5e2a1f docker exec garage-node1 /garage layout assign \ --zone london \ --capacity 500G \ 9c6a3d8e4b # Просматриваем и применяем docker exec garage-node1 /garage layout show docker exec garage-node1 /garage layout apply --version 1
Создание пользователей и buckets
# Создаём bucket docker exec garage-node1 /garage bucket create my-data # Создаём ключ docker exec garage-node1 /garage key create my-app-key # Выводим ключ (запоминаем access_key и secret_key) docker exec garage-node1 /garage key info my-app-key --show-secret # Даём права на bucket docker exec garage-node1 /garage bucket allow \ --read \ --write \ --owner \ my-data \ --key my-app-key # Проверяем docker exec garage-node1 /garage bucket info my-data
Работа с S3 API
# Настраиваем AWS CLI cat >> ~/.aws/credentials << EOF [garage] aws_access_key_id = BESTACCESSKEY aws_secret_access_key = WriterSecretKeyChangeInProduction12345678 EOF cat >> ~/.aws/config << EOF [profile garage] region = garage output = json EOF # Тестируем aws --profile garage \ --endpoint-url http://localhost:3900 \ s3 ls # Создаём bucket через CLI (альтернатива garage CLI) aws --profile garage \ --endpoint-url http://localhost:3900 \ s3 mb s3://test-bucket # Multipart upload для больших файлов aws --profile garage \ --endpoint-url http://localhost:3900 \ s3 cp large-file.zip s3://my-data/ \ --expected-size 10737418240
K2V API — уникальная фича Garage
Garage предоставляет не только S3, но и K2V (Key-Value-Value) API — распределённое key-value хранилище с поддержкой causal consistency:
# Включено по умолчанию на порту 3904 # Пример использования через curl # Записываем значение curl -X PUT \ -H "Authorization: AWS4-HMAC-SHA256 ..." \ -d "my-value" \ "http://localhost:3904/my-bucket/my-partition-key?sort_key=my-sort-key" # Читаем curl -H "Authorization: ..." \ "http://localhost:3904/my-bucket/my-partition-key?sort_key=my-sort-key"
K2V полезен для хранения метаданных, сессий, конфигураций — всего, что требует быстрого доступа по ключу.
Web UI для Garage
Официального UI нет, но есть community-проект garage-webui:
# Добавляем в docker-compose garage-webui: image: khairul169/garage-webui:latest container_name: garage-webui ports: - "3909:3909" volumes: - ./garage.toml:/etc/garage.toml:ro environment: API_BASE_URL: "http://garage:3903" S3_ENDPOINT_URL: "http://garage:3900" # Опционально: аутентификация # AUTH_USER_PASS: "admin:$2y$10$xxxxx" # bcrypt hash restart: unless-stopped
Мониторинг Garage
# prometheus.yml scrape_configs: - job_name: 'garage' static_configs: - targets: - 'node1:3903' - 'node2:3903' - 'node3:3903' metrics_path: /metrics authorization: credentials: YOUR_METRICS_TOKEN
Ключевые метрики:
Метрика | Описание |
|---|---|
| Размер хранимых данных |
| Количество блоков данных |
| Объектов в bucket |
| Байт в bucket |
| Латентность RPC |
| Латентность S3 |
Troubleshooting Garage
Проблема: узлы не видят друг друга
# Проверяем сетевую доступность nc -zv node2_ip 3901 # Проверяем, что rpc_secret одинаков на всех узлах grep rpc_secret /etc/garage.toml # Проверяем firewall sudo iptables -L -n | grep 3901
Проблема: Layout не применяется
# Смотрим текущую версию docker exec garage /garage layout show # Если "staged changes", применяем docker exec garage /garage layout apply --version N # Если конфликт, можно откатить docker exec garage /garage layout revert --version N
Проблема: данные не реплицируются
# Проверяем здоровье кластера docker exec garage /garage status # Смотрим repair status docker exec garage /garage repair --all-nodes status # Запускаем repair вручную docker exec garage /garage repair --all-nodes blocks
Когда выбирать Garage?
✅ Да:
Edge-деплой на Raspberry Pi или маломощных VPS.
Геораспределённый кластер с высокой задержкой между узлами.
Минимальное потребление RAM критично (Rust!).
Self-hosted бэкапы с репликацией.
Простота важнее фич.
❌ Нет:
Нужна полная S3-совместимость (versioning, lifecycle, advanced IAM).
Enterprise-фичи обязательны.
Больше 50TB данных.
Высокие требования к производительности.
Ceph: Оставь надежду, всяк сюда входящий
Ceph — это монстр enterprise-уровня. Используется в CERN для хранения данных Large Hadron Collider, в Bloomberg для финансовой аналитики, в DreamWorks для рендер-пайплайнов. Если вам нужны петабайты — это ваш выбор.
Зачем такая сложность?
Ceph предоставляет unified storage — из одного кластера вы получаете:
RBD — block storage (аналог EBS).
CephFS — distributed file system (аналог EFS).
RGW — object storage с S3/Swift API (аналог S3).
Это означает, что вместо трёх разных систем хранения вы управляете одной.
Архитектура Ceph
┌─────────────────────────────────────────────────────────────────────────┐ │ Клиенты │ │ S3/Swift API POSIX/NFS Block Devices │ │ ▼ ▼ ▼ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ RGW │ │ MDS │ │ RBD │ │ │ │ (Object)│ │ (File) │ │ (Block) │ │ │ └────┬────┘ └────┬────┘ └────┬────┘ │ └────────────┼─────────────────┼──────────────────┼───────────────────────┘ │ │ │ └─────────────────┼──────────────────┘ │ ┌──────────────────────────────▼──────────────────────────────────────────┐ │ librados │ │ Библиотека доступа к RADOS │ └──────────────────────────────┬──────────────────────────────────────────┘ │ ┌──────────────────────────────▼──────────────────────────────────────────┐ │ RADOS │ │ Reliable Autonomic Distributed Object Store │ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ CRUSH Algorithm │ │ │ │ Controlled Replication Under Scalable Hashing │ │ │ │ (Определяет размещение данных без центрального каталога) │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ MON │ │ MON │ │ MON │ │ MGR │ │ │ │ Monitor │ │ Monitor │ │ Monitor │ │ Manager │ │ │ │ (quorum)│ │ │ │ │ │(metrics)│ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ OSD │ │ OSD │ │ OSD │ │ OSD │ ... │ │ │ Disk1 │ │ Disk2 │ │ Disk3 │ │ Disk4 │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ └─────────────────────────────────────────────────────────────────────────┘
Компоненты:
MON (Monitor) — хранит карту кластера, обеспечивает консенсус. Минимум 3 для кворума.
OSD (Object Storage Daemon) — один на каждый физический диск. Хранит данные, обрабатывает репликацию.
MGR (Manager) — метрики, dashboard, модули расширения.
MDS (Metadata Server) — только для CephFS, хранит метаданные файловой системы.
RGW (RADOS Gateway) — S3/Swift API gateway.
Быстрый старт с MicroCeph
Canonical упростили жизнь, создав MicroCeph — обёртку над Ceph для простого развёртывания:
# Требования: Ubuntu 22.04+, минимум 8GB RAM, свободный диск # Устанавливаем MicroCeph sudo snap install microceph --channel=latest/stable # ВАЖНО: фиксируем версию, чтобы избежать автообновлений sudo snap refresh --hold microceph # Инициализируем кластер sudo microceph cluster bootstrap # Проверяем статус sudo microceph status
Добавляем хранилище:
# Вариант 1: реальный диск sudo microceph disk add /dev/sdb --wipe # Вариант 2: loop-устройство для тестов sudo dd if=/dev/zero of=/var/snap/microceph/common/data/osd.img bs=1M count=20480 LOOP=$(sudo losetup --find --show /var/snap/microceph/common/data/osd.img) sudo microceph disk add $LOOP --wipe # Проверяем sudo ceph osd tree
Включаем RGW:
# Включаем Object Gateway sudo microceph enable rgw # Ждём запуска (может занять минуту) sleep 60 # Проверяем sudo ceph -s | grep rgw curl http://localhost:80
Создаём пользователя:
# Создаём S3 пользователя sudo radosgw-admin user create \ --uid=myuser \ --display-name="My User" \ --access-key=myaccesskey \ --secret=mysupersecretkey # Или с автогенерацией ключей sudo radosgw-admin user create \ --uid=autouser \ --display-name="Auto User" # Ключи будут в выводе команды
Production-деплой с Cephadm
Для серьёзных инсталляций используйте cephadm — современный оркестратор Ceph:
# На первом узле (будет bootstrap node) # Устанавливаем cephadm curl --silent --remote-name \ https://download.ceph.com/rpm-squid/el9/noarch/cephadm chmod +x cephadm sudo mv cephadm /usr/local/bin/ # Или через пакетный менеджер (Ubuntu) sudo apt install -y cephadm # Bootstrap кластера sudo cephadm bootstrap \ --mon-ip 192.168.1.10 \ --initial-dashboard-user admin \ --initial-dashboard-password SecureP@ssw0rd \ --dashboard-ssl \ --allow-fqdn-hostname # Сохраняем вывод! Там будут: # - URL dashboard # - Credentials # - SSH ключ для добавления узлов
Добавляем узлы:
# Копируем SSH-ключ на другие узлы sudo cephadm shell -- ceph cephadm get-pub-key > ceph.pub ssh-copy-id -f -i ceph.pub root@node2 ssh-copy-id -f -i ceph.pub root@node3 # Добавляем узлы sudo cephadm shell -- ceph orch host add node2 192.168.1.11 sudo cephadm shell -- ceph orch host add node3 192.168.1.12 # Добавляем OSD на всех узлах (автоматически найдёт свободные диски) sudo cephadm shell -- ceph orch apply osd --all-available-devices # Или явно указываем диски sudo cephadm shell -- ceph orch daemon add osd node2:/dev/sdb sudo cephadm shell -- ceph orch daemon add osd node3:/dev/sdb
Развёртываем RGW:
# Создаём spec-файл для RGW cat > rgw-spec.yaml << 'EOF' service_type: rgw service_id: objectstore service_name: rgw.objectstore placement: count: 2 label: "rgw" spec: rgw_frontend_port: 8080 rgw_frontend_extra_args: - "tcp_nodelay=1" - "request_timeout_ms=30000" EOF # Добавляем label на узлы, где будет RGW sudo cephadm shell -- ceph orch host label add node1 rgw sudo cephadm shell -- ceph orch host label add node2 rgw # Применяем spec sudo cephadm shell -- ceph orch apply -i rgw-spec.yaml # Проверяем sudo cephadm shell -- ceph orch ls sudo cephadm shell -- ceph orch ps | grep rgw
Настраиваем SSL и HA с ingress:
# rgw-ingress-spec.yaml service_type: ingress service_id: rgw.objectstore placement: count: 2 label: rgw spec: backend_service: rgw.objectstore virtual_ip: 192.168.1.100/24 frontend_port: 443 monitor_port: 1967 ssl_cert: | -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- ssl_key: | -----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY-----
Kubernetes: Rook-Ceph Operator
Если у вас Kubernetes, Rook — лучший способ запустить Ceph:
# Устанавливаем Rook-operator kubectl create namespace rook-ceph kubectl apply -f https://raw.githubusercontent.com/rook/rook/v1.14.0/deploy/examples/crds.yaml kubectl apply -f https://raw.githubusercontent.com/rook/rook/v1.14.0/deploy/examples/common.yaml kubectl apply -f https://raw.githubusercontent.com/rook/rook/v1.14.0/deploy/examples/operator.yaml # Ждём готовности operator kubectl -n rook-ceph wait --for=condition=ready pod -l app=rook-ceph-operator --timeout=300s
Создаём CephCluster:
# ceph-cluster.yaml apiVersion: ceph.rook.io/v1 kind: CephCluster metadata: name: rook-ceph namespace: rook-ceph spec: cephVersion: image: quay.io/ceph/ceph:v18.2.2 allowUnsupported: false dataDirHostPath: /var/lib/rook mon: count: 3 allowMultiplePerNode: false mgr: count: 2 allowMultiplePerNode: false modules: - name: pg_autoscaler enabled: true - name: prometheus enabled: true dashboard: enabled: true ssl: true storage: useAllNodes: true useAllDevices: true # Или явно указываем устройства: # nodes: # - name: "worker-1" # devices: # - name: "sdb" # - name: "sdc" resources: osd: limits: cpu: "2" memory: "4Gi" requests: cpu: "1" memory: "2Gi"
Создаём Object Store:
# ceph-objectstore.yaml apiVersion: ceph.rook.io/v1 kind: CephObjectStore metadata: name: my-store namespace: rook-ceph spec: metadataPool: replicated: size: 3 dataPool: erasureCoded: dataChunks: 2 codingChunks: 1 preservePoolsOnDelete: true gateway: port: 80 securePort: 443 instances: 2 resources: limits: cpu: "2" memory: "2Gi" requests: cpu: "500m" memory: "1Gi"
Создаём пользователя:
# ceph-objectstore-user.yaml apiVersion: ceph.rook.io/v1 kind: CephObjectStoreUser metadata: name: my-user namespace: rook-ceph spec: store: my-store displayName: "S3 User"Е
# Применяем kubectl apply -f ceph-cluster.yaml kubectl apply -f ceph-objectstore.yaml kubectl apply -f ceph-objectstore-user.yaml # Ждём готовности (может занять 5-10 минут) kubectl -n rook-ceph wait --for=condition=ready cephcluster rook-ceph --timeout=600s # Получаем credentials kubectl -n rook-ceph get secret rook-ceph-object-user-my-store-my-user -o jsonpath='{.data.AccessKey}' | base64 -d kubectl -n rook-ceph get secret rook-ceph-object-user-my-store-my-user -o jsonpath='{.data.SecretKey}' | base64 -d # Получаем endpoint kubectl -n rook-ceph get svc rook-ceph-rgw-my-store
Erasure Coding vs Replication
Ceph поддерживает два способа защиты данных:
Replication (репликация):
Простая: данные копируются N раз.
Overhead: 200% при 3 репликациях (1TB данных = 3TB на дисках).
Производительность: быстрее на чтение.
Надёжность: переживает потерю N-1 узлов.
Erasure Coding (EC):
Сложная: данные разбиваются на K чанков + M чанков чётности.
Overhead: зависит от профиля (например, 4+2 = 50% overhead).
Производительность: медленнее, больше CPU.
Надёжность: переживает потерю M-узлов.
# Создаём EC-профиль sudo ceph osd erasure-code-profile set ec-42-profile \ k=4 m=2 \ crush-failure-domain=host # Создаём EC pool sudo ceph osd pool create ec-data-pool 32 32 erasure ec-42-profile # Для RGW: используем EC для data pool sudo radosgw-admin zone placement modify \ --rgw-zone=default \ --placement-id=default-placement \ --data-pool=ec-data-pool
Мониторинг Ceph
Ceph имеет встроенный модуль Prometheus:
# Включаем модуль sudo ceph mgr module enable prometheus # Метрики доступны на порту 9283 curl http://ceph-mgr-host:9283/metrics
Ключевые метрики:
Метрика | Описание | Alert |
|---|---|---|
| 0=OK, 1=WARN, 2=ERR | != 0 |
| OSD online | == 0 |
| Деградированные PG | > 0 |
| Использование пула | > 80% |
| Латентность RGW | p99 > 500ms |
Alertmanager rules:
groups: - name: ceph rules: - alert: CephHealthWarning expr: ceph_health_status == 1 for: 5m labels: severity: warning annotations: summary: "Ceph health warning" - alert: CephHealthError expr: ceph_health_status == 2 for: 1m labels: severity: critical annotations: summary: "Ceph health critical" - alert: CephOSDDown expr: ceph_osd_up == 0 for: 5m labels: severity: critical annotations: summary: "Ceph OSD {{ $labels.osd }} is down"
Troubleshooting Ceph
Проблема: HEALTH_WARN
# Смотрим детали sudo ceph health detail # Частые причины: # - "too few PGs per OSD" — увеличьте pg_num # - "pool has no replicas configured" — настройте реплик��цию # - "clock skew detected" — синхронизируйте NTP
Проблема: медленные OSD
# Проверяем latency sudo ceph osd perf # Проверяем blocked requests sudo ceph daemon osd.0 dump_blocked_ops # Проверяем slow ops sudo ceph daemon osd.0 dump_historic_slow_ops
Проблема: RGW 503 errors
# Проверяем статус RGW sudo systemctl status ceph-radosgw@rgw.* # Смотрим логи sudo journalctl -u ceph-radosgw@rgw.* -f # Проверяем pools sudo ceph osd pool ls detail | grep rgw
Когда выбирать Ceph?
✅ Да:
Петабайты данных.
Нужны block + file + object из одного кластера.
Enterprise SLA обязателен.
Есть команда с опытом Ceph или готовностью учиться.
Kubernetes-native storage нужен.
❌ Нет:
Меньше 3 узлов.
Меньше 8GB RAM на узел.
Нет времени на обучение.
Quick and dirty решение.
OpenMaxIO: форк старого доброго MinIO
OpenMaxIO — это community-форк последней версии MinIO до удаления фич.
Развёртывание
# Docker (single node) docker run -d \ --name openmaxio \ -p 9000:9000 \ -p 9001:9001 \ -v /data:/data \ -e "MINIO_ROOT_USER=admin" \ -e "MINIO_ROOT_PASSWORD=password123456" \ openmaxio/openmaxio:latest \ server /data --console-address ":9001" # Веб-консоль доступна на :9001
Важное предупреждение
⚠️ OpenMaxIO — community-проект без гарантий:
Нет коммерческой поддержки.
Обновления безопасности могут задерживаться.
Долгосрочная судьба проекта неизвестна.
Используйте на свой страх и риск.
Рекомендую рассматривать его только как временное решение на время миграции на SeaweedFS/Garage/Ceph.
Сравнительное тестирование производительности
Я провёл тестирование на идентичном железе для объективного сравнения:
Тестовый стенд:
3 x VM: 4 vCPU, 16GB RAM, 500GB NVMe SSD.
Network: 10Gbps между узлами.
OS: Ubuntu 24.04 LTS.
Методология
Использовал s3-benchmark:
# Тест 1: мелкие файлы (1KB x 100,000) s3-benchmark -a ACCESS_KEY -s SECRET_KEY \ -b test-bucket -u http://endpoint:port \ -t 32 -z 1K -n 100000 # Тест 2: средние файлы (1MB x 10,000) s3-benchmark -a ACCESS_KEY -s SECRET_KEY \ -b test-bucket -u http://endpoint:port \ -t 32 -z 1M -n 10000 # Тест 3: большие файлы (100MB x 100) s3-benchmark -a ACCESS_KEY -s SECRET_KEY \ -b test-bucket -u http://endpoint:port \ -t 16 -z 100M -n 100
Результаты
Тест 1: мелкие файлы (1KB x 100,000)
Решение | Write ops/sec | Read ops/sec | Write MB/s | Read MB/s |
|---|---|---|---|---|
MinIO (baseline) | 12,500 | 18,200 | 12.2 | 17.8 |
SeaweedFS | 45,000 | 52,000 | 43.9 | 50.8 |
Garage | 8,200 | 11,500 | 8.0 | 11.2 |
Ceph RGW | 6,800 | 9,400 | 6.6 | 9.2 |
SeaweedFS выигрывает на мелких файлах благодаря архитектуре Haystack, которая включает распределённые метаданные.
Тест 2: средние файлы (1MB x 10,000)
Решение | Write ops/sec | Read ops/sec | Write MB/s | Read MB/s |
|---|---|---|---|---|
MinIO | 850 | 1,200 | 850 | 1,200 |
SeaweedFS | 920 | 1,350 | 920 | 1,350 |
Garage | 680 | 950 | 680 | 950 |
Ceph RGW | 780 | 1,100 | 780 | 1,100 |
На средних файлах разница сглаживается — все решения упираются в сеть и диски.
Тест 3: большие файлы (100MB x 100)
Решение | Write MB/s | Read MB/s | CPU % | RAM GB |
|---|---|---|---|---|
MinIO | 980 | 1,150 | 45% | 2.1 |
SeaweedFS | 920 | 1,080 | 38% | 1.8 |
Garage | 850 | 980 | 25% | 0.8 |
Ceph RGW | 890 | 1,050 | 52% | 3.2 |
Garage показывает минимальное потребление ресурсов благодаря реализации на Rust.
S3 API Compatibility
Тестировал с помощью ceph/s3-tests:
Feature | MinIO | SeaweedFS | Garage | Ceph RGW |
|---|---|---|---|---|
Basic GET/PUT/DELETE | ✅ | ✅ | ✅ | ✅ |
Multipart Upload | ✅ | ✅ | ✅ | ✅ |
Presigned URLs | ✅ | ✅ | ✅ | ✅ |
Versioning | ✅ | ❌ | ❌ | ✅ |
Lifecycle Policies | ✅ | ❌ | ❌ | ✅ |
Object Lock | ✅ | ❌ | ❌ | ✅ |
Bucket Policies | ✅ | Partial | Partial | ✅ |
IAM | ✅ | Basic | Basic | ✅ |
Server-Side Encryption | ✅ | ✅ | ✅ | ✅ |
Cross-Region Replication | ✅ | ✅ | ✅ | ✅ |
S3 Select | ✅ | ❌ | ❌ | ✅ |
Compatibility score (ceph s3-tests):
Ceph RGW: 576/700 passed (82%).
MinIO: 321/700 passed (46%).
SeaweedFS: ~200/700 passed (~29%).
Garage: ~150/700 passed (~21%).
Итоговая сравнительная таблица
Критерий | SeaweedFS | Garage | Ceph RGW | OpenMaxIO |
|---|---|---|---|---|
Лицензия | Apache 2.0 ✅ | AGPL v3 ⚠️ | LGPL ✅ | Apache 2.0 ✅ |
Язык | Go | Rust | C++ | Go |
Min. RAM | 2GB | 512MB | 8GB | 4GB |
Min. CPU | 2 cores | 1 core | 4 cores | 2 cores |
Min. узлов (HA) | 3 | 3 | 3 | 4 |
S3 совместимость | ~70% | ~60% | ~90% | ~95% |
Versioning | ❌ | ❌ | ✅ | ✅ |
Lifecycle | ❌ | ❌ | ✅ | ✅ |
Object Lock | ❌ | ❌ | ✅ | ✅ |
Erasure Coding | ✅ | ❌ (3x repl) | ✅ | ✅ |
Geo-replication | ✅ | ✅ (native) | ✅ | ✅ |
Web UI | ✅ Filer | Community | ✅ Dashboard | ✅ |
Kubernetes native | Helm | Manual | Rook ✅ | Operator |
Сложность | Средняя | Низкая | Высокая | Низкая |
Production-ready | ✅ | ✅ (< 50TB) | ✅ | ⚠️ Community |
Лучший для | CDN, медиа | Edge, homelab | Enterprise | Миграция |
Заключение
Уход MinIO в коммерческое русло — это боль для тысяч инженеров по всему миру, но также это хороший толчок для засидевшихся админов, которые получили возможность изучить что-то новое.
Мои личные рекомендации:
Для большинства случаев — SeaweedFS. Отличный баланс фич, производительности и сложности. Apache 2.0 лицензия снимает юридические риски.
Для edge/homelab — Garage. Минимальные требования к ресурсам, встроенная георепликация, простота.
Для enterprise — Ceph. Да, сложно. Да, нужна команда. Но это единственное решение, которое масштабируется до петабайт и даёт unified storage.
Для срочной миграции — OpenMaxIO как временное решение, с планом перехода на что-то из п.1-3.
И главное помните: вопрос не в том, какое решение лучше в синтетических тестах, а в том, какое лучше для вас. Поэтому тестируйте и удачи с миграцией!
*Принадлежит компании Meta, признанной экстремистской и запрещённой в России.
© 2026 ООО «МТ ФИНАНС»

