Files
admin/.wiki/concepts/bindmount-config-edit-preserve-mode.md
vitya 85b13ae6a4 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>
2026-06-18 12:31:41 +03:00

5.9 KiB
Raw Permalink Blame History

title, type, tags, updated
title type tags updated
Правка bind-mounted config'а — сохранять file mode (иначе EACCES crash-loop у non-root контейнера) concept
docker
bind-mount
permissions
gotcha
postmortem
books-vds
eacces
2026-06-18

Bind-mounted config: сохраняй mode при правке

Когда правишь config-файл, bind-mounted в контейнер, который бежит под non-root юзером (например books node, uid 1000), атомарная запись через mktemp+mv теряет исходный режим файла и роняет контейнер в crash-loop с EACCES.

Механизм

jq ... > "$(mktemp)" && mv tmp default.json   # ← баг

mktemp создаёт файл 0600 root. mv (rename) заменяет inode, перенося права temp-файла на цель — режим назначения НЕ сохраняется (в отличие от cp/install). Исходный был 0644; после правки others-read бит пропал → контейнерный uid 1000 не может открыть файл → падение ещё в загрузчике config'а (node lib config, util.parseFile), до любого прикладного кода:

Error: Config file .../default.json cannot be read. Error code is: EACCES

Правильно

Восстановить mode/owner после mv — проще всего по до-правочному бэкапу (cp -a его сохраняет):

chmod --reference="$backup" "$f" && chown --reference="$backup" "$f"

Альтернативы: install -m 644, либо запись в тот же inode (сохраняет mode):

jq ... > "$f.new" && cat "$f.new" > "$f" && rm "$f.new"

Всегда ls -l после — убедиться, что others-read бит на месте для контейнерного uid.

Инцидент 2026-06-18 (worked example)

Добавлял docker.registryAuth (кред books-ci) в /opt/books/job-scheduler/config/default.json на books-vds через jq>mktemp && mv. Режим слетел 644 → 600 root. На ближайшем деплое books (all-tenant пересоздаёт контейнеры) books-job-scheduler ушёл в Restarting (1) каждые ~60спрод-даун, slovo-cron'ы стояли. Корень — не код books и не деплой, а мой способ записи.

Фикс: 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:

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 (кред books-ci для registryGc).

Инцидент-2 2026-06-18 (task-runner, без поломки)

Тот же кред-класс (books-ci для registryGc), но в /opt/books/task-runner/config/default.json, секция registry (не docker.registryAuth — другой потребитель). Учтя оба урока выше: бэкап cp -ajq > .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 — UID 1001 vs 1000 на oCIS (тот же класс: контейнерный uid ≠ root, права host-файлов должны это учитывать).
  • registry-kzntsv-auth-model — что за кред клали, когда словили инцидент.