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:
+35
-5
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user