ZeroFS: журналируемая файловая система поверх S3

Логотип ZeroFS — журналируемая файловая система для S3

ZeroFS — это журналируемая (log-structured) файловая система для S3. Она предоставляет S3-совместимые бакеты как POSIX-файловые системы по протоколам NFS и 9P, а также как сырые блочные устройства через NBD. Все три сервера работают в едином пользовательском процессе. Перед загрузкой данные сжимаются и шифруются.

ZeroFS отличается от других проектов категории «файловая система поверх S3» следующим:

  • Соответствует стандарту POSIX

  • Обеспечивает пропускную способность, близкую к прямому S3, при работе с большими файлами, и масштабируется до рабочих нагрузок из сотен миллионов мелких файлов

  • Обрабатывает десятки тысяч запросов в секунду

  • Не требует внешней базы данных — всё хранится в S3

  • Хорошо протестирована: в CI запускаются pjdfstest, xfstests, сборка ядра Linux, stress-ng, ZFS, Jepsen local-fs, Jepsen HA и другие тесты

Файловый доступ

Серверы NFS и 9P. При наличии точного пакета используется родной клиент ядра; в остальных случаях — zerofs mount в качестве запасного варианта.

Блочный доступ

NBD-устройства с поддержкой TRIM. Ответы FLUSH и FUA возвращаются только после того, как данные надёжно сохранены.

Шифрование

Экстенты (extents) шифруются алгоритмом XChaCha20-Poly1305. Ключ данных оборачивается через Argon2id.

Сжатие

zstd или lz4, применяется до шифрования. Кодек можно менять в любой момент без миграции данных.

Кэширование

Двухуровневый кэш: оперативная память и диск.

Высокая доступность

Необязательная схема лидер/резерв поверх одного бакета с автоматическим переключением при сбое.

Веб-интерфейс

Файловый менеджер, панель мониторинга, терминал в браузере.

Бэкенды

Amazon S3, Google Cloud Storage, Azure Blob, любое S3-совместимое хранилище, локальный диск.

Быстрый старт

apt / dnf

# Debian / Ubuntu
curl -fsSL https://pkgs.zerofs.net/zerofs.gpg | sudo gpg --dearmor -o /usr/share/keyrings/zerofs.gpg
echo "deb [signed-by=/usr/share/keyrings/zerofs.gpg] https://pkgs.zerofs.net/deb stable main" | sudo tee /etc/apt/sources.list.d/zerofs.list
sudo apt update && sudo apt install zerofs

# Fedora / RHEL / Rocky
curl -fsSL https://pkgs.zerofs.net/zerofs.repo | sudo tee /etc/yum.repos.d/zerofs.repo
sudo dnf install zerofs

Пакеты также устанавливают systemd-сервис (zerofs.service, по умолчанию отключён) и шаблон конфигурации в /etc/zerofs/. Задайте ZEROFS_PASSWORD и учётные данные в /etc/zerofs/zerofs.env, укажите URL в секции [storage] файла /etc/zerofs/config.toml, затем выполните sudo systemctl enable --now zerofs. Подробности — в packaging/README.md.

Скрипт установки

curl -sSfL https://sh.zerofs.net | sh

# Указать конкретную версию и установить без root
curl -sSfL https://sh.zerofs.net | VERSION=v1.2.5 INSTALL_DIR=$HOME/.local/bin sh

Скрипт скачивает архив с релизом, проверяет опубликованную контрольную сумму SHA-256 и устанавливает готовый бинарный файл. Поддерживаемые платформы: Linux (amd64, arm64), macOS (x86_64, aarch64), FreeBSD (amd64). Полная матрица платформ — в руководстве по быстрому старту.

Docker

docker pull ghcr.io/barre/zerofs:latest

# Сгенерировать стартовый конфиг на хосте ("-" выводит в stdout)
docker run --rm ghcr.io/barre/zerofs:latest init - > zerofs.toml

$EDITOR zerofs.toml
docker run --rm -v "$PWD/zerofs.toml:/zerofs.toml" \
  ghcr.io/barre/zerofs:latest run -c /zerofs.toml

Контейнер запускается от имени UID 1001. Примонтированная директория кэша должна быть доступна для записи пользователю UID 1001. Чтобы серверы были доступны с хоста, привяжите адреса к 0.0.0.0 и пробросьте порт для каждого включённого сервера: 2049 (NFS), 5564 (9P), 10809 (NBD).

