Оглавление
- Какую проблему решает fs-verity, а какую — нет
- Как fs-verity проверяет гигабайтные shard-файлы
- Infrastructure gate: kernel, файловая система и отдельный staging
- Загрузка модели: pinned revision вместо плавающего latest
- Включение fs-verity и доверенный manifest
- Service gate: не запускать inference с неизвестными байтами
- Application gate: доказать, что загружена именно эта модель
- Rollout без перезаписи: canary, promotion и быстрый rollback
- Что делать при EIO, SIGBUS или digest mismatch
- Ограничения и соседние механизмы защиты
- Практический итог
Готовы перейти на современную серверную инфраструктуру?
В King Servers мы предлагаем серверы как на AMD EPYC, так и на Intel Xeon, с гибкими конфигурациями под любые задачи — от виртуализации и веб-хостинга до S3-хранилищ и кластеров хранения данных.
- S3-совместимое хранилище для резервных копий
- Панель управления, API, масштабируемость
- Поддержку 24/7 и помощь в выборе конфигурации
Результат регистрации
...
Создайте аккаунт
Быстрая регистрация для доступа к инфраструктуре
Какую проблему решает fs-verity, а какую — нет
Узел inference может быть технически здоров: GPU видны, сервис отвечает на health-check, latency не выросла. Но если файл весов на локальном NVMe заменён после загрузки, приложение способно без шума поднять не ту модель. Причиной бывает не только атака. Ошибочный sync, повреждение блока, ручная команда в неправильном каталоге или гонка при rollout дают тот же неприятный результат: имя ревизии в конфигурации одно, фактические байты — другие.
Именно здесь полезен fs-verity — механизм ядра Linux для прозрачной проверки содержимого неизменяемого отдельного файла. После включения ядро связывает файл с деревом Меркла, запрещает запись и проверяет прочитанные страницы. Если данные не сходятся с деревом, чтение завершается ошибкой EIO, а доступ через mmap() может привести к SIGBUS. Для больших shard-файлов это важное отличие от одноразового sha256sum: защита продолжает работать при последующих чтениях.
Граница материала узкая и практическая. Мы не повторяем общий процесс версионирования из статьи про LLMOps и не ускоряем загрузку, как в руководствах про FS-Cache или OCI-кэш весов на NVMe. Здесь задача одна: сделать идентичность локального model artifact проверяемым условием запуска.
При этом fs-verity не является доверенным источником сам по себе. Digest нужно сравнить с manifest, который хранится отдельно и защищён лучше каталога модели. Механизм не измеряет владельца, mode bits, timestamps и xattrs; файл можно переименовать, удалить или заменить другим inode. Если злоумышленник контролирует kernel или одновременно меняет файл и доверенный manifest, этот слой уже не спасает. Поэтому дальше будут два gate: инфраструктурный и прикладной.

Как fs-verity проверяет гигабайтные shard-файлы
При fsverity enable файловая система строит скрытое дерево Меркла. Листья соответствуют блокам данных, следующий уровень хранит их хеши, а верхний корень входит в fs-verity digest вместе с размером файла и параметрами алгоритма. В результате digest однозначно описывает содержимое и структуру файла, а получить его через fsverity measure можно без полного перечитывания многогигабайтного shard.
Проверка выполняется не «по расписанию», а на пути чтения. Когда loader или mmap() затягивает страницу весов в page cache, ядро проверяет нужную ветвь дерева до доверенного корня. Уже проверенная страница может отдаваться из page cache, а после повторного чтения с диска верификация выполняется снова. Документация ядра подчёркивает, что direct I/O для verity-файлов не поддерживается и откатывается к buffered I/O, а DAX исключён, потому что обходил бы проверку.
Есть два уровня доверия. Первый — integrity: случайная порча обнаруживается, потому что данные не соответствуют сохранённому дереву. Второй — authenticity: оператор убеждается, что сам digest разрешён. Для production-пайплайна проще всего держать подписанный manifest вне writable model volume и сравнивать измеренный digest до старта сервиса. Встроенные PKCS#7-подписи и keyring .fs-verity доступны, но документация fsverity-utils называет их специализированным механизмом, который требует осторожного проектирования trusted userspace.

