Skip to content

0063. Реестр образов OCI поверх артефактов ​

Статус: accepted. Owner: Ian Panaev. Дата: 2026-10-03. Расширяет публичный HTTP-контракт вторым протоколом рядом с /api/v1 (ADR 0045); хранение и права — прежние.

Контекст ​

Команды, которые держат в Arkvory пакеты и файлы, собирают и контейнерные образы. Отдельный реестр (Harbor, registry:2) означает второе хранилище, свои ключи, квоты и резервные копии. Docker, containerd, Podman, Buildx, Helm (OCI) и ORAS говорят на одном протоколе — OCI Distribution. Если Arkvory говорит на нём сам, образы получают те же права, квоты, проверку SHA-256 и резервные копии, что и остальные артефакты.

Решение ​

Протокол. Подмножество OCI Distribution 1.1 под /v2/ на том же шлюзе, что и /api/v1:

  • pull: GET|HEAD /v2/<name>/manifests/<tag|digest>, GET|HEAD /v2/<name>/blobs/<digest> с Range, GET /v2/<name>/tags/list?n=&last= с Link на следующую страницу;
  • push: POST /v2/<name>/blobs/uploads/ (сессия либо весь blob сразу с ?digest=), PATCH частями с Content-Range, PUT …?digest= с последней частью или без неё, GET состояния, DELETE сессии; PUT /v2/<name>/manifests/<ref>;
  • удаление: DELETE /v2/<name>/manifests/<digest> удаляет манифест и все его теги, DELETE …/manifests/<tag> — только тег; DELETE blob отвечает 405;
  • GET /v2/ — проверка версии; ответы несут Docker-Distribution-API-Version: registry/2.0, ошибки — конверт {errors:[{code,message,detail:{requestId}}]} с кодами спецификации.

Имена. <name> = <repository>/<image>: первый сегмент — репозиторий Arkvory (граница доступа), остальное — имя образа из одного или нескольких компонентов. Пример: docker push arkvory.example.com/releases/team/web:1.4. Дайджесты — только sha256: Arkvory проверяет каждый объект по SHA-256. Реестру нужен корень хоста: Docker не поддерживает префикс пути, поэтому /v2 проксируется как есть.

Аутентификация. Сервис токенов не нужен: docker login получает вызов WWW-Authenticate: Basic realm="Arkvory" и присылает ключ Arkvory как пароль Basic, имя пользователя не проверяется. Подходят сервисный ключ, персональный токен и файловый ключ; Bearer тоже принимается. Отозванный или истёкший ключ — 401 UNAUTHORIZED, нехватка прав — 403 DENIED. Только на маршрутах /v2 учётные данные принимаются в Basic; /api/v1 по-прежнему принимает только Bearer.

Права. Те же действия, что у артефактов:

  • pull — content.read, список тегов — artifact.list (файловые ключи и группы — read);
  • push — upload.create, upload.write, upload.complete (write); токен только для чтения и зеркало (ADR 0058) push не принимают;
  • удаление манифеста или тега — artifact.delete, как у retention: только сервисный ключ с этим действием.

Хранение. Blob и манифест — обычные артефакты: имя — дайджест, метка oci, metadata oci.kind (blob/manifest), у манифеста ещё oci.image и oci.mediaType. Квоты, ёмкость, проверка SHA-256, маршрутизация хранилища и резервные копии — общие. Индекс реестра в БД (миграция 30, только расширение схемы):

  • arkvory_oci_blobs — дайджест → артефакт, общий для образов одного репозитория. Повторный push того же слоя ничего не хранит дважды. Монтирования между репозиториями нет: запрос mount получает обычную сессию загрузки, как разрешает спецификация.
  • arkvory_oci_manifests, arkvory_oci_tags — манифесты и теги образа;
  • arkvory_oci_references — что манифест ссылается на blob или дочерний манифест;
  • arkvory_oci_uploads — незавершённые загрузки (в резервные копии не входят).

Загрузка. Части пишутся в <data>/oci-uploads/<id> на томе хранилища:

  • Запись идёт потоком с ограничением полосы и сроками загрузки, как у /api/v1; запас свободного места проверяется каждые 64 MiB; каждая часть синхронизируется на диск до того, как строка БД продвинется.
  • Часть должна начинаться с уже принятого размера, иначе 416. Байты, оставленные прерванным запросом, отрезаются при следующей части.
  • Одновременные запросы к одной сессии отклоняются: шлюз записи один, сериализация в процессе.
  • PUT ?digest= копирует принятые байты в хранилище артефактов, которое само сверяет размер и SHA-256. При несовпадении артефакт отменяется и возвращается DIGEST_INVALID. Повтор после сбоя находит тот же артефакт по ключу сессии.
  • Временно нужно до двух объёмов слоя.
  • Сессия без активности сутки забывается вместе с файлом. Это делает обслуживание шлюза записи раз в минуту по часам БД; файлы без сессии удаляются по времени изменения.
  • Шлюз чтения (ADR 0009) обслуживает только GET|HEAD.