Запуск

zerofs init            # Создать zerofs.toml
$EDITOR zerofs.toml    # Указать S3-учётные данные
zerofs run -c zerofs.toml

Родной клиент ядра Linux

Директория kernel/ содержит внешний (out-of-tree) VFS-модуль, который говорит на приватном диалекте 9P2000.L.Z поверх TCP или AF_UNIX без использования FUSE или Linux v9fs. Поддерживаются архитектуры x86-64 и little-endian arm64. Подробности — в руководстве по родному клиенту ядра.

Тестирование

  • pjdfstest: 8 662 POSIX-сценария (pjdfstest_nfs), запускаемых отдельно для каждого протокола: NFS, 9P, FUSE. Списки исключений по протоколам находятся в директории .github/.

  • xfstests: стандартный регрессионный набор для файловых систем, запускается поверх NFS, 9P и FUSE.

  • Сборка ядра: ядро Linux компилируется командой make -j$(nproc) при монтировании через NFS, 9P и FUSE.

  • stress-ng: стрессовые нагрузки на файловые операции запускаются параллельно против живых точек монтирования.

  • ZFS: пул ZFS создаётся поверх блочных устройств ZeroFS; затем выполняются распаковка исходников ядра и команда scrub.

  • Jepsen local-fs: случайные истории операций против монтирования по 9P, проверяемые на соответствие эталонной модели (local-fs). В режиме сбоя сервер убивается в середине прогона, после чего проверяется, что восстановление соответствует последнему fsync.

  • Jepsen HA: пара лидер/резерв поверх MinIO под управлением «немезиды», которая убивает или приостанавливает узлы; ни одна подтверждённая запись не должна быть потеряна, воскрешена или повреждена в ходе проверяемых переключений при сбое. Средство проверки модели local-fs также запускается с инжектируемыми переключениями.

  • Детерминированное моделирование: пути обработки данных, пространства имён, сборщика мусора сегментов и уплотнения выполняются в симулированной среде (zerofs/tests/dst): виртуальное время, задержки хранилища с заданным зерном и переходные сбои, падения в произвольных точках ожидания или узких окнах точек останова (failpoint). Восстановление проверяется на соответствие байтовым и пространственным эталонным моделям, выполняется полное сканирование согласованности метаданных, сверка учёта сегментов и авторитетное сканирование следа данных. Одно зерно — одно точное расписание, поэтому сбой воспроизводится идентично.

Веб-интерфейс

[servers.webui]
addresses = ["127.0.0.1:8080"]
uid = 1000  # Идентификатор POSIX для файловых операций из браузера; обязателен
gid = 1000  # Обязателен
Файловый менеджер веб-интерфейса ZeroFS

Файловый менеджер общается по протоколу 9P через WebSocket. Поддерживается перетаскивание файлов и папок целиком для загрузки.

Панель мониторинга веб-интерфейса ZeroFS

Панель мониторинга передаёт статистику в реальном времени через gRPC-web, а также включает трассировщик файловых операций.

Встроенный терминал веб-интерфейса ZeroFS

Терминал загружает Linux-VM через v86, при этом файловая система монтируется в /mnt по тому же 9P WebSocket. Гостевая машина не имеет сетевого устройства.

Архитектура

Серверы NFS, 9P, NBD и веб-интерфейс совместно используют единый уровень файловой системы. Содержимое файлов разбивается на экстенты по 32 КиБ; каждый экстент сжимается, шифруется и упаковывается как фрейм в неизменяемые объекты-сегменты (до 256 МиБ). Метаданные (инода, записи директорий и 32-байтовый указатель на каждый экстент) хранятся в базе данных с LSM-деревом (LSM-tree) в том же объектном хранилище. Подробности — в документации по архитектуре.

