Skip to content

Commit ca2b7bb

Browse files
committed
image manager: cleanup 'dead' and 'created' containers
also cleanup of 'dangling' images that have no tags or names associated with them (ie, they show as <none> in 'docker images') closes aws#1684 unit tests
1 parent df83ed3 commit ca2b7bb

File tree

3 files changed

+259
-48
lines changed

3 files changed

+259
-48
lines changed

agent/config/config.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ func (cfg *Config) validateAndOverrideBounds() error {
306306
// If a value has been set for taskCleanupWaitDuration and the value is less than the minimum allowed cleanup duration,
307307
// print a warning and override it
308308
if cfg.TaskCleanupWaitDuration < minimumTaskCleanupWaitDuration {
309-
seelog.Warnf("Invalid value for image cleanup duration, will be overridden with the default value: %s. Parsed value: %v, minimum value: %v.", DefaultTaskCleanupWaitDuration.String(), cfg.TaskCleanupWaitDuration, minimumTaskCleanupWaitDuration)
309+
seelog.Warnf("Invalid value for ECS_ENGINE_TASK_CLEANUP_WAIT_DURATION, will be overridden with the default value: %s. Parsed value: %v, minimum value: %v.", DefaultTaskCleanupWaitDuration.String(), cfg.TaskCleanupWaitDuration, minimumTaskCleanupWaitDuration)
310310
cfg.TaskCleanupWaitDuration = DefaultTaskCleanupWaitDuration
311311
}
312312

@@ -316,7 +316,7 @@ func (cfg *Config) validateAndOverrideBounds() error {
316316
}
317317

318318
if cfg.ImageCleanupInterval < minimumImageCleanupInterval {
319-
seelog.Warnf("Invalid value for image cleanup duration, will be overridden with the default value: %s. Parsed value: %v, minimum value: %v.", DefaultImageCleanupTimeInterval.String(), cfg.ImageCleanupInterval, minimumImageCleanupInterval)
319+
seelog.Warnf("Invalid value for ECS_IMAGE_CLEANUP_INTERVAL, will be overridden with the default value: %s. Parsed value: %v, minimum value: %v.", DefaultImageCleanupTimeInterval.String(), cfg.ImageCleanupInterval, minimumImageCleanupInterval)
320320
cfg.ImageCleanupInterval = DefaultImageCleanupTimeInterval
321321
}
322322

agent/engine/docker_image_manager.go

+50-36
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ func (imageManager *dockerImageManager) removeUnusedImages(ctx context.Context)
312312

313313
var numECSImagesDeleted int
314314
imageManager.imageStatesConsideredForDeletion = imageManager.imagesConsiderForDeletion(imageManager.getAllImageStates())
315+
315316
for i := 0; i < imageManager.numImagesToDelete; i++ {
316317
err := imageManager.removeLeastRecentlyUsedImage(ctx)
317318
numECSImagesDeleted = i
@@ -342,9 +343,16 @@ func (imageManager *dockerImageManager) removeNonECSContainers(ctx context.Conte
342343
continue
343344
}
344345

345-
finishedTime, _ := time.Parse(time.Now().String(), response.State.FinishedAt)
346+
seelog.Debugf("Inspecting Non-ECS Container ID [%s] for removal, Finished [%s] Status [%s]", id, response.State.FinishedAt, response.State.Status)
347+
finishedTime, err := time.Parse(time.RFC3339Nano, response.State.FinishedAt)
348+
if err != nil {
349+
seelog.Errorf("Error parsing time string for container. id: %s, time: %s err: %s", id, response.State.FinishedAt, err)
350+
}
346351

347-
if response.State.Status == "exited" && time.Now().Sub(finishedTime) > imageManager.nonECSContainerCleanupWaitDuration {
352+
if (response.State.Status == "exited" ||
353+
response.State.Status == "dead" ||
354+
response.State.Status == "created") &&
355+
time.Now().Sub(finishedTime) > imageManager.nonECSContainerCleanupWaitDuration {
348356
nonECSContainerRemoveAvailableIDs = append(nonECSContainerRemoveAvailableIDs, id)
349357
}
350358
}
@@ -353,13 +361,13 @@ func (imageManager *dockerImageManager) removeNonECSContainers(ctx context.Conte
353361
if numNonECSContainerDeleted == imageManager.numNonECSContainersToDelete {
354362
break
355363
}
356-
seelog.Infof("Removing non-ECS container id: %s", id)
364+
seelog.Debugf("Removing non-ECS Container ID %s", id)
357365
err := imageManager.client.RemoveContainer(ctx, id, dockerclient.RemoveContainerTimeout)
358366
if err == nil {
359-
seelog.Infof("Image removed: %s", id)
367+
seelog.Infof("Removed Container ID: %s", id)
360368
numNonECSContainerDeleted++
361369
} else {
362-
seelog.Errorf("Error removing Image %s - %v", id, err)
370+
seelog.Errorf("Error Removing Container ID %s - %s", id, err)
363371
continue
364372
}
365373
}
@@ -379,70 +387,75 @@ func (imageManager *dockerImageManager) getNonECSContainerIDs(ctx context.Contex
379387
return nonECSContainersIDs, nil
380388
}
381389

382-
type ImageWithSize struct {
390+
type ImageWithSizeID struct {
383391
ImageName string
392+
ImageID string
384393
Size int64
385394
}
386395

387396
func (imageManager *dockerImageManager) removeNonECSImages(ctx context.Context, nonECSImagesNumToDelete int) {
388397
if nonECSImagesNumToDelete == 0 {
389398
return
390399
}
391-
var nonECSImageNames = imageManager.getNonECSImageNames(ctx)
392-
var nonECSImageNamesRemoveEligible []string
393-
for _, nonECSImage := range nonECSImageNames {
394-
if !isInExclusionList(nonECSImage, imageManager.imageCleanupExclusionList) {
395-
nonECSImageNamesRemoveEligible = append(nonECSImageNamesRemoveEligible, nonECSImage)
400+
nonECSImages := imageManager.getNonECSImages(ctx)
401+
var nonECSImagesEligible []ImageWithSizeID
402+
for _, nonECSImage := range nonECSImages {
403+
if !isInExclusionList(nonECSImage.ImageName, imageManager.imageCleanupExclusionList) {
404+
nonECSImagesEligible = append(nonECSImagesEligible, nonECSImage)
396405
}
397406
}
398407

399-
var imageWithSizeList []ImageWithSize
400-
for _, imageName := range nonECSImageNamesRemoveEligible {
401-
resp, iiErr := imageManager.client.InspectImage(imageName)
402-
if iiErr != nil {
403-
seelog.Errorf("Error inspecting non-ECS image name: %s - %v", imageName, iiErr)
408+
// Get the all image sizes
409+
for _, image := range nonECSImagesEligible {
410+
resp, err := imageManager.client.InspectImage(image.ImageID)
411+
if err != nil {
412+
seelog.Errorf("Error inspecting non-ECS image: %s (%s), %s", image.ImageName, image.ImageID, err)
404413
continue
405414
}
406-
imageWithSizeList = append(imageWithSizeList, ImageWithSize{imageName, resp.Size})
415+
image.Size = resp.Size
407416
}
408417
// we want to sort images with size ascending
409-
sort.Slice(imageWithSizeList, func(i, j int) bool {
410-
return imageWithSizeList[i].Size < imageWithSizeList[j].Size
418+
sort.Slice(nonECSImagesEligible, func(i, j int) bool {
419+
return nonECSImagesEligible[i].Size < nonECSImagesEligible[j].Size
411420
})
412421

413422
// we will remove the remaining nonECSImages in each performPeriodicImageCleanup call()
414423
var numImagesAlreadyDeleted = 0
415-
for _, kv := range imageWithSizeList {
424+
for _, kv := range nonECSImagesEligible {
416425
if numImagesAlreadyDeleted == nonECSImagesNumToDelete {
417426
break
418427
}
419-
seelog.Infof("Removing non-ECS Image: %s", kv.ImageName)
420-
err := imageManager.client.RemoveImage(ctx, kv.ImageName, dockerclient.RemoveImageTimeout)
428+
seelog.Infof("Removing non-ECS Image: %s (%s)", kv.ImageName, kv.ImageID)
429+
err := imageManager.client.RemoveImage(ctx, kv.ImageID, dockerclient.RemoveImageTimeout)
421430
if err != nil {
422-
seelog.Errorf("Error removing Image %s - %v", kv.ImageName, err)
431+
seelog.Errorf("Error removing Image %s (%s) - %v", kv.ImageName, kv.ImageID, err)
423432
continue
424433
} else {
425-
seelog.Infof("Image removed: %s", kv.ImageName)
434+
seelog.Infof("Image removed: %s (%s)", kv.ImageName, kv.ImageID)
426435
numImagesAlreadyDeleted++
427436
}
428437
}
429438
}
430439

431-
func (imageManager *dockerImageManager) getNonECSImageNames(ctx context.Context) []string {
432-
response := imageManager.client.ListImages(ctx, dockerclient.ListImagesTimeout)
433-
var allImagesNames []string
434-
for _, name := range response.RepoTags {
435-
allImagesNames = append(allImagesNames, name)
440+
// getNonECSImages returns type ImageWithSizeID but does NOT populate the Size field.
441+
func (imageManager *dockerImageManager) getNonECSImages(ctx context.Context) []ImageWithSizeID {
442+
r := imageManager.client.ListImages(ctx, dockerclient.ListImagesTimeout)
443+
var allImages []ImageWithSizeID
444+
for i := 0; i < len(r.RepoTags); i++ {
445+
allImages = append(allImages, ImageWithSizeID{ImageName: r.RepoTags[i], ImageID: r.ImageIDs[i]})
436446
}
437-
var ecsImageNames []string
447+
var ecsImageIDs []string
438448
for _, imageState := range imageManager.getAllImageStates() {
439-
for _, imageName := range imageState.Image.Names {
440-
ecsImageNames = append(ecsImageNames, imageName)
441-
}
449+
ecsImageIDs = append(ecsImageIDs, imageState.Image.ImageID)
442450
}
443451

444-
var nonECSImageNames = exclude(allImagesNames, ecsImageNames)
445-
return nonECSImageNames
452+
var nonECSImages []ImageWithSizeID
453+
for _, image := range allImages {
454+
if !isInExclusionList(image.ImageID, ecsImageIDs) {
455+
nonECSImages = append(nonECSImages, image)
456+
}
457+
}
458+
return nonECSImages
446459
}
447460

448461
func isInExclusionList(imageName string, imageExclusionList []string) bool {
@@ -477,8 +490,9 @@ func (imageManager *dockerImageManager) imagesConsiderForDeletion(allImageStates
477490
for _, imageState := range allImageStates {
478491
if imageManager.isExcludedFromCleanup(imageState) {
479492
//imageState that we want to keep
480-
seelog.Infof("Image excluded from deletion: [%s]", imageState.String())
493+
seelog.Debugf("Image excluded from deletion: [%s]", imageState.String())
481494
} else {
495+
seelog.Debugf("Image going to be considered for deletion: [%s]", imageState.String())
482496
imagesConsiderForDeletionMap[imageState.Image.ImageID] = imageState
483497
}
484498
}

0 commit comments

Comments
 (0)