1import * as _ from 'lodash-es' 10import { spawnSync } from 'child_process' 12import { BatchV1Api } from '@kubernetes/client-node' 14type DockRegGcProps = { 19const getDiskUsage = (cluster_name: string, podName: string) => { 20 const result = spawnSync('kubectl', ['exec', podName, '--', 'sh', '-c', 'df -h /var/lib/registry | awk \'NR>1{for(i=1;i<=NF;i++) if($i ~ /%/) {pct=$i; used=$(i-2); total=$(i-3)}} END{print total, used, pct}\''], { 23 const parts = (result.stdout || '').trim().split(/\s+/) 24 return {used: parts[1] || '?', total: parts[0] || '?', pct: parts[2] || '?'} 27export const sweepStaleRegManifests = async ({apiHost, altHost, activeImageRefs, auth}: { 28 apiHost: string, altHost?: string, activeImageRefs: Set<string>, auth?: string 31 console.log(`${catalogs.length} repos in registry`) 33 let staleCount = 0, deletedCount = 0, failedCount = 0 35 for (const repo of catalogs) { 37 for (const tag of tags) { 38 const fullRef = `${apiHost}/${repo}:${tag}` 39 const altRef = `${altHost}/${repo}:${tag}` 40 const isActive = _.some([...activeImageRefs], ref => ref === fullRef || ref === altRef || ref.includes(`${repo}:${tag}`)) 41 if (isActive) continue 44 if (!digest) { failedCount++; continue } 50 console.log(` deleted ${repo}:${tag}`) 58 console.log(`\nstale manifests: ${staleCount}, deleted: ${deletedCount}, failed: ${failedCount}`) 59 return {staleCount, deletedCount, failedCount} 62const runRegistryGc = (cluster_name: string, podName: string) => { 63 console.log('running registry garbage-collect...') 64 const result = spawnSync('kubectl', ['exec', podName, '--', 'registry', 'garbage-collect', '/etc/docker/registry/config.yml'], { 67 return result.status === 0 70const deleteDiskFullKanikoJobs = async ({cluster_name}: {cluster_name: string}) => { 72 const batchApi = kubeConfig.makeApiClient(BatchV1Api) 73 const {items: jobs} = await batchApi.listNamespacedJob({namespace: 'default', labelSelector: `cluster_name=${cluster_name}`}) 74 const kanikoJobs = jobs.filter(job => job.metadata?.name?.startsWith('kaniko2-') && job.status?.failed) 75 if (!kanikoJobs.length) return 0 78 for (const job of kanikoJobs) { 79 const jobName = job.metadata?.name! 80 const podLogs = spawnSync('kubectl', ['logs', '-l', `job-name=${jobName}`, '--tail=50', '-c', 'kaniko'], { 81 encoding: 'utf-8', env: {...process.env, KUBECONFIG: getKubeConfigPath(cluster_name)}, timeout: 15_000, 83 const logs = podLogs.stdout || '' 86 await batchApi.deleteNamespacedJob({name: jobName, namespace: 'default', propagationPolicy: 'Background'}) 87 console.log(` deleted disk-full kaniko job ${jobName}`) 94export const dockRegGc = async ({dockreg_host, dockLanHost}: DockRegGcProps) => { 96 const regName = dockreg_host.split('.')[0] 100 const diskBefore = getDiskUsage(cluster_name, podName) 101 console.log(`disk before: ${diskBefore.used} / ${diskBefore.total} (${diskBefore.pct})`) 103 const activeImageRefs = new Set<string>() 105 for (const res of kubeResA) { 106 const containers = _.get(res, 'containers', []) 107 for (const c of containers) { 108 const image: string = c.image || '' 109 if (image.includes(regName)) activeImageRefs.add(image) 112 console.log(`${kubeResA.length} deployments using this registry, ${activeImageRefs.size} active image refs`) 117 const {deletedCount} = await sweepStaleRegManifests({apiHost, altHost: dockreg_host, activeImageRefs, auth}) 119 if (deletedCount > 0) { 120 const gcOk = runRegistryGc(cluster_name, podName) 121 if (!gcOk) console.log(chalkYellow('registry garbage-collect exited non-zero')) 123 console.log('no stale manifests to delete, skipping garbage-collect') 126 const deletedJobs = await deleteDiskFullKanikoJobs({cluster_name}) 127 if (deletedJobs) console.log(`cleaned up ${deletedJobs} disk-full kaniko job(s)`) 129 const diskAfter = getDiskUsage(cluster_name, podName) 130 console.log(`\ndisk after: ${diskAfter.used} / ${diskAfter.total} (${diskAfter.pct})`) 132 const freed = diskBefore.pct !== '?' && diskAfter.pct !== '?' 134 console.log(chalkGreen(`freed: ${diskBefore.pct} → ${diskAfter.pct} usage`)) 135 } else if (deletedCount === 0) { 136 console.log(chalkYellow('no space freed — all images are referenced by active deployments')) 137 console.log('consider: increase PVC size, redeploy apps to another registry, or manually delete stale deployments') 141dockRegGc.cliDescript = 'delete unreferenced manifests and run registry garbage-collect'