Korrupte/verwaiste Repos sichtbar machen + Live-GC sicher

Zwei Probleme nach dem alten --delete-untagged-Schaden:

1) Repos mit Tags aber ohne _layers (verwaist) tauchen nicht im
   Registry-Katalog auf -> waren in der Web-Oberflaeche unsichtbar und
   nicht loeschbar. images() listet jetzt zusaetzlich direkt aus dem
   Dateisystem (scan_fs_repositories/repo_tags), Vereinigung mit dem
   Katalog. So sind auch korrupte Repos sichtbar und ueber den
   Loeschen-Button entfernbar.

2) Die GC laeuft als separater Prozess auf dem gemounteten Speicher.
   Der In-Memory-Blob-Cache der laufenden Registry hielt geloeschte
   Blobs faelschlich fuer vorhanden -> erneuter Pull legte keine
   _layers-Verknuepfung an (Image fehlte im Katalog). Cache via
   REGISTRY_STORAGE_CACHE_BLOBDESCRIPTOR="" deaktiviert -> Live-GC sicher.

Verifiziert: korruptes Repo -> in Liste sichtbar -> Loeschen -> GC ->
erneut pushen -> Katalog korrekt und docker pull erfolgreich, ohne
Registry-Neustart.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-09 13:06:18 +02:00
parent b24bd96917
commit 1a1b4500d6
2 changed files with 41 additions and 5 deletions
+35 -5
View File
@@ -236,6 +236,36 @@ def _safe_repo_path(*parts):
return target return target
def scan_fs_repositories():
"""Listet alle Repositories direkt aus dem Speicher (Dateisystem).
Erfasst auch Repositories, die der Registry-Katalog (/v2/_catalog) nicht
zeigt - etwa nach Korruption, wenn das _layers-Verzeichnis fehlt.
"""
base = _repositories_base()
repos = []
if not os.path.isdir(base):
return repos
for root, dirs, _files in os.walk(base):
if "_manifests" in dirs:
name = os.path.relpath(root, base).replace(os.sep, "/")
if name != ".":
repos.append(name)
dirs[:] = [] # innerhalb eines Repos nicht weiter absteigen
return repos
def repo_tags(name):
"""Tags eines Repositories - aus dem Dateisystem, mit API als Fallback."""
tags_dir = _safe_repo_path(*name.split("/"), "_manifests", "tags")
if tags_dir and os.path.isdir(tags_dir):
return sorted(
d for d in os.listdir(tags_dir) if os.path.isdir(os.path.join(tags_dir, d))
)
data = query_registry(f"/v2/{name}/tags/list")
return sorted(data.get("tags") or []) if data else []
def remove_repository_storage(name): def remove_repository_storage(name):
"""Entfernt das leere Repository-Verzeichnis aus dem Registry-Speicher. """Entfernt das leere Repository-Verzeichnis aus dem Registry-Speicher.
@@ -389,13 +419,13 @@ def users():
@app.route("/images") @app.route("/images")
@login_required @login_required
def images(): def images():
repos = [] # Repos aus dem Dateisystem UND dem Katalog (Vereinigung), damit auch
# Repositories sichtbar/loeschbar sind, die der Katalog nicht zeigt.
names = set(scan_fs_repositories())
data = query_registry("/v2/_catalog") data = query_registry("/v2/_catalog")
if data: if data:
for name in sorted(data.get("repositories", [])): names.update(data.get("repositories", []))
tags_data = query_registry(f"/v2/{name}/tags/list") repos = [{"name": name, "tags": repo_tags(name)} for name in sorted(names)]
tags = sorted(tags_data.get("tags", [])) if tags_data and tags_data.get("tags") else []
repos.append({"name": name, "tags": tags})
return render_template("images.html", repos=repos) return render_template("images.html", repos=repos)
+6
View File
@@ -34,6 +34,12 @@ services:
REGISTRY_AUTH_HTPASSWD_REALM: Docker Registry REGISTRY_AUTH_HTPASSWD_REALM: Docker Registry
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
REGISTRY_STORAGE_DELETE_ENABLED: "true" REGISTRY_STORAGE_DELETE_ENABLED: "true"
# Blob-Cache deaktivieren: Die Garbage Collection laeuft als separater
# Prozess auf dem gemounteten Speicher. Mit aktivem In-Memory-Cache wuerde
# die laufende Registry geloeschte Blobs faelschlich fuer vorhanden halten
# und beim erneuten Pull keine Layer-Verknuepfung anlegen (Image fehlt dann
# im Katalog / "manifest unknown"). Ohne Cache ist die Live-GC sicher.
REGISTRY_STORAGE_CACHE_BLOBDESCRIPTOR: ""
volumes: volumes:
- ./data/registry:/var/lib/registry - ./data/registry:/var/lib/registry
- ./data/htpasswd:/auth:ro - ./data/htpasswd:/auth:ro