Issue #410: Cleanup images before running an update, preserving the ones that will be used in the target state

We add an extra image/container cleanup before applying updates, allowing any unwanted images to be deleted.
When doing this, we take care not to delete images that will be used when the target state is applied.

This prevents the problem of stale images being stored while the update lock is set, potentially
leaving the device out of space.

Running the cleanup *before* applying the update ensures that only one target image is downloaded: if a stale one
had been downloaded previously, it will be deleted before starting the update for the new one. This can have a slight
impact on delta performance, since the delta is potentially done from an older (and more different) version of the app,
but can have a big impact on storage usage, as not doing this would duplicate the required free storage space when
the update lock is set.

Change-Type: patch
Signed-off-by: Pablo Carranza Velez <pablo@resin.io>
This commit is contained in:
Pablo Carranza Velez 2017-03-28 16:40:54 -03:00
parent d9b421a574
commit 5412e766da
2 changed files with 20 additions and 5 deletions

View File

@ -700,6 +700,13 @@ application.update = update = (force, scheduled = false) ->
utils.setConfig('name', local.name) if local.name != deviceName
.then ->
parseEnvAndFormatRemoteApps(local.apps, uuid, apiKey)
.tap (remoteApps) ->
# Before running the updates, try to clean up any images that aren't in use
# and will not be used in the target state
return if application.localMode
dockerUtils.cleanupContainersAndImages(_.map(remoteApps, 'imageId'))
.catch (err) ->
console.log('Cleanup failed: ', err, err.stack)
.then (remoteApps) ->
localApps = formatLocalApps(apps)
resourcesForUpdate = compareForUpdate(localApps, remoteApps)

View File

@ -142,7 +142,7 @@ do ->
supervisorTagPromise = normalizeRepoTag(config.supervisorImage)
exports.cleanupContainersAndImages = ->
exports.cleanupContainersAndImages = (extraImagesToIgnore = []) ->
Promise.using writeLockImages(), ->
Promise.join(
knex('image').select('repoTag')
@ -159,19 +159,25 @@ do ->
.map (image) ->
image.NormalizedRepoTags = Promise.map(image.RepoTags, normalizeRepoTag)
Promise.props(image)
(locallyCreatedTags, apps, dependentApps, supervisorTag, images) ->
Promise.map(extraImagesToIgnore, normalizeRepoTag)
(locallyCreatedTags, apps, dependentApps, supervisorTag, images, normalizedExtraImages) ->
imageTags = _.map(images, 'NormalizedRepoTags')
supervisorTags = _.filter imageTags, (tags) ->
_.includes(tags, supervisorTag)
appTags = _.filter imageTags, (tags) ->
_.some tags, (tag) ->
_.includes(apps, tag) or _.includes(dependentApps, tag)
extraTags = _.filter imageTags, (tags) ->
_.some tags, (tag) ->
_.includes(normalizedExtraImages, tag)
supervisorTags = _.flatten(supervisorTags)
appTags = _.flatten(appTags)
extraTags = _.flatten(extraTags)
locallyCreatedTags = _.flatten(locallyCreatedTags)
return { images, supervisorTags, appTags, locallyCreatedTags }
return { images, supervisorTags, appTags, locallyCreatedTags, extraTags }
)
.then ({ images, supervisorTags, appTags, locallyCreatedTags }) ->
.then ({ images, supervisorTags, appTags, locallyCreatedTags, extraTags }) ->
# Cleanup containers first, so that they don't block image removal.
docker.listContainersAsync(all: true)
.filter (containerInfo) ->
@ -182,6 +188,8 @@ do ->
return false
if _.includes(locallyCreatedTags, repoTag)
return false
if _.includes(extraTags, repoTag)
return false
if !_.includes(supervisorTags, repoTag)
return true
return containerHasExited(containerInfo.Id)
@ -193,7 +201,7 @@ do ->
.then ->
imagesToClean = _.reject images, (image) ->
_.some image.NormalizedRepoTags, (tag) ->
return _.includes(appTags, tag) or _.includes(supervisorTags, tag) or _.includes(locallyCreatedTags, tag)
return _.includes(appTags, tag) or _.includes(supervisorTags, tag) or _.includes(locallyCreatedTags, tag) or _.includes(extraTags, tag)
Promise.map imagesToClean, (image) ->
Promise.map image.RepoTags.concat(image.Id), (tag) ->
docker.getImage(tag).removeAsync(force: true)