homelab notes ポート開放できない家の自宅サーバー

:latest は「新しい」を保証しない

:latest が保証するのは「メンテナが最後に push したもの」だけ。 メンテナが手を止めたら、タグはそこで凍る。同じサーバーで2件見つかった。

1件目: プロジェクトが引っ越していた

ダッシュボードのコンテナが起動しなくなっていた。イメージを見たら ビルド日が4年前だった。

調べたら、プロジェクトが別の組織アカウントに移籍していて、 旧リポジトリは放棄されていた。新しいリリースは全部新しい場所に出ている。 :latest を指したまま4年放置されていたことになる。

old-namespace/app:latest        ビルド 4年前   ← 参照していたもの
ghcr.io/new-org/app:v1.76.2     ビルド 8日前   ← 実際の最新

2件目: 非公式ミラーが止まっていた

チャット UI のイメージが :0 という浮動タグだった。 Docker Hub の説明文を読んで気づいた。

Automated (unofficial) Docker Hub mirror of tagged images on ... GHCR repo

非公式ミラーで、5ヶ月前に更新が止まっていた。 本家は GHCR にあり、その間に3マイナーバージョン進んでいた。

厄介なのは、ミラーの中では :0 は「最新」で、ダイジェスト比較でも 「更新なし」と出ること。ミラーごと死んでいるので、ミラー内部の整合性は保たれている。

どう検出するか

ダイジェスト比較だけでは足りない。この2つを両方見る。

見るものわかること
ローカルとレジストリのダイジェストpull すれば新しくなるか
バージョンタグの最終更新日リポジトリが生きているか

浮動タグ(:latest, :0 など)を使っていて、 かつバージョンタグが数ヶ月増えていないものは、放棄を疑う。

ダイジェストの取り方に注意

最初 docker manifest inspect --verbose の結果を使ったら、 ほぼ全部が「更新あり」になった。誤りだった。

--verbose はマルチアーキイメージだとアーキ別マニフェストの配列を返す。 その先頭の digest と、ローカルの RepoDigests(マニフェストリストの digest)は そもそも別物なので、比較しても意味がない。

レジストリ API に HEAD して Docker-Content-Digest を見るのが確実。

curl -sI \
  -H 'Accept: application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.list.v2+json' \
  https://<registry>/v2/<repo>/manifests/<tag> \
  | grep -i '^docker-content-digest'

固定するときの手順

「今動いているバージョン」に固定する。ここで大事なのは、 固定先タグのダイジェストが今のものと同一か、書き換える前に確認すること。

ローカル prom/prometheus:latest  の digest
レジストリ prom/prometheus:v3.13.2 の digest
                → 一致すれば、書き換えても再DLもバージョン変化も起きない

一致を確認してから書き換えれば、固定作業そのものは無変更の操作になる。 バージョンを上げるのはその後、別の作業としてやる。混ぜると切り分けができなくなる。

実際にやった順序

止まっていたミラーの件では、2段階に分けた。

  1. 同じバージョンのまま本家のレジストリに移す(0.8.12 → 0.8.12)
  2. その後で、改めてバージョンを上げる(0.8.12 → 0.11.3)

1 では何も起きないことを確認でき、2 では DB マイグレーションだけを単独で観測できた。 まとめてやると、問題が出たときにレジストリ移行のせいかバージョン更新のせいか分からなくなる。

prune するときの注意

docker image prune -a は使わないほうがいい。 コンテナとして常駐していないが、スクリプトが都度 docker run するイメージalpinerestic/restic など)まで消える。 バックアップスクリプトが次に走るとき、静かに再ダウンロードが始まる。 消すときはイメージ名を明示する。