docs(wiki): OCI image-index GC gotcha + bind-mount config-shadow lesson
Следствие закрытия [books-task-runner-registry-auth-cred]. concepts/registry-oci-image-index-gc.md (new): books-* образы в registry = OCI image-index (buildx), top-level .config=null, дата .created в платформенном sub-manifest. Наивный GC по top-level дате → null у всех → null-dated группы защищаются → drop=0 всегда (keepLastN не применяется). Правила: Accept со всеми media-types, дата из sub, DELETE по index-digest не sub. Зафиксировано на books registryGc dryRun (deleted=0 при 14 tags) + манифест-dump books-web. concepts/bindmount-config-edit-preserve-mode.md: дополнен гочей про bind-mount shadow (config образа затенён целиком → полная секция, не дельта) + worked example task-runner (mode не слетел, постмортем сработал). +index.md (2 строки) +log.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,28 @@ jq ... > "$f.new" && cat "$f.new" > "$f" && rm "$f.new"
|
||||
|
||||
Фикс: `chmod/chown --reference=<backup>` → `-rw-r--r-- root root`. Контейнер сам поднялся на следующей попытке restart-policy (отдельный рестарт не нужен — policy уже крутила). `registryAuth` остался цел.
|
||||
|
||||
## Связанная гоча: bind-mount затеняет config-каталог образа ЦЕЛИКОМ
|
||||
|
||||
Books-стеки монтируют host-каталог поверх config-пути образа:
|
||||
```
|
||||
/opt/books/task-runner/config → /usr/src/app/packages/task-runner/config (rw)
|
||||
/opt/books/job-scheduler/config → /usr/src/app/packages/job-scheduler/config
|
||||
```
|
||||
Bind-mount каталога **полностью заменяет** содержимое целевого каталога — `default.json`, запечённый в образ, **не виден** контейнеру. Читается только host'овый файл.
|
||||
|
||||
**Следствие при добавлении кред-секции:** нельзя класть «только дельту» (один `password`), полагаясь что `baseUrl`/`username` придут из образа — их там нет, образный `default.json` затенён. **Кладётся полная секция.** На этом спотыкаются, когда таска сформулирована как «в git default.json baseUrl+username уже есть, нужен только password» — в git есть, в эффективном (host) конфиге нет.
|
||||
|
||||
Проверка эффективного конфига — изнутри контейнера, не по git:
|
||||
```bash
|
||||
docker exec <ctr> node -e "const c=require('config'); console.log(c.get('registry.baseUrl'))"
|
||||
```
|
||||
|
||||
Host config-каталоги книг от **25.05** (миграция в Portainer) и с тех пор не пере-наливались из образа → дрейфуют от git. node-config читает ровно их. Worked example — `registry`-секция в task-runner (2026-06-18): см. [`registry-oci-image-index-gc`](registry-oci-image-index-gc.md) (кред books-ci для registryGc).
|
||||
|
||||
## Инцидент-2 2026-06-18 (task-runner, без поломки)
|
||||
|
||||
Тот же кред-класс (books-ci для [`registryGc`](registry-oci-image-index-gc.md)), но в `/opt/books/task-runner/config/default.json`, секция **`registry`** (не `docker.registryAuth` — другой потребитель). Учтя оба урока выше: бэкап `cp -a` → `jq > .new && cat .new > "$F"` (запись в тот же inode, mode сохранён) → `ls -l` показал `-rw-r--r-- root root`. Структурный diff: добавлен только ключ `registry`. Рестарт контейнера (node-config читает на старте) → `healthy`, EACCES не было. **Mode-гоча не повторилась — постмортем сработал.**
|
||||
|
||||
## Связанные uid-гочи
|
||||
|
||||
- [`ocis-on-vds-deploy-recipe`](ocis-on-vds-deploy-recipe.md) — UID 1001 vs 1000 на oCIS (тот же класс: контейнерный uid ≠ root, права host-файлов должны это учитывать).
|
||||
|
||||
50
.wiki/concepts/registry-oci-image-index-gc.md
Normal file
50
.wiki/concepts/registry-oci-image-index-gc.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
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-дерева, не в доступе.
|
||||
|
||||
## Правила для любого 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).
|
||||
@@ -23,7 +23,7 @@ Catalog of all wiki pages. One line per page, organized by type. Updated on ever
|
||||
|
||||
- [admin-infra-project](concepts/admin-infra-project.md) — design + migration plan для OpeItcLoc03/admin (canonical)
|
||||
- [backup-inventory-2026-06](concepts/backup-inventory-2026-06.md) — карта всех машин × что реально бэкапится (с доказательством); дыры по приоритету (kreknin SPOF, nl-vds x-ui.db, bookva-*, openwrt) — триггер: MSSQL 3-нед gap
|
||||
- [bindmount-config-edit-preserve-mode](concepts/bindmount-config-edit-preserve-mode.md) — правка bind-mounted config'а через mktemp+mv роняет режим 644→600 → non-root контейнер (uid 1000) EACCES crash-loop; chmod --reference=backup. Инцидент books-job-scheduler 2026-06-18
|
||||
- [bindmount-config-edit-preserve-mode](concepts/bindmount-config-edit-preserve-mode.md) — правка bind-mounted config'а через mktemp+mv роняет режим 644→600 → non-root контейнер (uid 1000) EACCES crash-loop; chmod --reference=backup. + гоча: bind-mount затеняет config образа целиком → класть полную секцию, не дельту. Инциденты books-job-scheduler + task-runner 2026-06-18
|
||||
- [vds-kzntsv-ssh-access](concepts/vds-kzntsv-ssh-access.md) — SSH access audit на vds-kzntsv (infra VDS), retained keys + add/revoke processes (was misnamed `books-ssh-access` до 2026-05-25 — content всегда был про vds-kzntsv)
|
||||
- [compose-bcrypt-escape-trap](concepts/compose-bcrypt-escape-trap.md) — Docker Compose ест `$` в bcrypt hashes
|
||||
- [db-tls-self-signed-via-traefik-raw-tcp](concepts/db-tls-self-signed-via-traefik-raw-tcp.md) — DB TLS через traefik raw TCP с self-signed certs
|
||||
@@ -46,6 +46,7 @@ Catalog of all wiki pages. One line per page, organized by type. Updated on ever
|
||||
- [recovery-architecture-snapshot](concepts/recovery-architecture-snapshot.md) — текущая recovery architecture (2026-05-19/21, attempt 2)
|
||||
- [registry-gc-mount-and-modify-flag](concepts/registry-gc-mount-and-modify-flag.md) — Docker Registry GC mount layout + `-m` flag
|
||||
- [registry-kzntsv-auth-model](concepts/registry-kzntsv-auth-model.md) — registry.kzntsv.site = standalone registry:2 + htpasswd Basic (бинарный доступ, НЕ Gitea-packages, нет per-repo ACL/robot-токенов); как заводить htpasswd-юзеров (hot-reload), GC через v2 DELETE; юзеры vitya + books-ci
|
||||
- [registry-oci-image-index-gc](concepts/registry-oci-image-index-gc.md) — books-* образы в registry = OCI image-index (buildx multi-manifest): top-level `.config`=null, дата `.created` живёт в платформенном sub-manifest; наивный GC по top-level дате → null у всех → no-op (keepLastN не применяется). DELETE по index-digest, дата из sub. Зафиксировано на books registryGc dryRun 2026-06-18
|
||||
- [rusonyx-vps-onboarding-quirks](concepts/rusonyx-vps-onboarding-quirks.md) — Rusonyx VPS onboarding quirks (Astra Облако / myvm.rusonyx.ru)
|
||||
- [traefik-acme-json-to-iis-cert-import](concepts/traefik-acme-json-to-iis-cert-import.md) — Экспорт LE certs из traefik acme.json в IIS (PFX + SNI bindings)
|
||||
- [traefik-file-watch-wsl2-broken](concepts/traefik-file-watch-wsl2-broken.md) — Traefik file-watch broken под Docker Desktop Windows (WSL2 9p mount)
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
Append-only log of wiki operations (ingests, promotions, lints, migrations).
|
||||
|
||||
## [2026-06-18] ingest | concepts/registry-oci-image-index-gc.md (new) + concepts/bindmount-config-edit-preserve-mode.md (расширен) — следствие закрытия [books-task-runner-registry-auth-cred]. (1) Новый концепт: books-* образы в registry = OCI image-index (buildx), top-level `.config`=null, дата `.created` в платформенном sub-manifest → наивный GC по top-level дате даёт null у всех → планировщик защищает null-dated группы → drop=0 всегда (keepLastN не применяется). Правила: Accept со всеми media-types, дата из sub, DELETE по index-digest (не sub), 1 версия=1 index+attestation. Зафиксировано на books registryGc dryRun (deleted=0 при 14 tags). (2) bindmount-постмортем дополнен: bind-mount затеняет config-каталог образа целиком → нужна полная кред-секция, не дельта; + worked example task-runner registry-секция (mode не слетел, постмортем сработал). +index.md (2 строки). Источник: dryRun-верификация registryGc + манифест-dump books-web 2026-06-18.
|
||||
|
||||
## [2026-06-18] ingest | concepts/registry-kzntsv-auth-model.md (new) + concepts/bindmount-config-edit-preserve-mode.md (new) — registry.kzntsv.site = standalone registry:2 + htpasswd Basic (бинарный доступ, НЕ Gitea-packages; нет per-repo ACL/robot-токенов; hot-reload htpasswd; GC через v2 DELETE + host garbage-collect; завёл юзера books-ci для books job-scheduler pull, кред в pass vds-kzntsv/registry-books-ci). Постмортем: правка bind-mounted `default.json` через mktemp+mv уронила режим 644→600 → `books-job-scheduler` EACCES crash-loop (прод-даун slovo-cron), fix chmod/chown --reference=backup. +entities/vds-kzntsv.md (registry-строка) +index.md. Источник: консультация+инфра-деплой books registry-auth + инцидент 2026-06-18.
|
||||
|
||||
## [2026-06-17] ingest | concepts/stostayer-web-deploy-runbook.md (new) — ранбук деплоя легаси web на прод stostayer: канал build(offline)→push docker.stostayer.ru→Portainer-стек 16 через container-IP API (172.18.0.2:9000) с хоста мимо Angie BA; rollback; БЛОКЕР ESM-стена (top-level await @stostayer/data на build + ERR_REQUIRE_ESM @snollajs/snolla в рантайме d02f740) → 0.3.18 остаётся; гоча VPN-IP бан хостером при retry-push-шторме. Источник: попытка деплоя `stostayer-web-complaint-form-deploy` 2026-06-17 (откат на 0.3.18). +index.md.
|
||||
|
||||
Reference in New Issue
Block a user