graph TB
    subgraph "Client Layer"
        NFS[NFS Client]
        P9[9P Client]
        NBD[NBD Client]
        WEB[Web Browser]
    end

    subgraph "ZeroFS Core"
        NFSD[NFS Server]
        P9D[9P Server]
        NBDD[NBD Server]
        WEBUI[Web UI]
        VFS[Virtual Filesystem]
        SEG[Segment Store<br/>file data as compressed, encrypted frames]
        SLATE[LSM tree<br/>metadata + 32-byte extent pointers]
        CACHE[Local Cache]

        NFSD --> VFS
        P9D --> VFS
        NBDD --> VFS
        WEBUI --> VFS
        VFS --> SEG
        VFS --> SLATE
        SEG --> CACHE
        SLATE --> CACHE
    end

    subgraph "Storage Backend"
        SEGOBJ[Immutable segment objects<br/>segments/shard/epoch/counter]
        SSTS[Metadata SSTs + manifest]
        S3[S3 Object Store]

        CACHE --> SEGOBJ
        CACHE --> SSTS
        SEGOBJ --> S3
        SSTS --> S3
    end

    NFS --> NFSD
    P9 --> P9D
    NBD --> NBDD
    WEB --> WEBUI

Высокая доступность

Секция [replication] запускает пару лидер/резерв, использующих один бакет, — при этом не нужно создавать вторую долговечную копию данных. Пока узлы соединены (Connected), резервный узел получает каждую мутацию до того, как лидер отправит ответ, включая незафлашенные фреймы файлов. Если репликация недоступна, лидер продолжает работу в автономном режиме (Solo mode) с окном долговечности автономного режима. Захват роли лидера (takeover) использует поэтапное фиксированное выдвижение с выделением эпохи записи (writer-epoch fencing) до того, как новый лидер начинает обслуживать запросы. Схема работы, гарантии, конфигурация и клиенты с несколькими целевыми адресами описаны в разделе высокой доступности.

Конфигурация

Файл конфигурации в формате TOML с подстановкой переменных окружения вида $VAR/${VAR}; все упомянутые переменные должны быть заданы. Секции [cache], [storage] и [servers] обязательны. Полный справочник параметров — в руководстве по конфигурации.

[cache]
dir = "${HOME}/.cache/zerofs"
disk_size_gb = 10.0
memory_size_gb = 1.0  # Необязательно, по умолчанию 0.25

[storage]
url = "s3://my-bucket/zerofs-data"
encryption_password = "${ZEROFS_PASSWORD}"

[filesystem]
max_size_gb = 100.0     # Необязательно; записи сверх квоты возвращают ENOSPC (по умолчанию 16 ЭиБ)
compression = "zstd-3"  # Необязательно: "zstd-{1-22}" (по умолчанию "zstd-3") или "lz4"

[servers.nfs]
addresses = ["127.0.0.1:2049"]

[servers.ninep]
addresses = ["127.0.0.1:5564"]
unix_socket = "/tmp/zerofs.9p.sock"  # Необязательно

[servers.nbd]
addresses = ["127.0.0.1:10809"]
unix_socket = "/tmp/zerofs.nbd.sock"  # Необязательно

[servers.rpc]
addresses = ["127.0.0.1:7000"]  # Нужен для zerofs checkpoint, flush, monitor, fatrace, otrace

[aws]
access_key_id = "${AWS_ACCESS_KEY_ID}"
secret_access_key = "${AWS_SECRET_ACCESS_KEY}"
# endpoint = "https://s3.us-east-1.amazonaws.com"  # Для S3-совместимых сервисов
# default_region = "us-east-1"
# allow_http = "true"  # Для не-HTTPS эндпоинтов (например, MinIO)
# conditional_put = "redis://localhost:6379"  # Для хранилищ без поддержки conditional-put

Бэкенды

url = "s3://bucket/path"        # + учётные данные [aws]
url = "azure://container/path"  # + [azure] storage_account_name / storage_account_key
url = "gs://bucket/path"        # + [gcp] service_account, или ADC из окружения на GCP VM/GKE
url = "file:///path/to/storage" # Локальный диск; учётные данные не нужны