Infrastructure gate: kernel, файловая система и отдельный staging
Начните с фактов, а не с установки пакета. В ядре должна быть включена опция CONFIG_FS_VERITY, а файловая система — реально поддерживать механизм. По актуальной документации ядра это ext4, f2fs и btrfs; ext4 поддерживает fs-verity с Linux 5.4, btrfs — с 5.15. Для ext4 у файловой системы должен быть feature flag verity. Его можно задать при форматировании или включить через tune2fs -O verity, но последняя операция меняет совместимость тома со старыми kernels и e2fsck, поэтому её нельзя превращать в бездумный copy-paste для production.
Практичный layout разделяет три зоны: download cache, writable staging и immutable revisions. Hub-кэш удобен для сети, но symlink-структура и shared blobs плохо подходят на роль конечного доверенного каталога. Скачивайте pinned commit в staging, проверяйте полноту, копируйте нужные файлы в новый каталог ревизии на fs-verity-capable volume и только затем включайте verity. Каталог current должен быть атомарной ссылкой на уже подготовленную ревизию, а не местом, куда downloader пишет поверх работающей модели.
Проверяли ли вы реальную файловую систему под model path? В контейнере путь может выглядеть локальным, но быть overlayfs или сетевым mount. Gate нужно выполнять на host path, где лежат байты, а затем проверять тот же inode из launch context сервиса.
set -euo pipefail
MODEL_ROOT=/srv/models
findmnt -no SOURCE,FSTYPE,OPTIONS "$MODEL_ROOT"
grep -E 'CONFIG_FS_VERITY=(y|m)' /boot/config-$(uname -r)
sudo tune2fs -l "$(findmnt -no SOURCE "$MODEL_ROOT")" | grep 'Filesystem features'
fsverity --version
touch "$MODEL_ROOT/.verity-probe"
fsverity enable "$MODEL_ROOT/.verity-probe"
fsverity measure "$MODEL_ROOT/.verity-probe"
Probe-файл доказывает больше, чем наличие бинарника: ioctl дошёл до нужной файловой системы, дерево построено, digest измеряется. После теста файл можно удалить; снять verity с inode нельзя. Если probe не проходит, не расширяйте привилегии контейнера наугад — сначала локализуйте слой: kernel config, filesystem feature, mount type или путь.

