Skip to content

Commit 0da8124

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 #1684 unit tests
1 parent df83ed3 commit 0da8124

File tree

3 files changed

+256
-48
lines changed

3 files changed

+256
-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

+47-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,13 @@ func (imageManager *dockerImageManager) removeNonECSContainers(ctx context.Conte
342343
continue
343344
}
344345

345-
finishedTime, _ := time.Parse(time.Now().String(), response.State.FinishedAt)
346+
seelog.Infof("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" || response.State.Status == "dead" || response.State.Status == "created") && time.Now().Sub(finishedTime) > imageManager.nonECSContainerCleanupWaitDuration {
348353
nonECSContainerRemoveAvailableIDs = append(nonECSContainerRemoveAvailableIDs, id)
349354
}
350355
}
@@ -353,13 +358,13 @@ func (imageManager *dockerImageManager) removeNonECSContainers(ctx context.Conte
353358
if numNonECSContainerDeleted == imageManager.numNonECSContainersToDelete {
354359
break
355360
}
356-
seelog.Infof("Removing non-ECS container id: %s", id)
361+
seelog.Debugf("Removing non-ECS Container ID %s", id)
357362
err := imageManager.client.RemoveContainer(ctx, id, dockerclient.RemoveContainerTimeout)
358363
if err == nil {
359-
seelog.Infof("Image removed: %s", id)
364+
seelog.Infof("Container ID Removed: %s", id)
360365
numNonECSContainerDeleted++
361366
} else {
362-
seelog.Errorf("Error removing Image %s - %v", id, err)
367+
seelog.Errorf("Error Removing Container ID %s - %s", id, err)
363368
continue
364369
}
365370
}
@@ -379,70 +384,75 @@ func (imageManager *dockerImageManager) getNonECSContainerIDs(ctx context.Contex
379384
return nonECSContainersIDs, nil
380385
}
381386

382-
type ImageWithSize struct {
387+
type ImageWithSizeID struct {
383388
ImageName string
389+
ImageID string
384390
Size int64
385391
}
386392

387393
func (imageManager *dockerImageManager) removeNonECSImages(ctx context.Context, nonECSImagesNumToDelete int) {
388394
if nonECSImagesNumToDelete == 0 {
389395
return
390396
}
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)
397+
nonECSImages := imageManager.getNonECSImages(ctx)
398+
var nonECSImagesRmEligible []ImageWithSizeID
399+
for _, nonECSImage := range nonECSImages {
400+
if !isInExclusionList(nonECSImage.ImageName, imageManager.imageCleanupExclusionList) {
401+
nonECSImagesRmEligible = append(nonECSImagesRmEligible, nonECSImage)
396402
}
397403
}
398404

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)
405+
// Get the all image sizes
406+
for _, image := range nonECSImagesRmEligible {
407+
resp, err := imageManager.client.InspectImage(image.ImageID)
408+
if err != nil {
409+
seelog.Errorf("Error inspecting non-ECS image: %s (%s), %s", image.ImageName, image.ImageID, err)
404410
continue
405411
}
406-
imageWithSizeList = append(imageWithSizeList, ImageWithSize{imageName, resp.Size})
412+
image.Size = resp.Size
407413
}
408414
// 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
415+
sort.Slice(nonECSImagesRmEligible, func(i, j int) bool {
416+
return nonECSImagesRmEligible[i].Size < nonECSImagesRmEligible[j].Size
411417
})
412418

413419
// we will remove the remaining nonECSImages in each performPeriodicImageCleanup call()
414420
var numImagesAlreadyDeleted = 0
415-
for _, kv := range imageWithSizeList {
421+
for _, kv := range nonECSImagesRmEligible {
416422
if numImagesAlreadyDeleted == nonECSImagesNumToDelete {
417423
break
418424
}
419-
seelog.Infof("Removing non-ECS Image: %s", kv.ImageName)
420-
err := imageManager.client.RemoveImage(ctx, kv.ImageName, dockerclient.RemoveImageTimeout)
425+
seelog.Infof("Removing non-ECS Image: %s (%s)", kv.ImageName, kv.ImageID)
426+
err := imageManager.client.RemoveImage(ctx, kv.ImageID, dockerclient.RemoveImageTimeout)
421427
if err != nil {
422-
seelog.Errorf("Error removing Image %s - %v", kv.ImageName, err)
428+
seelog.Errorf("Error removing Image %s (%s) - %v", kv.ImageName, kv.ImageID, err)
423429
continue
424430
} else {
425-
seelog.Infof("Image removed: %s", kv.ImageName)
431+
seelog.Infof("Image removed: %s (%s)", kv.ImageName, kv.ImageID)
426432
numImagesAlreadyDeleted++
427433
}
428434
}
429435
}
430436

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)
437+
// getNonECSImages returns type ImageWithSizeID but does NOT populate the Size field.
438+
func (imageManager *dockerImageManager) getNonECSImages(ctx context.Context) []ImageWithSizeID {
439+
r := imageManager.client.ListImages(ctx, dockerclient.ListImagesTimeout)
440+
var allImages []ImageWithSizeID
441+
for i := 0; i < len(r.RepoTags); i++ {
442+
allImages = append(allImages, ImageWithSizeID{ImageName: r.RepoTags[i], ImageID: r.ImageIDs[i]})
436443
}
437-
var ecsImageNames []string
444+
var ecsImageIDs []string
438445
for _, imageState := range imageManager.getAllImageStates() {
439-
for _, imageName := range imageState.Image.Names {
440-
ecsImageNames = append(ecsImageNames, imageName)
441-
}
446+
ecsImageIDs = append(ecsImageIDs, imageState.Image.ImageID)
442447
}
443448

444-
var nonECSImageNames = exclude(allImagesNames, ecsImageNames)
445-
return nonECSImageNames
449+
var nonECSImages []ImageWithSizeID
450+
for _, image := range allImages {
451+
if !isInExclusionList(image.ImageID, ecsImageIDs) {
452+
nonECSImages = append(nonECSImages, image)
453+
}
454+
}
455+
return nonECSImages
446456
}
447457

448458
func isInExclusionList(imageName string, imageExclusionList []string) bool {
@@ -477,8 +487,9 @@ func (imageManager *dockerImageManager) imagesConsiderForDeletion(allImageStates
477487
for _, imageState := range allImageStates {
478488
if imageManager.isExcludedFromCleanup(imageState) {
479489
//imageState that we want to keep
480-
seelog.Infof("Image excluded from deletion: [%s]", imageState.String())
490+
seelog.Debugf("Image excluded from deletion: [%s]", imageState.String())
481491
} else {
492+
seelog.Debugf("Image going to be considered for deletion: [%s]", imageState.String())
482493
imagesConsiderForDeletionMap[imageState.Image.ImageID] = imageState
483494
}
484495
}

0 commit comments

Comments
 (0)