Files
admin/.wiki/concepts/registry-oci-image-index-gc.md
vitya 93b4ef1d13 docs(wiki): dangling image-index finding — registry full of tag-tombstones
Перепрогон 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>
2026-06-18 12:51:28 +03:00

68 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: registry.kzntsv.site — books-* образы это OCI image-index (multi-manifest) → дата живёт в sub-manifest, GC должен спускаться
type: concept
tags: [registry, vds-kzntsv, oci, buildx, docker, gc, manifest, gotcha]
sources: [../sources/vds-kzntsv-bootstrap-2026-05-20.md]
related: [registry-kzntsv-auth-model, registry-gc-mount-and-modify-flag, bindmount-config-edit-preserve-mode]
updated: 2026-06-18
---
# books-* образы в registry = OCI image-index, не плоский манифест
books-* образы (собраны `docker buildx`) пушатся в [`registry.kzntsv.site`](registry-kzntsv-auth-model.md) как **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`:
```json
{ "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`](registry-gc-mount-and-modify-flag.md).)
**Ловушка для 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 над этим реестром
1. **Accept на manifest-fetch** обязан включать и index, и manifest-list, и oci.image.manifest, и docker.manifest.v2 — иначе registry отдаёт не тот тип / 404 (та же media-type-гоча, что для HEAD тега в [`registry-kzntsv-auth-model`](registry-kzntsv-auth-model.md)).
2. **Дату/размер тянуть из платформенного sub-manifest**, спустившись через index. Выбор записи: `platform.os != "unknown"` и `vnd.docker.reference.type != "attestation-manifest"`.
3. **DELETE — по digest INDEX'а** (digest, на который указывает тег; берётся из `Docker-Content-Digest` HEAD-заголовка тега), НЕ по sub-manifest. Удаление sub осиротит index. Два разных digest — index-digest для DELETE, sub.config для `.created`.
4. **Одна «версия» = один index** (включает и платформенный manifest, и attestation). Группировать на удаление по index-digest; attestation удаляется вместе с index. keepLastN считать по образам, не по записям `.manifests`.
5. Disk-reclaim после mark-delete — отдельный host-cron `registry garbage-collect`, см. [`registry-gc-mount-and-modify-flag`](registry-gc-mount-and-modify-flag.md). v2 DELETE сам место не возвращает.
## Связь
- [`registry-kzntsv-auth-model`](registry-kzntsv-auth-model.md) — auth/htpasswd, юзер books-ci, GC через v2 DELETE.
- [`bindmount-config-edit-preserve-mode`](bindmount-config-edit-preserve-mode.md) — как клали кред books-ci в task-runner config (shadow + mode гочи).
- Хост: [`books-vds`](../entities/books-vds.md) (task-runner), реестр на [`vds-kzntsv`](../entities/vds-kzntsv.md).