From bb8748d426788e17000dd26bc9a7c45fcd556fab Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Sun, 29 Mar 2026 19:26:10 -0500 Subject: [PATCH] =?UTF-8?q?feat(registry):=20daily=20GC=20cronjob=20?= =?UTF-8?q?=E2=80=94=20keep=20last=2010=20SHA=20tags=20per=20repo,=20prune?= =?UTF-8?q?=20layers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- servers/legion/k8s/registry/gc-cronjob.yaml | 104 ++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 servers/legion/k8s/registry/gc-cronjob.yaml diff --git a/servers/legion/k8s/registry/gc-cronjob.yaml b/servers/legion/k8s/registry/gc-cronjob.yaml new file mode 100644 index 0000000..d64bdca --- /dev/null +++ b/servers/legion/k8s/registry/gc-cronjob.yaml @@ -0,0 +1,104 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: registry-gc + namespace: registry +spec: + # Run daily at 3am + schedule: "0 3 * * *" + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + jobTemplate: + spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: registry-gc + image: registry:2 + command: + - /bin/sh + - -c + - | + set -e + echo "Starting registry garbage collection..." + + # Keep the last 10 tags per repo; delete the rest via registry API + REGISTRY_URL="http://registry:5000" + + # Get all repositories + REPOS=$(wget -qO- "$REGISTRY_URL/v2/_catalog" | sed 's/.*"repositories":\[//;s/\].*//' | tr ',' '\n' | tr -d '"') + + for repo in $REPOS; do + echo "Processing repo: $repo" + + # Get all tags sorted by date (registry API returns unordered, use digest order) + TAGS=$(wget -qO- "$REGISTRY_URL/v2/$repo/tags/list" | sed 's/.*"tags":\[//;s/\].*//' | tr ',' '\n' | tr -d '"') + TAG_COUNT=$(echo "$TAGS" | grep -c . || true) + + echo " $TAG_COUNT tags found" + + # Separate SHA tags (40-char hex) from named tags (latest, etc.) + SHA_TAGS=$(echo "$TAGS" | grep -E '^[0-9a-f]{40}$' || true) + SHA_COUNT=$(echo "$SHA_TAGS" | grep -c . || true) + + # Keep the 10 most recent SHA tags; delete older ones + KEEP=10 + if [ "$SHA_COUNT" -gt "$KEEP" ]; then + TO_DELETE=$(echo "$SHA_TAGS" | head -n "-$KEEP") + echo " Deleting $(echo "$TO_DELETE" | grep -c .) old SHA tags..." + + for tag in $TO_DELETE; do + # Get the manifest digest + DIGEST=$(wget -qO- --server-response \ + --header="Accept: application/vnd.docker.distribution.manifest.v2+json" \ + "$REGISTRY_URL/v2/$repo/manifests/$tag" 2>&1 \ + | grep -i "docker-content-digest" | awk '{print $2}' | tr -d '\r') + + if [ -n "$DIGEST" ]; then + # Delete by digest + wget -q --method=DELETE \ + "$REGISTRY_URL/v2/$repo/manifests/$DIGEST" && \ + echo " Deleted $repo:$tag ($DIGEST)" || \ + echo " Failed to delete $repo:$tag" + fi + done + else + echo " Skipping (only $SHA_COUNT SHA tags, keep threshold is $KEEP)" + fi + done + + # Run the built-in GC to reclaim disk space from deleted layers + echo "Running storage garbage collection..." + /bin/registry garbage-collect /etc/docker/registry/config.yml --delete-untagged=false + echo "Done." + volumeMounts: + - name: data + mountPath: /var/lib/registry + - name: config + mountPath: /etc/docker/registry/config.yml + subPath: config.yml + volumes: + - name: data + persistentVolumeClaim: + claimName: registry-data + - name: config + configMap: + name: registry-gc-config +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: registry-gc-config + namespace: registry +data: + config.yml: | + version: 0.1 + storage: + filesystem: + rootdirectory: /var/lib/registry + delete: + enabled: true + http: + addr: :5000