Загрузка модели: pinned revision вместо плавающего latest
fs-verity честно защищает любые байты, даже не те, которые вы собирались развернуть. Поэтому идентичность начинается до enable. В Hugging Face Hub параметр revision принимает branch, tag или commit hash, но для воспроизводимого rollout нужен commit hash. В официальной документации commit рассматривается как immutable revision, а snapshot_download() умеет фильтровать файлы через allow_patterns и сигнализировать о неполном offline snapshot.
Для Safetensors это особенно удобно: формат не использует произвольное выполнение pickle-кода при обычной загрузке и рассчитан на безопасное хранение tensor data. Но «безопасный формат» не означает «доверенная модель». Конфигурация, tokenizer, custom code и сами tensor values всё равно должны приходить из разрешённой ревизии. Не включайте trust_remote_code=True без отдельного review и pinning кода.
from huggingface_hub import snapshot_download
path = snapshot_download(
repo_id="ORG/MODEL",
revision="FULL_COMMIT_SHA",
local_dir="/srv/models/staging/FULL_COMMIT_SHA",
allow_patterns=["*.safetensors", "*.json", "tokenizer*"],
)
print(path)
После скачивания проверьте, что staging содержит ожидаемые shards, index JSON ссылается только на существующие файлы, а свободного места хватает и для новой ревизии, и для предыдущей. Это ваш storage budget: минимум две полные рабочие ревизии плюс временный staging. Не называйте конкретный коэффициент универсальным — sharding и auxiliary files у моделей различаются.
Скопируйте файлы в новый каталог через временное имя на том же filesystem, выполните sync, затем атомарно переименуйте каталог. Только после этого начинайте строить verity metadata. Если downloader пишет напрямую в каталог, где уже включён fs-verity, первая же попытка обновить shard закономерно получит EPERM; это защита, а не поломка.
Включение fs-verity и доверенный manifest
Операция fsverity enable необратима для inode. Сначала убедитесь, что файл закрыт на запись и больше не будет меняться. Для LLM-репозитория защищайте не только *.safetensors, но и конфигурацию, tokenizer и index JSON, если именно эти файлы определяют поведение loader. Символические ссылки измерять бессмысленно: включайте verity на конечных regular files.
set -euo pipefail
REV=/srv/models/revisions/FULL_COMMIT_SHA
MANIFEST=/srv/model-manifests/FULL_COMMIT_SHA.fsverity
: > "$MANIFEST.tmp"
find "$REV" -type f -print0 | sort -z | while IFS= read -r -d '' file; do
fsverity enable "$file"
digest=$(fsverity measure "$file" | awk '{print $1}')
rel=$(realpath --relative-to="$REV" "$file")
printf '%s %s\n' "$digest" "$rel" >> "$MANIFEST.tmp"
done
chmod 0444 "$MANIFEST.tmp"
mv "$MANIFEST.tmp" "$MANIFEST"
Manifest храните вне model volume и доставляйте через отдельный доверенный канал: signed release artifact, конфигурационный репозиторий с review или secret/config mechanism с жёсткими правами. Обычный root-owned файл на том же writable диске защищает от случайностей, но не создаёт сильной границы против root-компрометации.
Не путайте fs-verity digest с обычным SHA-256 файла. Digest включает descriptor и корень дерева Меркла; сравнивать нужно вывод fsverity measure с таким же типом digest из manifest. Хорошая проверка также убеждается, что набор файлов совпадает полностью: отсутствие ожидаемого файла и появление лишнего должны завершать gate ошибкой.
Для больших моделей стоимость построения дерева платится один раз при подготовке revision. Скрытая metadata занимает дополнительное место; документация ядра оценивает дерево для больших файлов примерно как небольшую долю от исходного размера, но планировать storage лучше по фактическому du на своих shards, а не по чужой цифре.

