2015-03-01 03:49:12 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2015 The Kubernetes Authors .
2015-03-01 03:49:12 +00:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2016-08-04 19:26:06 +00:00
package images
2015-03-01 03:49:12 +00:00
import (
2017-09-27 00:58:18 +00:00
goerrors "errors"
2015-03-16 03:37:19 +00:00
"fmt"
2016-07-13 17:58:58 +00:00
"math"
2015-03-01 03:49:12 +00:00
"sort"
"sync"
"time"
2015-08-05 22:05:17 +00:00
"github.com/golang/glog"
2017-09-27 00:58:18 +00:00
2017-06-22 18:24:23 +00:00
"k8s.io/api/core/v1"
2017-01-11 14:09:48 +00:00
"k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
2017-01-30 18:39:54 +00:00
"k8s.io/client-go/tools/record"
2017-09-27 00:58:18 +00:00
statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
2015-09-26 00:29:08 +00:00
"k8s.io/kubernetes/pkg/kubelet/container"
2016-07-13 00:32:24 +00:00
"k8s.io/kubernetes/pkg/kubelet/events"
2015-03-01 03:49:12 +00:00
)
2017-09-27 00:58:18 +00:00
// StatsProvider is an interface for fetching stats used during image garbage
// collection.
type StatsProvider interface {
// ImageFsStats returns the stats of the image filesystem.
ImageFsStats ( ) ( * statsapi . FsStats , error )
}
2015-03-01 03:49:12 +00:00
// Manages lifecycle of all images.
//
2015-03-16 03:37:19 +00:00
// Implementation is thread-safe.
2016-08-04 19:26:06 +00:00
type ImageGCManager interface {
2015-03-16 03:37:19 +00:00
// Applies the garbage collection policy. Errors include being unable to free
// enough space as per the garbage collection policy.
GarbageCollect ( ) error
2015-03-01 03:49:12 +00:00
2015-05-05 18:15:12 +00:00
// Start async garbage collection of images.
2016-12-21 06:58:00 +00:00
Start ( )
2015-05-05 18:15:12 +00:00
2018-01-11 11:15:11 +00:00
GetImageList ( ) ( [ ] container . Image , error )
2015-12-02 08:53:56 +00:00
2016-07-13 17:58:58 +00:00
// Delete all unused images and returns the number of bytes freed. The number of bytes freed is always returned.
DeleteUnusedImages ( ) ( int64 , error )
2015-03-01 03:49:12 +00:00
}
2015-03-16 03:37:19 +00:00
// A policy for garbage collecting images. Policy defines an allowed band in
// which garbage collection will be run.
type ImageGCPolicy struct {
// Any usage above this threshold will always trigger garbage collection.
// This is the highest usage we will allow.
HighThresholdPercent int
// Any usage below this threshold will never trigger garbage collection.
// This is the lowest threshold we will try to garbage collect to.
LowThresholdPercent int
2016-01-24 08:54:51 +00:00
2016-08-02 22:13:54 +00:00
// Minimum age at which an image can be garbage collected.
2016-01-24 08:54:51 +00:00
MinAge time . Duration
2015-03-16 03:37:19 +00:00
}
2016-08-04 19:26:06 +00:00
type realImageGCManager struct {
2015-09-26 00:29:08 +00:00
// Container runtime
runtime container . Runtime
2015-03-01 03:49:12 +00:00
// Records of images and their use.
2015-03-16 03:37:19 +00:00
imageRecords map [ string ] * imageRecord
2015-03-01 03:49:12 +00:00
imageRecordsLock sync . Mutex
2015-03-16 03:37:19 +00:00
// The image garbage collection policy in use.
policy ImageGCPolicy
2017-09-27 00:58:18 +00:00
// statsProvider provides stats used during image garbage collection.
statsProvider StatsProvider
2015-03-27 20:12:48 +00:00
// Recorder for Kubernetes events.
recorder record . EventRecorder
// Reference to this node.
2017-07-15 05:25:54 +00:00
nodeRef * v1 . ObjectReference
2015-09-26 00:36:43 +00:00
// Track initialization
initialized bool
2016-12-08 09:44:13 +00:00
// imageCache is the cache of latest image list.
imageCache imageCache
2018-01-12 18:00:59 +00:00
// sandbox image exempted from GC
sandboxImage string
2016-12-08 09:44:13 +00:00
}
// imageCache caches latest result of ListImages.
type imageCache struct {
// sync.RWMutex is the mutex protects the image cache.
sync . RWMutex
// images is the image cache.
2018-01-11 11:15:11 +00:00
images [ ] container . Image
2016-12-08 09:44:13 +00:00
}
// set updates image cache.
2018-01-11 11:15:11 +00:00
func ( i * imageCache ) set ( images [ ] container . Image ) {
2016-12-08 09:44:13 +00:00
i . Lock ( )
defer i . Unlock ( )
i . images = images
}
// get gets image list from image cache.
2018-01-11 11:15:11 +00:00
func ( i * imageCache ) get ( ) [ ] container . Image {
2016-12-08 09:44:13 +00:00
i . RLock ( )
defer i . RUnlock ( )
return i . images
2015-03-01 03:49:12 +00:00
}
// Information about the images we track.
type imageRecord struct {
// Time when this image was first detected.
2015-12-17 12:46:56 +00:00
firstDetected time . Time
2015-03-01 03:49:12 +00:00
// Time when we last saw this image being used.
lastUsed time . Time
// Size of the image in bytes.
size int64
}
2018-01-12 18:00:59 +00:00
func NewImageGCManager ( runtime container . Runtime , statsProvider StatsProvider , recorder record . EventRecorder , nodeRef * v1 . ObjectReference , policy ImageGCPolicy , sandboxImage string ) ( ImageGCManager , error ) {
2015-03-16 03:37:19 +00:00
// Validate policy.
if policy . HighThresholdPercent < 0 || policy . HighThresholdPercent > 100 {
return nil , fmt . Errorf ( "invalid HighThresholdPercent %d, must be in range [0-100]" , policy . HighThresholdPercent )
}
if policy . LowThresholdPercent < 0 || policy . LowThresholdPercent > 100 {
return nil , fmt . Errorf ( "invalid LowThresholdPercent %d, must be in range [0-100]" , policy . LowThresholdPercent )
}
LowThresholdPercent can not be higher than HighThresholdPercent
if LowThresholdPercent > HighThresholdPercent, amountToFree at image_manager.go:208 is negative and image GC will not free memory properly.
Justification:
1) LowThresholdPercent > HighThresholdPercent implies (LowThresholdPercent * capacity / 100) > (HighThresholdPercent * capacity / 100)
2) usage is at least (HighThresholdPercent * capacity / 100)
3) amountToFree = usage - (LowThresholdPercent * capacity / 100)
Combining 1), 2) and 3) implies amountToFree can be negative.
What happens if amountToFree is negative? in freeSpace method, "for _, image := range images " loops at least once
and if everything goes fine, "delete(im.imageRecords, image.id)" is executed.
When checking for condition "if spaceFreed >= bytesToFree", it is always true as bytesToFree is negative
and spaceFreed is positive. The loop is finished, so is image GC.
At the end, only the oldest image is deleted. In situations where there is a lot of dead containers,
each container corresponing to distinct image, number of unused images can get higher.
If two new images get pulled in every 5 minutes, image GC will not work properly and will not free enough space.
Secondly, it will take a lot of time to free all unused images (hours depending on a number of unused images).
This is an incorrect configuration. Image GC should report it and refuse to work.
2015-12-02 13:10:32 +00:00
if policy . LowThresholdPercent > policy . HighThresholdPercent {
return nil , fmt . Errorf ( "LowThresholdPercent %d can not be higher than HighThresholdPercent %d" , policy . LowThresholdPercent , policy . HighThresholdPercent )
}
2016-08-04 19:26:06 +00:00
im := & realImageGCManager {
2017-09-27 00:58:18 +00:00
runtime : runtime ,
policy : policy ,
imageRecords : make ( map [ string ] * imageRecord ) ,
statsProvider : statsProvider ,
recorder : recorder ,
nodeRef : nodeRef ,
initialized : false ,
2018-01-12 18:00:59 +00:00
sandboxImage : sandboxImage ,
2015-03-16 03:37:19 +00:00
}
return im , nil
2015-03-01 03:49:12 +00:00
}
2016-12-21 06:58:00 +00:00
func ( im * realImageGCManager ) Start ( ) {
2016-02-02 10:57:06 +00:00
go wait . Until ( func ( ) {
2015-09-26 00:36:43 +00:00
// Initial detection make detected time "unknown" in the past.
var ts time . Time
if im . initialized {
ts = time . Now ( )
}
2017-12-11 07:20:55 +00:00
_ , err := im . detectImages ( ts )
2015-03-01 03:49:12 +00:00
if err != nil {
2016-08-04 19:26:06 +00:00
glog . Warningf ( "[imageGCManager] Failed to monitor images: %v" , err )
2015-09-26 00:36:43 +00:00
} else {
im . initialized = true
2015-03-01 03:49:12 +00:00
}
2016-02-02 10:57:06 +00:00
} , 5 * time . Minute , wait . NeverStop )
2015-03-01 03:49:12 +00:00
2016-12-08 09:44:13 +00:00
// Start a goroutine periodically updates image cache.
// TODO(random-liu): Merge this with the previous loop.
go wait . Until ( func ( ) {
images , err := im . runtime . ListImages ( )
if err != nil {
glog . Warningf ( "[imageGCManager] Failed to update image list: %v" , err )
} else {
im . imageCache . set ( images )
}
} , 30 * time . Second , wait . NeverStop )
2015-03-01 03:49:12 +00:00
}
2015-12-02 08:53:56 +00:00
// Get a list of images on this node
2018-01-11 11:15:11 +00:00
func ( im * realImageGCManager ) GetImageList ( ) ( [ ] container . Image , error ) {
2016-12-08 09:44:13 +00:00
return im . imageCache . get ( ) , nil
2015-12-02 08:53:56 +00:00
}
2017-12-11 07:20:55 +00:00
func ( im * realImageGCManager ) detectImages ( detectTime time . Time ) ( sets . String , error ) {
imagesInUse := sets . NewString ( )
2018-01-12 18:00:59 +00:00
// Always consider the container runtime pod sandbox image in use
imageRef , err := im . runtime . GetImageRef ( container . ImageSpec { Image : im . sandboxImage } )
if err == nil && imageRef != "" {
imagesInUse . Insert ( imageRef )
}
2015-09-26 00:29:08 +00:00
images , err := im . runtime . ListImages ( )
2015-03-01 03:49:12 +00:00
if err != nil {
2017-12-11 07:20:55 +00:00
return imagesInUse , err
2015-03-01 03:49:12 +00:00
}
2015-09-26 00:29:08 +00:00
pods , err := im . runtime . GetPods ( true )
2015-03-01 03:49:12 +00:00
if err != nil {
2017-12-11 07:20:55 +00:00
return imagesInUse , err
2015-03-01 03:49:12 +00:00
}
// Make a set of images in use by containers.
2015-09-26 00:29:08 +00:00
for _ , pod := range pods {
for _ , container := range pod . Containers {
2016-07-21 00:06:18 +00:00
glog . 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 )
2015-09-26 00:29:08 +00:00
}
2015-03-01 03:49:12 +00:00
}
// Add new images and record those being used.
now := time . Now ( )
2015-09-09 17:45:01 +00:00
currentImages := sets . NewString ( )
2015-04-20 03:26:07 +00:00
im . imageRecordsLock . Lock ( )
defer im . imageRecordsLock . Unlock ( )
2015-03-01 03:49:12 +00:00
for _ , image := range images {
2016-05-03 17:22:39 +00:00
glog . V ( 5 ) . Infof ( "Adding image ID %s to currentImages" , image . ID )
2015-03-01 03:49:12 +00:00
currentImages . Insert ( image . ID )
// New image, set it as detected now.
2015-04-20 03:26:07 +00:00
if _ , ok := im . imageRecords [ image . ID ] ; ! ok {
2016-05-03 17:22:39 +00:00
glog . V ( 5 ) . Infof ( "Image ID %s is new" , image . ID )
2015-04-20 03:26:07 +00:00
im . imageRecords [ image . ID ] = & imageRecord {
2016-01-26 02:35:11 +00:00
firstDetected : detectTime ,
2015-03-01 03:49:12 +00:00
}
}
// Set last used time to now if the image is being used.
2017-12-11 07:20:55 +00:00
if isImageUsed ( image . ID , imagesInUse ) {
2016-05-03 17:22:39 +00:00
glog . V ( 5 ) . Infof ( "Setting Image ID %s lastUsed to %v" , image . ID , now )
2015-04-20 03:26:07 +00:00
im . imageRecords [ image . ID ] . lastUsed = now
2015-03-01 03:49:12 +00:00
}
2016-05-03 17:22:39 +00:00
glog . V ( 5 ) . Infof ( "Image ID %s has size %d" , image . ID , image . Size )
2015-09-26 00:29:08 +00:00
im . imageRecords [ image . ID ] . size = image . Size
2015-03-01 03:49:12 +00:00
}
// Remove old images from our records.
2015-04-20 03:26:07 +00:00
for image := range im . imageRecords {
2015-03-01 03:49:12 +00:00
if ! currentImages . Has ( image ) {
2016-05-03 17:22:39 +00:00
glog . V ( 5 ) . Infof ( "Image ID %s is no longer present; removing from imageRecords" , image )
2015-04-20 03:26:07 +00:00
delete ( im . imageRecords , image )
2015-03-01 03:49:12 +00:00
}
}
2017-12-11 07:20:55 +00:00
return imagesInUse , nil
2015-03-01 03:49:12 +00:00
}
2016-08-04 19:26:06 +00:00
func ( im * realImageGCManager ) GarbageCollect ( ) error {
2015-03-16 03:37:19 +00:00
// Get disk usage on disk holding images.
2017-09-27 00:58:18 +00:00
fsStats , err := im . statsProvider . ImageFsStats ( )
2015-03-16 03:37:19 +00:00
if err != nil {
return err
}
2017-09-27 00:58:18 +00:00
var capacity , available int64
if fsStats . CapacityBytes != nil {
capacity = int64 ( * fsStats . CapacityBytes )
}
if fsStats . AvailableBytes != nil {
available = int64 ( * fsStats . AvailableBytes )
}
2016-06-24 00:44:56 +00:00
if available > capacity {
glog . Warningf ( "available %d is larger than capacity %d" , available , capacity )
available = capacity
}
2015-03-16 04:00:46 +00:00
// Check valid capacity.
if capacity == 0 {
2017-09-27 00:58:18 +00:00
err := goerrors . New ( "invalid capacity 0 on image filesystem" )
2016-11-18 20:50:58 +00:00
im . recorder . Eventf ( im . nodeRef , v1 . EventTypeWarning , events . InvalidDiskCapacity , err . Error ( ) )
2015-03-27 20:12:48 +00:00
return err
2015-03-16 04:00:46 +00:00
}
2015-03-16 03:37:19 +00:00
// If over the max threshold, free enough to place us at the lower threshold.
2016-06-24 00:44:56 +00:00
usagePercent := 100 - int ( available * 100 / capacity )
2015-04-20 03:26:07 +00:00
if usagePercent >= im . policy . HighThresholdPercent {
2016-06-24 00:44:56 +00:00
amountToFree := capacity * int64 ( 100 - im . policy . LowThresholdPercent ) / 100 - available
2017-09-27 00:58:18 +00:00
glog . Infof ( "[imageGCManager]: Disk usage on image filesystem is at %d%% which is over the high threshold (%d%%). Trying to free %d bytes" , usagePercent , im . policy . HighThresholdPercent , amountToFree )
2016-01-26 02:35:11 +00:00
freed , err := im . freeSpace ( amountToFree , time . Now ( ) )
2015-03-16 03:37:19 +00:00
if err != nil {
return err
}
if freed < amountToFree {
2017-04-27 08:27:04 +00:00
err := fmt . Errorf ( "failed to garbage collect required amount of images. Wanted to free %d bytes, but freed %d bytes" , amountToFree , freed )
2016-11-18 20:50:58 +00:00
im . recorder . Eventf ( im . nodeRef , v1 . EventTypeWarning , events . FreeDiskSpaceFailed , err . Error ( ) )
2015-03-27 20:12:48 +00:00
return err
2015-03-16 03:37:19 +00:00
}
}
return nil
}
2016-08-04 19:26:06 +00:00
func ( im * realImageGCManager ) DeleteUnusedImages ( ) ( int64 , error ) {
2016-07-13 17:58:58 +00:00
return im . freeSpace ( math . MaxInt64 , time . Now ( ) )
}
2015-03-16 03:37:19 +00:00
// Tries to free bytesToFree worth of images on the disk.
//
2015-08-08 21:29:57 +00:00
// Returns the number of bytes free and an error if any occurred. The number of
2015-03-16 03:37:19 +00:00
// bytes freed is always returned.
// Note that error may be nil and the number of bytes free may be less
// than bytesToFree.
2016-08-04 19:26:06 +00:00
func ( im * realImageGCManager ) freeSpace ( bytesToFree int64 , freeTime time . Time ) ( int64 , error ) {
2017-12-11 07:20:55 +00:00
imagesInUse , err := im . detectImages ( freeTime )
2015-03-01 03:49:12 +00:00
if err != nil {
return 0 , err
}
2015-04-20 03:26:07 +00:00
im . imageRecordsLock . Lock ( )
defer im . imageRecordsLock . Unlock ( )
2015-03-01 03:49:12 +00:00
// Get all images in eviction order.
2015-04-20 03:26:07 +00:00
images := make ( [ ] evictionInfo , 0 , len ( im . imageRecords ) )
for image , record := range im . imageRecords {
2017-12-11 07:20:55 +00:00
if isImageUsed ( image , imagesInUse ) {
glog . V ( 5 ) . Infof ( "Image ID %s is being used" , image )
continue
}
2015-03-01 03:49:12 +00:00
images = append ( images , evictionInfo {
id : image ,
imageRecord : * record ,
} )
}
sort . Sort ( byLastUsedAndDetected ( images ) )
// Delete unused images until we've freed up enough space.
2016-06-15 23:54:14 +00:00
var deletionErrors [ ] error
2015-03-01 03:49:12 +00:00
spaceFreed := int64 ( 0 )
for _ , image := range images {
2016-05-03 17:22:39 +00:00
glog . V ( 5 ) . Infof ( "Evaluating image ID %s for possible garbage collection" , image . id )
2015-03-01 03:49:12 +00:00
// Images that are currently in used were given a newer lastUsed.
2016-05-03 11:11:05 +00:00
if image . lastUsed . Equal ( freeTime ) || image . lastUsed . After ( freeTime ) {
2016-05-03 17:22:39 +00:00
glog . V ( 5 ) . Infof ( "Image ID %s has lastUsed=%v which is >= freeTime=%v, not eligible for garbage collection" , image . id , image . lastUsed , freeTime )
2015-03-01 03:49:12 +00:00
break
}
2015-12-17 12:46:56 +00:00
// 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.
2016-01-24 08:54:51 +00:00
if freeTime . Sub ( image . firstDetected ) < im . policy . MinAge {
2016-05-03 17:22:39 +00:00
glog . 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 )
2015-12-17 12:46:56 +00:00
continue
}
2015-03-01 03:49:12 +00:00
// Remove image. Continue despite errors.
2016-08-04 19:26:06 +00:00
glog . Infof ( "[imageGCManager]: Removing image %q to free %d bytes" , image . id , image . size )
2015-09-26 00:29:08 +00:00
err := im . runtime . RemoveImage ( container . ImageSpec { Image : image . id } )
2015-03-01 03:49:12 +00:00
if err != nil {
2016-06-15 23:54:14 +00:00
deletionErrors = append ( deletionErrors , err )
2015-03-01 03:49:12 +00:00
continue
}
2015-04-20 03:26:07 +00:00
delete ( im . imageRecords , image . id )
2015-03-01 03:49:12 +00:00
spaceFreed += image . size
if spaceFreed >= bytesToFree {
break
}
}
2016-06-15 23:54:14 +00:00
if len ( deletionErrors ) > 0 {
2017-04-27 08:27:04 +00:00
return spaceFreed , fmt . Errorf ( "wanted to free %d bytes, but freed %d bytes space with errors in image deletion: %v" , bytesToFree , spaceFreed , errors . NewAggregate ( deletionErrors ) )
2016-06-15 23:54:14 +00:00
}
return spaceFreed , nil
2015-03-01 03:49:12 +00:00
}
type evictionInfo struct {
id string
imageRecord
}
type byLastUsedAndDetected [ ] evictionInfo
2015-04-20 03:26:07 +00:00
func ( ev byLastUsedAndDetected ) Len ( ) int { return len ( ev ) }
func ( ev byLastUsedAndDetected ) Swap ( i , j int ) { ev [ i ] , ev [ j ] = ev [ j ] , ev [ i ] }
func ( ev byLastUsedAndDetected ) Less ( i , j int ) bool {
2015-03-01 03:49:12 +00:00
// Sort by last used, break ties by detected.
2015-04-20 03:26:07 +00:00
if ev [ i ] . lastUsed . Equal ( ev [ j ] . lastUsed ) {
2015-12-17 12:46:56 +00:00
return ev [ i ] . firstDetected . Before ( ev [ j ] . firstDetected )
2015-03-01 03:49:12 +00:00
} else {
2015-04-20 03:26:07 +00:00
return ev [ i ] . lastUsed . Before ( ev [ j ] . lastUsed )
2015-03-01 03:49:12 +00:00
}
}
2017-12-11 07:20:55 +00:00
func isImageUsed ( imageID string , imagesInUse sets . String ) bool {
2016-07-21 00:06:18 +00:00
// Check the image ID.
2017-12-11 07:20:55 +00:00
if _ , ok := imagesInUse [ imageID ] ; ok {
2015-03-01 03:49:12 +00:00
return true
}
return false
}