Перепрогон registryGc dryRun на descent-фиксе (master-dcd7c91) вскрыл 2-й root-cause: реестр засорён dangling OCI image-индексами — тег жив, но его платформенный sub-manifest отдаёт MANIFEST_UNKNOWN (вычищен прежним host-side registry garbage-collect, не следящим index->child для multi-arch). books-web: 19 тегов -> 3 датируемых, 16 dangling. date-based GC защищает dangling как null-dated -> никогда не удаляет, хотя они и есть мусор (логика задом наперёд). Рекомендация books: различать transient-error (protect) vs MANIFEST_UNKNOWN (eligible for delete). freedBytes от dangling ~0 (слои уже вычищены). +registry-oci-image-index-gc.md (раздел) +index.md +log.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7.2 KiB
title, type, tags, sources, related, updated
| title | type | tags | sources | related | updated | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| registry.kzntsv.site — books-* образы это OCI image-index (multi-manifest) → дата живёт в sub-manifest, GC должен спускаться | concept |
|
|
|
2026-06-18 |
books-* образы в registry = OCI image-index, не плоский манифест
books-* образы (собраны docker buildx) пушатся в registry.kzntsv.site как OCI image-index (application/vnd.oci.image.index.v1+json), а не как одиночный image-manifest. Это меняет всё, что трогает дату/размер/удаление образа.
Структура (факт, books-web:master, 2026-06-18)
tag master → OCI image-index (mediaType: ...image.index.v1+json)
.config.digest = null ← у index НЕТ top-level config
.manifests = [
{ mediaType: ...image.manifest.v1+json, platform: {amd64, linux} }, ← реальный образ
{ mediaType: ...image.manifest.v1+json, platform: {unknown, unknown}, ← attestation, скипать
annotations["vnd.docker.reference.type"] = "attestation-manifest" }
]
Дата сборки (.created) не на верхнем уровне — она в config-blob платформенного sub-manifest:
index → .manifests[] где platform.os/arch != "unknown" → <sub>.config.digest → GET /v2/<repo>/blobs/<digest> → .created
Подтверждено: books-web amd64-sub .config.digest=sha256:45249a6…, blob → "created":"2026-06-18T08:56:51Z". Дата валидная — просто на уровень глубже.
Почему это ломает наивный registry-GC (no-op-баг)
Если GC берёт .created из top-level манифеста тега — у image-index его НЕТ (config.digest=null), getCreated возвращает null для всех тегов. Если планировщик удаления защищает группы с created==null (разумный fail-safe «не удаляю то, что не датировал») — drop пуст всегда, keepLastN не применяется, реестр не чистится. Симптом в отчёте: по каждой репе keep = tags − 1, drop = 0 (−1 = buildcache-тег, отваливается в errors при резолве).
Зафиксировано на books registryGc dryRun 2026-06-18 (packages/task-runner/lib/registryV2.js: getCreated→null, planDeletions ставит protected любой null-dated группе). Auth при этом исправен (books-ci, 401 нет) — баг чисто в обходе manifest-дерева, не в доступе.
Dangling image-индексы — реестр уже засорён ими (2026-06-18)
Бóльшая часть тегов books-* — dangling index: тег указывает на живой OCI image-index, но его платформенный sub-manifest при фетче отдаёт MANIFEST_UNKNOWN:
{ "errors":[{"code":"MANIFEST_UNKNOWN","message":"manifest unknown",
"detail":{"Name":"books-web","Revision":"sha256:8d2b447a…"}}] }
Sub-manifest (и его слои) физически удалены, а тегированный родитель-index остался. Замер books-web 2026-06-18: 19 не-buildcache тегов → 3 датируемых (живых), 16 dangling.
Происхождение: классическое последствие host-side registry garbage-collect, который для multi-arch НЕ следует ссылкам index→child → удаляет детей-манифесты и блобы, оставляя тег-надгробие на верхнем index'е. (См. registry-gc-mount-and-modify-flag.)
Ловушка для date-based GC: getCreated на dangling вернёт null (sub недоступен) → планировщик защитит группу как «недатированную» → dangling никогда не удаляется, хотя это лучший кандидат на чистку. Логика задом наперёд, если не различать:
- transient resolve error (5xx/таймаут) → protect, safe;
MANIFEST_UNKNOWNна sub (dangling) → eligible for delete (не protect); даты нет — в хвост сортировки или дроп безусловно, образ уже сломан (docker pullпо нему падает).
freedBytes-нюанс: удаление dangling-индекса освобождает ~0 байт (слои уже вычищены прежним GC) — это гигиена tag-list + удаление битых pull-целей, не disk-reclaim. Реальное место по keepLastN экономится только на живых датируемых образах.
Правила для любого GC/cleanup над этим реестром
- Accept на manifest-fetch обязан включать и index, и manifest-list, и oci.image.manifest, и docker.manifest.v2 — иначе registry отдаёт не тот тип / 404 (та же media-type-гоча, что для HEAD тега в
registry-kzntsv-auth-model). - Дату/размер тянуть из платформенного sub-manifest, спустившись через index. Выбор записи:
platform.os != "unknown"иvnd.docker.reference.type != "attestation-manifest". - DELETE — по digest INDEX'а (digest, на который указывает тег; берётся из
Docker-Content-DigestHEAD-заголовка тега), НЕ по sub-manifest. Удаление sub осиротит index. Два разных digest — index-digest для DELETE, sub.config для.created. - Одна «версия» = один index (включает и платформенный manifest, и attestation). Группировать на удаление по index-digest; attestation удаляется вместе с index. keepLastN считать по образам, не по записям
.manifests. - Disk-reclaim после mark-delete — отдельный host-cron
registry garbage-collect, см.registry-gc-mount-and-modify-flag. v2 DELETE сам место не возвращает.
Связь
registry-kzntsv-auth-model— auth/htpasswd, юзер books-ci, GC через v2 DELETE.bindmount-config-edit-preserve-mode— как клали кред books-ci в task-runner config (shadow + mode гочи).- Хост:
books-vds(task-runner), реестр наvds-kzntsv.