Service gate: не запускать inference с неизвестными байтами
Теперь нужен trusted userspace, который свяжет path, revision и digest. Минимальный verifier читает manifest, убеждается, что каждый путь остаётся внутри revision directory, вызывает fsverity measure, сравнивает digest и завершается ненулевым кодом при первом расхождении. Важно проверять effective path из того же namespace, в котором стартует inference service: host-скрипт, проверяющий один каталог, не должен запускать контейнер с другим bind mount.
#!/usr/bin/env bash
set -euo pipefail
REV=${1:?revision directory}
MANIFEST=${2:?trusted manifest}
while read -r expected rel; do
file=$(realpath -e "$REV/$rel")
case "$file" in "$REV"/*) ;; *) echo "path escape: $rel" >&2; exit 20;; esac
actual=$(fsverity measure "$file" | awk '{print $1}')
test "$actual" = "$expected" || {
echo "digest mismatch: $rel" >&2
exit 21
}
done < "$MANIFEST"
echo "fs-verity gate passed"
[Unit]
Description=LLM inference with fs-verity gate
After=local-fs.target network-online.target
[Service]
Type=simple
User=llm
Group=llm
ExecStartPre=/usr/local/libexec/verify-model-verity.sh /srv/models/current /srv/model-manifests/current.fsverity
ExecStart=/usr/local/bin/llm-server --model /srv/models/current
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Если ExecStartPre завершается ошибкой, основной процесс не стартует. Это failure-visible readiness: балансировщик не должен видеть endpoint, а alert должен содержать revision и имя первого конфликтного файла, но не секреты и не полный внутренний manifest. Не ставьте перед командой префикс, игнорирующий exit code, иначе gate станет декоративным.
Положительный тест: разрешённая ревизия проходит verifier, сервис стартует и сообщает ожидаемый model identifier. Отрицательный тест: соседняя, корректная, но неразрешённая ревизия должна быть отвергнута из-за другого digest. Такая проверка безопаснее эксперимента с raw block corruption на production-диске.

Application gate: доказать, что загружена именно эта модель
Infrastructure gate отвечает: «файлы имеют разрешённые digest и будут проверяться при чтении». Он не отвечает: «сервер загрузил все shards, использовал нужный tokenizer и готов обслуживать запросы». Для этого нужен отдельный application gate.
После старта зафиксируйте pinned identity в метриках: repository, полный commit, manifest digest и software image digest. Затем выполните короткий smoke test, который заставляет loader прочитать модель и выдаёт детерминированно проверяемый признак готовности. Это не benchmark качества и не обещание одинаковых токенов между разными kernels; достаточно проверить успешную загрузку, ожидаемый model ID, отсутствие read errors и базовый inference response.
Отрицательная граница обязательна. Подмените ссылку current на подготовленную, но отсутствующую в доверенном manifest ревизию на тестовом узле. Service gate должен остановить запуск до инициализации GPU. Затем верните разрешённую ссылку и убедитесь, что readiness восстановилась. Такой контрфактический тест доказывает причинность: сервис разрешает не «любой читаемый каталог», а только утверждённую identity.
Наконец, читайте весь набор shards во время canary или запускайте реальную загрузку модели. fsverity measure подтверждает закреплённый digest, но ошибки повреждённых data blocks проявляются именно при чтении. Для mmap-loader это может быть SIGBUS, поэтому supervisor и alerting должны отличать integrity incident от обычного OOM.
set -euo pipefail
sudo systemctl stop llm-inference
sudo ln -sfn /srv/models/revisions/UNAPPROVED_SHA /srv/models/current
if sudo systemctl start llm-inference; then
echo "ERROR: unapproved revision started" >&2
exit 1
fi
sudo ln -sfn /srv/models/revisions/APPROVED_SHA /srv/models/current
sudo systemctl start llm-inference
systemctl is-active --quiet llm-inference
Rollout без перезаписи: canary, promotion и быстрый rollback
Не обновляйте защищённую ревизию на месте. Правильная единица изменения — новый каталог с новым commit и manifest. Pipeline выглядит так: download в staging, проверка полноты, перенос на целевой filesystem, fsverity enable, сохранение digest manifest, canary load, application tests и только потом promotion ссылки current.
Canary должен читать модель с того же типа диска и тем же runtime, что production. Если он проходит, раскатывайте revision по узлам постепенно, сохраняя предыдущий каталог. Смена symlink атомарна, но уже работающий процесс может держать открытые inode старой ревизии; это хорошо для стабильности, однако нужно явно перезапустить или дренировать процесс, чтобы новая identity действительно вступила в силу.
Storage budget здесь важнее красоты pipeline. Держите рабочую, предыдущую и staging revision, пока rollout не завершён. Если места не хватает, сборщик может удалить rollback target в самый неподходящий момент. Политику garbage collection связывайте не с возрастом каталога, а с фактом, что ни один process не держит inode, revision не является current/previous и rollout закрыт.
С fs-verity rollback прост: ссылка возвращается на уже защищённый неизменяемый каталог, verifier проверяет старый manifest, после чего сервис снова проходит application gate. Не нужно переписывать гигабайты поверх текущего файла — и именно это сокращает время восстановления.

Что делать при EIO, SIGBUS или digest mismatch
Integrity incident не лечится повторным запуском в цикле. Первый шаг — убрать узел из балансировки и сохранить evidence: revision, manifest digest, kernel version, filesystem, сообщения kernel log, exit status и имя файла. Не пытайтесь «починить» verity-file через chmod или запись поверх: inode неизменяем по дизайну.
Разделите симптомы. digest mismatch до старта чаще означает замену path/inode, неверный manifest или rollout не той ревизии. EIO во время read() и SIGBUS у mmap-loader указывают, что прочитанный блок не совпал с деревом. EPERM при записи обычно означает, что downloader попал в immutable каталог. Один и тот же алерт «модель не стартует» скрывает три совершенно разных runbook.
Восстановление выполняйте новым inode. Загрузите тот же pinned commit в чистый staging, снова проверьте upstream identity, перенесите файлы, включите fs-verity и сравните digest с доверенным manifest. Если digest совпал, замените ссылку и прогоните оба gate. Если не совпал, не «обновляйте manifest под фактические байты» — это уничтожает смысл контроля. Эскалируйте проблему к источнику артефакта или pipeline.
Проверьте соседний слой. Ошибка могла возникнуть из-за NVMe media error, памяти, filesystem corruption или ошибочной автоматизации. Посмотрите SMART/NVMe health и kernel logs, но не объявляйте диск виновным только по одному EIO. И наоборот, успешный rollback не закрывает incident: он возвращает сервис, а root cause analysis отвечает, почему байты изменились.

Ограничения и соседние механизмы защиты
fs-verity не шифрует модель и не ограничивает чтение. Для конфиденциальности нужны права доступа, изоляция workload, encryption at rest и контроль выгрузки данных. Общую threat model полезно сверить со статьёй о безопасности AI-систем. Safetensors уменьшает риск небезопасной десериализации, но не заменяет контроль происхождения и digest.
Механизм также не стабилизирует производительность. Первое чтение по-прежнему зависит от NVMe, page cache и соседней нагрузки. Если integrity rollout совпал с I/O storm, разделяйте безопасность и resource control: рекомендации по cgroup v2 и systemd собраны в материале про I/O-изоляцию LLM-узла. Сначала докажите, что gate разрешил нужные байты, затем отдельно измеряйте model load time и latency.
Для read-only образа целого узла лучше подходит dm-verity. Для системной политики исполнения можно рассмотреть IMA appraisal или IPE; документация ядра IPE умеет использовать свойства fsverity_digest и fsverity_signature, но это уже kernel-enforced policy с отдельной моделью развертывания. Для обычного inference service trusted manifest и строгий pre-start verifier часто дают более понятный первый шаг.
Наконец, не включайте глобальный fs.verity.require_signatures=1 без инвентаризации. Он требует корректно подписанных verity-файлов и доверенных сертификатов в keyring. Ошибка в boot-time key provisioning способна остановить все consumers. Начните с userspace gate, протестируйте rotation ключей и только потом решайте, нужна ли встроенная signature enforcement.
Практический итог
fs-verity полезен не потому, что добавляет ещё один hash в pipeline. Он превращает весовые файлы в неизменяемые объекты, проверяемые ядром при чтении, и даёт быстрый digest для identity gate. Но настоящая защита появляется только в связке: pinned upstream revision, отдельный staging, доверенный manifest, pre-start verifier, application smoke test и rollback на заранее подготовленный inode.
Начните с одного canary-узла и одного model revision. Подтвердите поддержку filesystem probe-файлом, защитите все shards и конфигурацию, проверьте отрицательный сценарий с неразрешённой ревизией, затем измерьте полную загрузку. Когда оба gate устойчиво проходят, переносите схему на остальные GPU-серверы и добавляйте alerts для mismatch, EIO и SIGBUS.
Так вы получите не абстрактную «защиту модели», а конкретное эксплуатационное свойство: inference либо запускается с утверждёнными байтами, либо останавливается до приёма трафика. Для production это куда ценнее тихой надежды, что каталог на NVMe никто не трогал.