type ImageGCManager interface { // Applies the garbage collection policy. Errors include being unable to free // enough space as per the garbage collection policy. GarbageCollect() error
// Start async garbage collection of images. Start()
GetImageList() ([]container.Image, error)
// Delete all unused images. DeleteUnusedImages() error }
funcNewImageGCManager(runtime container.Runtime, statsProvider StatsProvider, recorder record.EventRecorder, nodeRef *v1.ObjectReference, policy ImageGCPolicy, sandboxImage string)(ImageGCManager, error) { // Validate policy. if policy.HighThresholdPercent < 0 || policy.HighThresholdPercent > 100 { returnnil, fmt.Errorf("invalid HighThresholdPercent %d, must be in range [0-100]", policy.HighThresholdPercent) } if policy.LowThresholdPercent < 0 || policy.LowThresholdPercent > 100 { returnnil, fmt.Errorf("invalid LowThresholdPercent %d, must be in range [0-100]", policy.LowThresholdPercent) } if policy.LowThresholdPercent > policy.HighThresholdPercent { returnnil, fmt.Errorf("LowThresholdPercent %d can not be higher than HighThresholdPercent %d", policy.LowThresholdPercent, policy.HighThresholdPercent) } im := &realImageGCManager{ runtime: runtime, policy: policy, imageRecords: make(map[string]*imageRecord), statsProvider: statsProvider, recorder: recorder, nodeRef: nodeRef, initialized: false, sandboxImage: sandboxImage, }
// Make a set of images in use by containers. for _, pod := range pods { for _, container := range pod.Containers { klog.V(5).Infof("Pod %s/%s, container %s uses image %s(%s)", pod.Namespace, pod.Name, container.Name, container.Image, container.ImageID) imagesInUse.Insert(container.ImageID) } }
// Add new images and record those being used. now := time.Now() currentImages := sets.NewString() im.imageRecordsLock.Lock() defer im.imageRecordsLock.Unlock() for _, image := range images { klog.V(5).Infof("Adding image ID %s to currentImages", image.ID) currentImages.Insert(image.ID)
// New image, set it as detected now. if _, ok := im.imageRecords[image.ID]; !ok { klog.V(5).Infof("Image ID %s is new", image.ID) im.imageRecords[image.ID] = &imageRecord{ firstDetected: detectTime, } }
// Set last used time to now if the image is being used. if isImageUsed(image.ID, imagesInUse) { klog.V(5).Infof("Setting Image ID %s lastUsed to %v", image.ID, now) im.imageRecords[image.ID].lastUsed = now }
klog.V(5).Infof("Image ID %s has size %d", image.ID, image.Size) im.imageRecords[image.ID].size = image.Size }
// Remove old images from our records. for image := range im.imageRecords { if !currentImages.Has(image) { klog.V(5).Infof("Image ID %s is no longer present; removing from imageRecords", image) delete(im.imageRecords, image) } }
func(im *realImageGCManager)GarbageCollect()error { // Get disk usage on disk holding images. fsStats, err := im.statsProvider.ImageFsStats() if err != nil { return err }
var capacity, available int64 if fsStats.CapacityBytes != nil { capacity = int64(*fsStats.CapacityBytes) } if fsStats.AvailableBytes != nil { available = int64(*fsStats.AvailableBytes) }
if available > capacity { klog.Warningf("available %d is larger than capacity %d", available, capacity) available = capacity }
// If over the max threshold, free enough to place us at the lower threshold. usagePercent := 100 - int(available*100/capacity) if usagePercent >= im.policy.HighThresholdPercent { amountToFree := capacity*int64(100-im.policy.LowThresholdPercent)/100 - available klog.Infof("[imageGCManager]: Disk usage on image filesystem is at %d%% which is over the high threshold (%d%%). Trying to free %d bytes down to the low threshold (%d%%).", usagePercent, im.policy.HighThresholdPercent, amountToFree, im.policy.LowThresholdPercent) freed, err := im.freeSpace(amountToFree, time.Now()) if err != nil { return err }
if freed < amountToFree { err := fmt.Errorf("failed to garbage collect required amount of images. Wanted to free %d bytes, but freed %d bytes", amountToFree, freed) im.recorder.Eventf(im.nodeRef, v1.EventTypeWarning, events.FreeDiskSpaceFailed, err.Error()) return err } }
// Get all images in eviction order. images := make([]evictionInfo, 0, len(im.imageRecords)) for image, record := range im.imageRecords { if isImageUsed(image, imagesInUse) { klog.V(5).Infof("Image ID %s is being used", image) continue } images = append(images, evictionInfo{ id: image, imageRecord: *record, }) } sort.Sort(byLastUsedAndDetected(images))
// Delete unused images until we've freed up enough space. var deletionErrors []error spaceFreed := int64(0) for _, image := range images { klog.V(5).Infof("Evaluating image ID %s for possible garbage collection", image.id) // Images that are currently in used were given a newer lastUsed. if image.lastUsed.Equal(freeTime) || image.lastUsed.After(freeTime) { klog.V(5).Infof("Image ID %s has lastUsed=%v which is >= freeTime=%v, not eligible for garbage collection", image.id, image.lastUsed, freeTime) continue }
// Avoid garbage collect the image if the image is not old enough. // In such a case, the image may have just been pulled down, and will be used by a container right away.
if freeTime.Sub(image.firstDetected) < im.policy.MinAge { klog.V(5).Infof("Image ID %s has age %v which is less than the policy's minAge of %v, not eligible for garbage collection", image.id, freeTime.Sub(image.firstDetected), im.policy.MinAge) continue }
funcbuildSignalToNodeReclaimFuncs(imageGC ImageGC, containerGC ContainerGC, withImageFs bool)map[evictionapi.Signal]nodeReclaimFuncs { signalToReclaimFunc := map[evictionapi.Signal]nodeReclaimFuncs{} // usage of an imagefs is optional if withImageFs { // with an imagefs, nodefs pressure should just delete logs signalToReclaimFunc[evictionapi.SignalNodeFsAvailable] = nodeReclaimFuncs{} signalToReclaimFunc[evictionapi.SignalNodeFsInodesFree] = nodeReclaimFuncs{} // with an imagefs, imagefs pressure should delete unused images signalToReclaimFunc[evictionapi.SignalImageFsAvailable] = nodeReclaimFuncs{containerGC.DeleteAllUnusedContainers, imageGC.DeleteUnusedImages} signalToReclaimFunc[evictionapi.SignalImageFsInodesFree] = nodeReclaimFuncs{containerGC.DeleteAllUnusedContainers, imageGC.DeleteUnusedImages} } else { // without an imagefs, nodefs pressure should delete logs, and unused images // since imagefs and nodefs share a common device, they share common reclaim functions signalToReclaimFunc[evictionapi.SignalNodeFsAvailable] = nodeReclaimFuncs{containerGC.DeleteAllUnusedContainers, imageGC.DeleteUnusedImages} signalToReclaimFunc[evictionapi.SignalNodeFsInodesFree] = nodeReclaimFuncs{containerGC.DeleteAllUnusedContainers, imageGC.DeleteUnusedImages} signalToReclaimFunc[evictionapi.SignalImageFsAvailable] = nodeReclaimFuncs{containerGC.DeleteAllUnusedContainers, imageGC.DeleteUnusedImages} signalToReclaimFunc[evictionapi.SignalImageFsInodesFree] = nodeReclaimFuncs{containerGC.DeleteAllUnusedContainers, imageGC.DeleteUnusedImages} } return signalToReclaimFunc }