Манифесты. Принимаются OCI image manifest и image index, Docker schema 2 manifest и manifest list:

  • Ограничения: не больше 4 MiB, schemaVersion: 2. Тип берётся из Content-Type или поля mediaType, и они не должны расходиться. Schema 1 не поддерживается.
  • Дайджест считает сервер; PUT по дайджесту должен с ним совпасть.
  • Всё, на что ссылается манифест (config и слои, у индекса — платформенные манифесты того же образа), должно быть в репозитории, иначе MANIFEST_BLOB_UNKNOWN. Проверка повторяется при фиксации под той же блокировкой изменений каталога, под которой удаляет retention.
  • После фиксации манифест и всё, на что он ссылается, защищены.

Retention. Артефакт манифеста и blob, на который ссылается хранимый манифест, получают уже существующую причину защиты reference. Новых значений в контракте retention нет.

  • Тег можно удалить, а образ всё равно будет доступен по дайджесту и защищён.
  • После DELETE манифеста по дайджесту защита снимается. Его артефакт и неиспользуемые слои удаляет retention или администратор. Если удалённый слой понадобится снова, реестр сообщит, что его нет, и примет push заново.
  • Blob, загруженный без манифеста, не защищён.

Ошибки. Отказы Arkvory сохраняют HTTP-статус и текст, код переводится: unauthorized → UNAUTHORIZED (с вызовом Basic), forbidden, conflict, capacity_exceeded → DENIED, not_found → NAME_UNKNOWN, busy, rate_limited → TOOMANYREQUESTS (с Retry-After), unavailable → UNAVAILABLE, integrity_mismatch → DIGEST_INVALID, прочие ошибки ввода → UNSUPPORTED. Метрики и журналы видят исходный код Arkvory.

Последствия ​

  • Маршруты /v2 следуют спецификации OCI, а не OpenAPI Arkvory: они исключены из инвентаря /api/v1 явно, SDK и CLI их не дублируют, клиент — любой OCI-совместимый.
  • Перед Arkvory не нужен второй сервис; прокси пропускает /v2 без буферизации тела (client_max_body_size 0, как в примере nginx).
  • Не сделано: Referrers API (404, клиенты переходят на схему тегов), /_catalog, монтирование между репозиториями, сервис токенов Bearer, pull-through cache, перенос образов в режиме импорта, автоматическая сборка манифестов без тегов, раздел консоли для образов.
  • Зеркала переносят образы: каждое изменение индекса пишет запись ленты (oci.blob, oci.manifest, oci.tag, oci.manifest.delete, oci.tag.delete) в своей транзакции (дополнение ADR 0058).

Проверки ​

  • tests/oci-registry.test.mjs:
    • разбор путей, включая вложенные имена и слова маршрута в имени;
    • отказы по именам, ссылкам и алгоритмам;
    • разбор четырёх типов манифестов и их ошибок;
    • порядок частей, чужая сессия, сериализация запросов, отмена артефакта при несовпадении дайджеста.
  • tests/integration/oci-registry.test.mjs:
    • вызов docker login и Basic;
    • push частями и целиком, pull по тегу и дайджесту, Range;
    • права чтения и записи, повторный push без новых артефактов, страницы тегов;
    • неизвестные blob и манифест, неверный дайджест, пропуск в частях, Referrers и прочие маршруты;
    • защита retention до удаления манифеста и повторный push после неё;
    • индекс с платформенными манифестами, истечение брошенных загрузок.
  • tests/integration/backup-schema.test.mjs сверяет реестр таблиц резервной копии с миграцией 30.

Дополнение: проверка перед 0.4.0 ​

  • Байты незавершённых сессий загрузки учитываются в квоте репозитория и ёмкости установки до записи каждого куска. Иначе брошенные сессии заполняли том мимо любой квоты. Отказ — 507 DENIED (storage_quota).
  • Тело блоба не разбирается ни при каком Content-Type: клиент может объявить application/json.
  • Записи индекса с типом манифеста (или без типа) — манифесты образа, остальные — блобы репозитория. Так кэш BuildKit (--cache-to type=registry) хранит слои и конфигурацию кэша. Обе группы попадают в ссылки, и retention их не удаляет.
  • Страницы тегов сравнивают курсор в порядке байтов, как и сортируют: при сортировке базы en_US страницы пропускали теги.

Arkvory · ProAnimaStudio