2015-03-01 03:49:12 +00:00
/ *
2015-05-01 16:19:44 +00:00
Copyright 2015 The Kubernetes Authors All rights reserved .
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 .
* /
package kubelet
import (
2015-03-16 03:37:19 +00:00
"fmt"
2015-03-01 03:49:12 +00:00
"sort"
"sync"
"time"
2015-08-05 22:05:17 +00:00
docker "github.com/fsouza/go-dockerclient"
"github.com/golang/glog"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/api"
2015-09-03 21:40:58 +00:00
"k8s.io/kubernetes/pkg/client/record"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/kubelet/cadvisor"
"k8s.io/kubernetes/pkg/kubelet/dockertools"
"k8s.io/kubernetes/pkg/util"
2015-09-09 17:45:01 +00:00
"k8s.io/kubernetes/pkg/util/sets"
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.
2015-03-01 03:49:12 +00:00
type imageManager 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.
Start ( ) error
2015-03-01 03:49:12 +00:00
// TODO(vmarmol): Have this subsume pulls as well.
}
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
}
2015-03-01 03:49:12 +00:00
type realImageManager struct {
// Connection to the Docker daemon.
dockerClient dockertools . DockerInterface
// 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
// cAdvisor instance.
cadvisor cadvisor . Interface
2015-03-27 20:12:48 +00:00
// Recorder for Kubernetes events.
recorder record . EventRecorder
// Reference to this node.
nodeRef * api . ObjectReference
2015-03-01 03:49:12 +00:00
}
// Information about the images we track.
type imageRecord struct {
// Time when this image was first detected.
detected time . Time
// Time when we last saw this image being used.
lastUsed time . Time
// Size of the image in bytes.
size int64
}
2015-03-27 20:12:48 +00:00
func newImageManager ( dockerClient dockertools . DockerInterface , cadvisorInterface cadvisor . Interface , recorder record . EventRecorder , nodeRef * api . ObjectReference , policy ImageGCPolicy ) ( imageManager , 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 )
}
im := & realImageManager {
2015-03-01 03:49:12 +00:00
dockerClient : dockerClient ,
2015-03-16 03:37:19 +00:00
policy : policy ,
2015-03-01 03:49:12 +00:00
imageRecords : make ( map [ string ] * imageRecord ) ,
2015-03-16 03:37:19 +00:00
cadvisor : cadvisorInterface ,
2015-03-27 20:12:48 +00:00
recorder : recorder ,
nodeRef : nodeRef ,
2015-03-16 03:37:19 +00:00
}
return im , nil
2015-03-01 03:49:12 +00:00
}
2015-05-05 18:15:12 +00:00
func ( im * realImageManager ) Start ( ) error {
2015-03-01 03:49:12 +00:00
// Initial detection make detected time "unknown" in the past.
var zero time . Time
2015-04-20 03:26:07 +00:00
err := im . detectImages ( zero )
2015-03-01 03:49:12 +00:00
if err != nil {
return err
}
2015-08-24 01:59:15 +00:00
go util . Until ( func ( ) {
2015-04-20 03:26:07 +00:00
err := im . detectImages ( time . Now ( ) )
2015-03-01 03:49:12 +00:00
if err != nil {
glog . Warningf ( "[ImageManager] Failed to monitor images: %v" , err )
}
2015-08-24 01:59:15 +00:00
} , 5 * time . Minute , util . NeverStop )
2015-03-01 03:49:12 +00:00
return nil
}
2015-04-20 03:26:07 +00:00
func ( im * realImageManager ) detectImages ( detected time . Time ) error {
images , err := im . dockerClient . ListImages ( docker . ListImagesOptions { } )
2015-03-01 03:49:12 +00:00
if err != nil {
return err
}
2015-04-20 03:26:07 +00:00
containers , err := im . dockerClient . ListContainers ( docker . ListContainersOptions {
2015-03-01 03:49:12 +00:00
All : true ,
} )
if err != nil {
return err
}
// Make a set of images in use by containers.
2015-09-09 17:45:01 +00:00
imagesInUse := sets . NewString ( )
2015-03-01 03:49:12 +00:00
for _ , container := range containers {
imagesInUse . Insert ( container . Image )
}
// 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 {
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 {
im . imageRecords [ image . ID ] = & imageRecord {
2015-03-01 03:49:12 +00:00
detected : detected ,
}
}
// Set last used time to now if the image is being used.
if isImageUsed ( & image , imagesInUse ) {
2015-04-20 03:26:07 +00:00
im . imageRecords [ image . ID ] . lastUsed = now
2015-03-01 03:49:12 +00:00
}
2015-04-20 03:26:07 +00:00
im . imageRecords [ image . ID ] . size = image . VirtualSize
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 ) {
2015-04-20 03:26:07 +00:00
delete ( im . imageRecords , image )
2015-03-01 03:49:12 +00:00
}
}
return nil
}
2015-04-20 03:26:07 +00:00
func ( im * realImageManager ) GarbageCollect ( ) error {
2015-03-16 03:37:19 +00:00
// Get disk usage on disk holding images.
2015-04-20 03:26:07 +00:00
fsInfo , err := im . cadvisor . DockerImagesFsInfo ( )
2015-03-16 03:37:19 +00:00
if err != nil {
return err
}
usage := int64 ( fsInfo . Usage )
capacity := int64 ( fsInfo . Capacity )
2015-03-16 04:00:46 +00:00
// Check valid capacity.
if capacity == 0 {
2015-03-27 20:12:48 +00:00
err := fmt . Errorf ( "invalid capacity %d on device %q at mount point %q" , capacity , fsInfo . Device , fsInfo . Mountpoint )
2015-08-11 07:25:10 +00:00
im . recorder . Eventf ( im . nodeRef , "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.
2015-03-16 04:00:46 +00:00
usagePercent := int ( usage * 100 / capacity )
2015-04-20 03:26:07 +00:00
if usagePercent >= im . policy . HighThresholdPercent {
amountToFree := usage - ( int64 ( im . policy . LowThresholdPercent ) * capacity / 100 )
glog . Infof ( "[ImageManager]: Disk usage on %q (%s) is at %d%% which is over the high threshold (%d%%). Trying to free %d bytes" , fsInfo . Device , fsInfo . Mountpoint , usagePercent , im . policy . HighThresholdPercent , amountToFree )
freed , err := im . freeSpace ( amountToFree )
2015-03-16 03:37:19 +00:00
if err != nil {
return err
}
if freed < amountToFree {
2015-03-27 20:12:48 +00:00
err := fmt . Errorf ( "failed to garbage collect required amount of images. Wanted to free %d, but freed %d" , amountToFree , freed )
2015-08-11 07:25:10 +00:00
im . recorder . Eventf ( im . nodeRef , "FreeDiskSpaceFailed" , err . Error ( ) )
2015-03-27 20:12:48 +00:00
return err
2015-03-16 03:37:19 +00:00
}
}
return nil
}
// 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.
2015-04-20 03:26:07 +00:00
func ( im * realImageManager ) freeSpace ( bytesToFree int64 ) ( int64 , error ) {
2015-03-01 03:49:12 +00:00
startTime := time . Now ( )
2015-04-20 03:26:07 +00:00
err := im . detectImages ( startTime )
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 {
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.
var lastErr error
spaceFreed := int64 ( 0 )
for _ , image := range images {
// Images that are currently in used were given a newer lastUsed.
if image . lastUsed . After ( startTime ) {
break
}
// Remove image. Continue despite errors.
2015-03-16 04:00:46 +00:00
glog . Infof ( "[ImageManager]: Removing image %q to free %d bytes" , image . id , image . size )
2015-04-20 03:26:07 +00:00
err := im . dockerClient . RemoveImage ( image . id )
2015-03-01 03:49:12 +00:00
if err != nil {
lastErr = err
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
}
}
return spaceFreed , lastErr
}
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 ) {
return ev [ i ] . detected . Before ( ev [ j ] . detected )
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
}
}
2015-09-09 17:45:01 +00:00
func isImageUsed ( image * docker . APIImages , imagesInUse sets . String ) bool {
2015-03-01 03:49:12 +00:00
// Check the image ID and all the RepoTags.
if _ , ok := imagesInUse [ image . ID ] ; ok {
return true
}
for _ , tag := range image . RepoTags {
if _ , ok := imagesInUse [ tag ] ; ok {
return true
}
}
return false
}