docker: rework image pruning

with our new retagging approach, the Digest ID remains <null> because
this is only set by docker if truly fetched from the registry.

this means that redis container always gets removed...
This commit is contained in:
Girish Ramakrishnan
2024-12-14 20:47:35 +01:00
parent bd107e849b
commit 8e6890b4d6
5 changed files with 19 additions and 38 deletions
+2 -1
View File
@@ -586,7 +586,8 @@ async function deleteImage(imageRef) {
// registry v1 used to pull down all *tags*. this meant that deleting image by tag was not enough (since that
// just removes the tag). we used to remove the image by id. this is not required anymore because aliases are
// not created anymore after https://github.com/docker/docker/pull/10571
const [error] = await safe(gConnection.getImage(imageRef).remove(removeOptions));
debug(`deleteImage: removing ${imageRef}`);
const [error] = await safe(gConnection.getImage(imageRef.replace(/@sha256:.*/,'')).remove(removeOptions)); // can't have the manifest id. won't remove anythin
if (error && error.statusCode === 400) return; // invalid image format. this can happen if user installed with a bad --docker-image
if (error && error.statusCode === 404) return; // not found
if (error && error.statusCode === 409) return; // another container using the image
+2
View File
@@ -213,6 +213,8 @@ async function start(existingInfra) {
debug('startMail: starting');
await restart();
if (existingInfra.version !== 'none' && existingInfra.images.mail !== infra.images.mail) await docker.deleteImage(existingInfra.images.mail);
}
async function restartIfActivated() {
+1 -37
View File
@@ -22,7 +22,6 @@ const apps = require('./apps.js'),
dashboard = require('./dashboard.js'),
database = require('./database.js'),
debug = require('debug')('box:platform'),
docker = require('./docker.js'),
dockerProxy = require('./dockerproxy.js'),
fs = require('fs'),
infra = require('./infra_version.js'),
@@ -46,37 +45,6 @@ function getStatus() {
return { message: gStatusMessage };
}
async function pruneInfraImages() {
debug('pruneInfraImages: checking existing images');
// cannot blindly remove all unused images since redis image may not be used
const imageRefs = Object.keys(infra.images).map(addon => infra.images[addon]);
const [error, output] = await safe(shell.bash('docker images --digests --format "{{.ID}} {{.Repository}} {{.Tag}} {{.Digest}}"', { encoding: 'utf8' }));
if (error) {
debug(`Failed to list images ${error.message}`);
throw error;
}
const lines = output.trim().split('\n');
for (const imageRef of imageRefs) {
const parsedRef = docker.parseImageRef(imageRef);
for (const line of lines) {
if (!line) continue;
const [, repo, tag, digest] = line.split(' '); // [ ID, Repo, Tag, Digest ]
const normalizedRepo = repo.replace('registry.ipv6.docker.com/', '').replace('registry-1.docker.io/', '').replace('registry.docker.com/', '');
if (!parsedRef.fullRepositoryName.endsWith(normalizedRepo)) continue; // some other repo
if (imageRef === `${repo}:${tag}@${digest}`) continue; // the image we want to keep
const imageIdToPrune = tag === '<none>' ? `${repo}@${digest}` : `${repo}:${tag}`; // untagged, use digest
console.log(`pruneInfraImages: removing unused image of ${imageRef}: ${imageIdToPrune}`);
const [error] = await safe(shell.spawn('docker', [ 'rmi', imageIdToPrune ], {}));
if (error) console.log(`Error removing image ${imageIdToPrune}: ${error.mesage}`);
}
}
}
async function pruneVolumes() {
debug('pruneVolumes: remove all unused local volumes');
@@ -138,11 +106,7 @@ async function onInfraReady(infraChanged) {
debug(`onInfraReady: platform is ready. infra changed: ${infraChanged}`);
gStatusMessage = 'Ready';
if (infraChanged) {
await safe(pruneInfraImages(), { debug }); // ignore error
await safe(pruneVolumes(), { debug }); // ignore error
}
if (infraChanged) await safe(pruneVolumes(), { debug }); // ignore error
await apps.schedulePendingTasks(AuditSource.PLATFORM);
await appTaskManager.start();
}
+12
View File
@@ -1035,6 +1035,8 @@ async function startTurn(existingInfra) {
debug('startTurn: starting turn container');
await shell.bash(runCmd, {});
if (existingInfra.version !== 'none' && existingInfra.images.turn !== image) await docker.deleteImage(existingInfra.images.turn);
}
async function teardownTurn(app, options) {
@@ -1248,6 +1250,8 @@ async function startMysql(existingInfra) {
await waitForContainer('mysql', 'CLOUDRON_MYSQL_TOKEN');
if (upgrading) await importDatabase('mysql');
}
if (existingInfra.version !== 'none' && existingInfra.images.mysql !== image) await docker.deleteImage(existingInfra.images.mysql);
}
async function setupMySql(app, options) {
@@ -1466,6 +1470,8 @@ async function startPostgresql(existingInfra) {
await waitForContainer('postgresql', 'CLOUDRON_POSTGRESQL_TOKEN');
if (upgrading) await importDatabase('postgresql');
}
if (existingInfra.version !== 'none' && existingInfra.images.postgresql !== image) await docker.deleteImage(existingInfra.images.postgresql);
}
async function setupPostgreSql(app, options) {
@@ -1615,6 +1621,8 @@ async function startMongodb(existingInfra) {
await waitForContainer('mongodb', 'CLOUDRON_MONGODB_TOKEN');
if (upgrading) await importDatabase('mongodb');
}
if (existingInfra.version !== 'none' && existingInfra.images.mongodb !== image) await docker.deleteImage(existingInfra.images.mongodb);
}
async function setupMongoDb(app, options) {
@@ -1782,6 +1790,8 @@ async function startGraphite(existingInfra) {
debug('startGraphite: starting graphite container');
await shell.bash(runCmd, {});
if (existingInfra.version !== 'none' && existingInfra.images.graphite !== image) await docker.deleteImage(existingInfra.images.graphite);
// restart collectd to get the disk stats after graphite starts. currently, there is no way to do graphite health check
setTimeout(async () => await safe(shell.promises.sudo([ RESTART_SERVICE_CMD, 'collectd' ], {})), 60000);
}
@@ -1872,6 +1882,8 @@ async function startRedis(existingInfra) {
}
if (upgrading) await importDatabase('redis');
if (existingInfra.version !== 'none' && existingInfra.images.redis !== image) await docker.deleteImage(existingInfra.images.redis);
}
// Ensures that app's addon redis container is running. Can be called when named container already exists/running
+2
View File
@@ -125,6 +125,8 @@ async function start(existingInfra) {
debug('startSftp: starting sftp container');
await shell.bash(runCmd, {});
if (existingInfra.version !== 'none' && existingInfra.images.sftp !== image) await docker.deleteImage(existingInfra.images.sftp);
}
async function status() {