Дополнительные схемы (s3a://, abfs://, host-routed https://, memory://) описаны в руководстве по конфигурации.

ZeroFS требует условных записей (put-if-not-exists, атомарная запись при отсутствии объекта) для механизма fencing. AWS S3 поддерживает это нативно; для хранилищ без такой поддержки укажите URL Redis в параметре conditional_put.

Необязательный параметр storage_class в секции [storage] передаётся бэкенду как есть (S3 x-amz-storage-class, GCS x-goog-storage-class, Azure x-ms-access-tier). Используйте горячий, стандартный класс доступа: архивные уровни делают том непригодным для использования, а уровни нечастого доступа (infrequent-access) тарифицируют каждое чтение — а ZeroFS читает постоянно, что обычно оборачивается значительными расходами.

Монтирование

При работе по 9P fsync возвращает управление только после того, как данные достигли стабильного хранилища; семантика NFS COMMIT позволяет fsync возвращаться раньше. Если вам важна гарантия долговечности fsync, используйте монтирование по 9P.

Родной клиент ядра

sudo mount -t zerofs 127.0.0.1:5564 /mnt/zerofs
# Unix-сокет
sudo mount -t zerofs /tmp/zerofs.9p.sock /mnt/zerofs

Пакеты для точных версий ядра, вопросы совместимости и параметры монтирования описаны в разделе родной клиент ядра.

zerofs mount (запасной вариант через FUSE)

zerofs mount 127.0.0.1:5564 /mnt/zerofs        # TCP
zerofs mount /tmp/zerofs.9p.sock /mnt/zerofs   # Unix-сокет

Стандартный Linux v9fs

mount -t 9p -o trans=tcp,port=5564,version=9p2000.L,cache=mmap,access=user 127.0.0.1 /mnt/9p
# Unix-сокет
mount -t 9p -o trans=unix,version=9p2000.L,cache=mmap,access=user /tmp/zerofs.9p.sock /mnt/9p

NFS

ZeroFS сообщает клиентам NFS, что записи устойчивы, пока они ещё буферизованы; протестированные клиенты (macOS, Linux) не отправляют COMMIT при fsync. Там, где важна долговечность fsync, используйте монтирование по 9P.

# macOS
mount -t nfs -o async,nolocks,rsize=1048576,wsize=1048576,tcp,port=2049,mountport=2049,hard 127.0.0.1:/ mnt
# Linux
mount -t nfs -o async,nolock,rsize=1048576,wsize=1048576,tcp,port=2049,mountport=2049,hard 127.0.0.1:/ /mnt

Параметры монтирования, постоянные точки монтирования, Windows — в разделе доступ по NFS.

NBD-блочные устройства

Файлы устройств в директории .nbd подключаются как сырые блочные устройства:

# Создать устройства через любую точку монтирования файловой системы
mkdir -p /mnt/zerofs/.nbd
truncate -s 1G /mnt/zerofs/.nbd/device1

# Подключить (рекомендуется: -persist, -timeout 600 для задержек S3, -connections 4)
nbd-client 127.0.0.1 10809 /dev/nbd0 -N device1 -persist -timeout 600 -connections 4
# Unix-сокет
nbd-client -unix /tmp/zerofs.nbd.sock /dev/nbd1 -N device1 -persist -timeout 600 -connections 4

mkfs.ext4 /dev/nbd0
# или
zpool create mypool /dev/nbd0

Хэндшейк анонсирует поддержку FLUSH, FUA и множественных соединений. Ответы FLUSH и FUA возвращаются только после того, как данные надёжно сохранены, а FLUSH на любом соединении распространяется на все соединения — благодаря этому барьеры записи работают корректно для пулов ZFS и баз данных. Подробности — в разделе NBD-устройства.

Новые файлы устройств подхватываются в режиме реального времени. Размер фиксируется при создании: чтобы изменить его, нужно отключить устройство, удалить файл и создать заново. Чтобы удалить устройство, сначала отключите клиент (nbd-client -d /dev/nbd0), затем выполните rm для файла.

TRIM

fstrim /mnt/block                      # Вручную
mount -o discard /dev/nbd0 /mnt/block  # Автоматически (файловые системы)
zpool set autotrim=on mypool           # Автоматически (ZFS)

TRIM удаляет указатели на экстенты и уменьшает счётчик живых байтов каждого сегмента; сборщик мусора, запускаемый каждые 60 секунд, удаляет мёртвые сегменты и переупаковывает фрагментированные, освобождая место в S3.

Ограничения

  • Максимальный размер файла: 16 ЭиБ

  • Максимальный размер файловой системы: 16 ЭиБ

  • Файлов за время жизни файловой системы: 2^64

  • Жёстких ссылок (hardlinks) на файл: 2^32

Это форматные ограничения (64-битные поля инод и размеров, экстенты по 32 КиБ), а не проверенные на практике; на деле первыми вступают в силу ограничения провайдера и стоимость хранения. Подробности — в описании архитектуры.

Лицензирование

Двойная лицензия: GNU AGPL v3 (полная функциональность, для открытого ПО) и коммерческая лицензия.

© 2026 meganuke