diff --git a/apps/registry/retention-cronjob.yaml b/apps/registry/retention-cronjob.yaml index 4eb6693..97d47c1 100644 --- a/apps/registry/retention-cronjob.yaml +++ b/apps/registry/retention-cronjob.yaml @@ -13,11 +13,20 @@ spec: backoffLimit: 1 template: spec: + serviceAccountName: registry-gc-runner restartPolicy: Never containers: - name: prune - image: python:3.12-alpine - command: ["python", "/scripts/prune.py"] + image: python:3.12-slim + command: ["sh", "-c"] + args: + - | + # Install kubectl + apt-get update && apt-get install -y curl --no-install-recommends && rm -rf /var/lib/apt/lists/* + curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" + install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl + # Run the prune script + python3 /scripts/prune.py volumeMounts: - name: script mountPath: /scripts diff --git a/apps/registry/retention-script.yaml b/apps/registry/retention-script.yaml index fba33a7..f4362ee 100644 --- a/apps/registry/retention-script.yaml +++ b/apps/registry/retention-script.yaml @@ -104,3 +104,81 @@ data: print(f' delete failed {repo}:{t} err={e}') print(f'deleted_manifests={deleted}') + + # Trigger garbage collection to delete unreferenced blob layers + if deleted > 0: + print('\n=== Triggering Garbage Collection ===') + try: + # Scale down registry to run GC + import subprocess + subprocess.run(['kubectl', 'scale', 'deployment', 'docker-registry', '--replicas=0', '-n', 'registry'], check=True) + print('Scaled down docker-registry deployment') + + # Wait for deployment to be fully down + import time + time.sleep(5) + + # Run GC job + gc_job = { + 'apiVersion': 'batch/v1', + 'kind': 'Job', + 'metadata': {'name': 'registry-gc-once', 'namespace': 'registry'}, + 'spec': { + 'backoffLimit': 0, + 'template': { + 'spec': { + 'restartPolicy': 'Never', + 'containers': [{ + 'name': 'gc', + 'image': 'registry:3', + 'command': ['registry', 'garbage-collect', '--delete-untagged', '/etc/distribution/config.yml'], + 'volumeMounts': [ + {'name': 'storage', 'mountPath': '/var/lib/registry'}, + {'name': 'config', 'mountPath': '/etc/distribution'} + ] + }], + 'volumes': [ + {'name': 'storage', 'persistentVolumeClaim': {'claimName': 'registry-pvc'}}, + {'name': 'config', 'configMap': {'name': 'registry-config'}} + ] + } + } + } + } + + # Delete old GC job if exists + subprocess.run(['kubectl', 'delete', 'job', 'registry-gc-once', '-n', 'registry', '--ignore-not-found=true'], check=False) + time.sleep(2) + + # Create and wait for GC job + import tempfile + with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f: + json.dump(gc_job, f) + f.flush() + subprocess.run(['kubectl', 'apply', '-f', f.name], check=True) + + print('GC job created, waiting for completion...') + + # Wait up to 10 minutes for GC to complete + for i in range(120): + result = subprocess.run(['kubectl', 'get', 'job', 'registry-gc-once', '-n', 'registry', '-o', 'jsonpath={.status.succeeded}'], capture_output=True, text=True) + if result.stdout.strip() == '1': + print('Garbage collection completed successfully') + break + result = subprocess.run(['kubectl', 'get', 'job', 'registry-gc-once', '-n', 'registry', '-o', 'jsonpath={.status.failed}'], capture_output=True, text=True) + if result.stdout.strip() == '1': + print('GC job failed') + break + time.sleep(5) + + # Scale back up + subprocess.run(['kubectl', 'scale', 'deployment', 'docker-registry', '--replicas=1', '-n', 'registry'], check=True) + print('Scaled up docker-registry deployment') + + except Exception as e: + print(f'GC trigger failed: {e}') + # Ensure registry is scaled back up even if GC failed + try: + subprocess.run(['kubectl', 'scale', 'deployment', 'docker-registry', '--replicas=1', '-n', 'registry'], check=False) + except: + pass