Как говорят у меня на родине: корпоративная жадность — двигатель миграций. И именно это мы сейчас можем наблюдать на примере 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-софт и предоставляете к нему доступ через сеть (даже если пользователи не скачивают ��аше ПО), вы обязаны открыть исходный код всего приложения под той же лицензией.

Практические последствия для бизнеса:

  1. SaaS-продукты — если ваш сервис использует MinIO для хранения данных клиентов, вам нужно либо открыть весь код, либо купить коммерческую лицензию.

  2. Внутренние продукты — даже если вы используете MinIO только внутри компании, но к нему есть сетевой доступ у сотрудников, формально это может считаться «предоставлением через сеть».

  3. Встраиваемые решения — если вы продаёте железо или софт с 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:

Значение

Описание

000

Без репликации (1 копия)

001

1 реплика на другом volume в том же rack

010

1 реплика в другом rack того же DC

100

1 реплика в другом DC

200

2 реплики в разных DC

110

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

seaweedfs_volume_total_disk_size

Общий размер дисков

seaweedfs_volume_used_disk_size

Использовано

> 80%

seaweedfs_filer_request_total

Запросы к filer

seaweedfs_filer_request_duration_seconds

Латентность

p99 > 100ms

seaweedfs_s3_request_total

S3-запросы

seaweedfs_s3_request_duration_seconds

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

Ключевые метрики:

Метрика

Описание

garage_data_block_bytes

Размер хранимых данных

garage_data_block_count

Количество блоков данных

garage_bucket_objects

Объектов в bucket

garage_bucket_bytes

Байт в bucket

garage_rpc_duration_seconds

Латентность RPC

garage_s3_api_request_duration_seconds

Латентность 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

ceph_health_status

0=OK, 1=WARN, 2=ERR

!= 0

ceph_osd_up

OSD online

== 0

ceph_pg_degraded

Деградированные PG

> 0

ceph_pool_bytes_used

Использование пула

> 80%

ceph_rgw_req_latency_sum

Латентность 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 в коммерческое русло — это боль для тысяч инженеров по всему миру, но также это хороший толчок для засидевшихся админов, которые получили возможность изучить что-то новое.

Мои личные рекомендации:

  1. Для большинства случаев — SeaweedFS. Отличный баланс фич, производительности и сложности. Apache 2.0 лицензия снимает юридические риски.

  2. Для edge/homelab — Garage. Минимальные требования к ресурсам, встроенная георепликация, простота.

  3. Для enterprise — Ceph. Да, сложно. Да, нужна команда. Но это единственное решение, которое масштабируется до петабайт и даёт unified storage.

  4. Для срочной миграции — OpenMaxIO как временное решение, с планом перехода на что-то из п.1-3.

И главное помните: вопрос не в том, какое решение лучше в синтетических тестах, а в том, какое лучше для вас. Поэтому тестируйте и удачи с миграцией!

*Принадлежит компании Meta, признанной экстремистской и запрещённой в России.

© 2026 ООО «МТ ФИНАНС»