image_gc_manager.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. /*
  2. Copyright 2015 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package images
  14. import (
  15. goerrors "errors"
  16. "fmt"
  17. "math"
  18. "sort"
  19. "sync"
  20. "time"
  21. "k8s.io/klog"
  22. "k8s.io/api/core/v1"
  23. "k8s.io/apimachinery/pkg/util/errors"
  24. "k8s.io/apimachinery/pkg/util/sets"
  25. "k8s.io/apimachinery/pkg/util/wait"
  26. "k8s.io/client-go/tools/record"
  27. statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
  28. "k8s.io/kubernetes/pkg/kubelet/container"
  29. "k8s.io/kubernetes/pkg/kubelet/events"
  30. "k8s.io/kubernetes/pkg/kubelet/util/sliceutils"
  31. )
  32. // StatsProvider is an interface for fetching stats used during image garbage
  33. // collection.
  34. type StatsProvider interface {
  35. // ImageFsStats returns the stats of the image filesystem.
  36. ImageFsStats() (*statsapi.FsStats, error)
  37. }
  38. // ImageGCManager is an interface for managing lifecycle of all images.
  39. // Implementation is thread-safe.
  40. type ImageGCManager interface {
  41. // Applies the garbage collection policy. Errors include being unable to free
  42. // enough space as per the garbage collection policy.
  43. GarbageCollect() error
  44. // Start async garbage collection of images.
  45. Start()
  46. GetImageList() ([]container.Image, error)
  47. // Delete all unused images.
  48. DeleteUnusedImages() error
  49. }
  50. // ImageGCPolicy is a policy for garbage collecting images. Policy defines an allowed band in
  51. // which garbage collection will be run.
  52. type ImageGCPolicy struct {
  53. // Any usage above this threshold will always trigger garbage collection.
  54. // This is the highest usage we will allow.
  55. HighThresholdPercent int
  56. // Any usage below this threshold will never trigger garbage collection.
  57. // This is the lowest threshold we will try to garbage collect to.
  58. LowThresholdPercent int
  59. // Minimum age at which an image can be garbage collected.
  60. MinAge time.Duration
  61. }
  62. type realImageGCManager struct {
  63. // Container runtime
  64. runtime container.Runtime
  65. // Records of images and their use.
  66. imageRecords map[string]*imageRecord
  67. imageRecordsLock sync.Mutex
  68. // The image garbage collection policy in use.
  69. policy ImageGCPolicy
  70. // statsProvider provides stats used during image garbage collection.
  71. statsProvider StatsProvider
  72. // Recorder for Kubernetes events.
  73. recorder record.EventRecorder
  74. // Reference to this node.
  75. nodeRef *v1.ObjectReference
  76. // Track initialization
  77. initialized bool
  78. // imageCache is the cache of latest image list.
  79. imageCache imageCache
  80. // sandbox image exempted from GC
  81. sandboxImage string
  82. }
  83. // imageCache caches latest result of ListImages.
  84. type imageCache struct {
  85. // sync.Mutex is the mutex protects the image cache.
  86. sync.Mutex
  87. // images is the image cache.
  88. images []container.Image
  89. }
  90. // set updates image cache.
  91. func (i *imageCache) set(images []container.Image) {
  92. i.Lock()
  93. defer i.Unlock()
  94. i.images = images
  95. }
  96. // get gets a sorted (by image size) image list from image cache.
  97. // There is a potentical data race in this function. See PR #60448
  98. // Because there is deepcopy function available currently, move sort
  99. // function inside this function
  100. func (i *imageCache) get() []container.Image {
  101. i.Lock()
  102. defer i.Unlock()
  103. sort.Sort(sliceutils.ByImageSize(i.images))
  104. return i.images
  105. }
  106. // Information about the images we track.
  107. type imageRecord struct {
  108. // Time when this image was first detected.
  109. firstDetected time.Time
  110. // Time when we last saw this image being used.
  111. lastUsed time.Time
  112. // Size of the image in bytes.
  113. size int64
  114. }
  115. // NewImageGCManager instantiates a new ImageGCManager object.
  116. func NewImageGCManager(runtime container.Runtime, statsProvider StatsProvider, recorder record.EventRecorder, nodeRef *v1.ObjectReference, policy ImageGCPolicy, sandboxImage string) (ImageGCManager, error) {
  117. // Validate policy.
  118. if policy.HighThresholdPercent < 0 || policy.HighThresholdPercent > 100 {
  119. return nil, fmt.Errorf("invalid HighThresholdPercent %d, must be in range [0-100]", policy.HighThresholdPercent)
  120. }
  121. if policy.LowThresholdPercent < 0 || policy.LowThresholdPercent > 100 {
  122. return nil, fmt.Errorf("invalid LowThresholdPercent %d, must be in range [0-100]", policy.LowThresholdPercent)
  123. }
  124. if policy.LowThresholdPercent > policy.HighThresholdPercent {
  125. return nil, fmt.Errorf("LowThresholdPercent %d can not be higher than HighThresholdPercent %d", policy.LowThresholdPercent, policy.HighThresholdPercent)
  126. }
  127. im := &realImageGCManager{
  128. runtime: runtime,
  129. policy: policy,
  130. imageRecords: make(map[string]*imageRecord),
  131. statsProvider: statsProvider,
  132. recorder: recorder,
  133. nodeRef: nodeRef,
  134. initialized: false,
  135. sandboxImage: sandboxImage,
  136. }
  137. return im, nil
  138. }
  139. func (im *realImageGCManager) Start() {
  140. go wait.Until(func() {
  141. // Initial detection make detected time "unknown" in the past.
  142. var ts time.Time
  143. if im.initialized {
  144. ts = time.Now()
  145. }
  146. _, err := im.detectImages(ts)
  147. if err != nil {
  148. klog.Warningf("[imageGCManager] Failed to monitor images: %v", err)
  149. } else {
  150. im.initialized = true
  151. }
  152. }, 5*time.Minute, wait.NeverStop)
  153. // Start a goroutine periodically updates image cache.
  154. // TODO(random-liu): Merge this with the previous loop.
  155. go wait.Until(func() {
  156. images, err := im.runtime.ListImages()
  157. if err != nil {
  158. klog.Warningf("[imageGCManager] Failed to update image list: %v", err)
  159. } else {
  160. im.imageCache.set(images)
  161. }
  162. }, 30*time.Second, wait.NeverStop)
  163. }
  164. // Get a list of images on this node
  165. func (im *realImageGCManager) GetImageList() ([]container.Image, error) {
  166. return im.imageCache.get(), nil
  167. }
  168. func (im *realImageGCManager) detectImages(detectTime time.Time) (sets.String, error) {
  169. imagesInUse := sets.NewString()
  170. // Always consider the container runtime pod sandbox image in use
  171. imageRef, err := im.runtime.GetImageRef(container.ImageSpec{Image: im.sandboxImage})
  172. if err == nil && imageRef != "" {
  173. imagesInUse.Insert(imageRef)
  174. }
  175. images, err := im.runtime.ListImages()
  176. if err != nil {
  177. return imagesInUse, err
  178. }
  179. pods, err := im.runtime.GetPods(true)
  180. if err != nil {
  181. return imagesInUse, err
  182. }
  183. // Make a set of images in use by containers.
  184. for _, pod := range pods {
  185. for _, container := range pod.Containers {
  186. klog.V(5).Infof("Pod %s/%s, container %s uses image %s(%s)", pod.Namespace, pod.Name, container.Name, container.Image, container.ImageID)
  187. imagesInUse.Insert(container.ImageID)
  188. }
  189. }
  190. // Add new images and record those being used.
  191. now := time.Now()
  192. currentImages := sets.NewString()
  193. im.imageRecordsLock.Lock()
  194. defer im.imageRecordsLock.Unlock()
  195. for _, image := range images {
  196. klog.V(5).Infof("Adding image ID %s to currentImages", image.ID)
  197. currentImages.Insert(image.ID)
  198. // New image, set it as detected now.
  199. if _, ok := im.imageRecords[image.ID]; !ok {
  200. klog.V(5).Infof("Image ID %s is new", image.ID)
  201. im.imageRecords[image.ID] = &imageRecord{
  202. firstDetected: detectTime,
  203. }
  204. }
  205. // Set last used time to now if the image is being used.
  206. if isImageUsed(image.ID, imagesInUse) {
  207. klog.V(5).Infof("Setting Image ID %s lastUsed to %v", image.ID, now)
  208. im.imageRecords[image.ID].lastUsed = now
  209. }
  210. klog.V(5).Infof("Image ID %s has size %d", image.ID, image.Size)
  211. im.imageRecords[image.ID].size = image.Size
  212. }
  213. // Remove old images from our records.
  214. for image := range im.imageRecords {
  215. if !currentImages.Has(image) {
  216. klog.V(5).Infof("Image ID %s is no longer present; removing from imageRecords", image)
  217. delete(im.imageRecords, image)
  218. }
  219. }
  220. return imagesInUse, nil
  221. }
  222. func (im *realImageGCManager) GarbageCollect() error {
  223. // Get disk usage on disk holding images.
  224. fsStats, err := im.statsProvider.ImageFsStats()
  225. if err != nil {
  226. return err
  227. }
  228. var capacity, available int64
  229. if fsStats.CapacityBytes != nil {
  230. capacity = int64(*fsStats.CapacityBytes)
  231. }
  232. if fsStats.AvailableBytes != nil {
  233. available = int64(*fsStats.AvailableBytes)
  234. }
  235. if available > capacity {
  236. klog.Warningf("available %d is larger than capacity %d", available, capacity)
  237. available = capacity
  238. }
  239. // Check valid capacity.
  240. if capacity == 0 {
  241. err := goerrors.New("invalid capacity 0 on image filesystem")
  242. im.recorder.Eventf(im.nodeRef, v1.EventTypeWarning, events.InvalidDiskCapacity, err.Error())
  243. return err
  244. }
  245. // If over the max threshold, free enough to place us at the lower threshold.
  246. usagePercent := 100 - int(available*100/capacity)
  247. if usagePercent >= im.policy.HighThresholdPercent {
  248. amountToFree := capacity*int64(100-im.policy.LowThresholdPercent)/100 - available
  249. 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)
  250. freed, err := im.freeSpace(amountToFree, time.Now())
  251. if err != nil {
  252. return err
  253. }
  254. if freed < amountToFree {
  255. err := fmt.Errorf("failed to garbage collect required amount of images. Wanted to free %d bytes, but freed %d bytes", amountToFree, freed)
  256. im.recorder.Eventf(im.nodeRef, v1.EventTypeWarning, events.FreeDiskSpaceFailed, err.Error())
  257. return err
  258. }
  259. }
  260. return nil
  261. }
  262. func (im *realImageGCManager) DeleteUnusedImages() error {
  263. klog.Infof("attempting to delete unused images")
  264. _, err := im.freeSpace(math.MaxInt64, time.Now())
  265. return err
  266. }
  267. // Tries to free bytesToFree worth of images on the disk.
  268. //
  269. // Returns the number of bytes free and an error if any occurred. The number of
  270. // bytes freed is always returned.
  271. // Note that error may be nil and the number of bytes free may be less
  272. // than bytesToFree.
  273. func (im *realImageGCManager) freeSpace(bytesToFree int64, freeTime time.Time) (int64, error) {
  274. imagesInUse, err := im.detectImages(freeTime)
  275. if err != nil {
  276. return 0, err
  277. }
  278. im.imageRecordsLock.Lock()
  279. defer im.imageRecordsLock.Unlock()
  280. // Get all images in eviction order.
  281. images := make([]evictionInfo, 0, len(im.imageRecords))
  282. for image, record := range im.imageRecords {
  283. if isImageUsed(image, imagesInUse) {
  284. klog.V(5).Infof("Image ID %s is being used", image)
  285. continue
  286. }
  287. images = append(images, evictionInfo{
  288. id: image,
  289. imageRecord: *record,
  290. })
  291. }
  292. sort.Sort(byLastUsedAndDetected(images))
  293. // Delete unused images until we've freed up enough space.
  294. var deletionErrors []error
  295. spaceFreed := int64(0)
  296. for _, image := range images {
  297. klog.V(5).Infof("Evaluating image ID %s for possible garbage collection", image.id)
  298. // Images that are currently in used were given a newer lastUsed.
  299. if image.lastUsed.Equal(freeTime) || image.lastUsed.After(freeTime) {
  300. klog.V(5).Infof("Image ID %s has lastUsed=%v which is >= freeTime=%v, not eligible for garbage collection", image.id, image.lastUsed, freeTime)
  301. continue
  302. }
  303. // Avoid garbage collect the image if the image is not old enough.
  304. // In such a case, the image may have just been pulled down, and will be used by a container right away.
  305. if freeTime.Sub(image.firstDetected) < im.policy.MinAge {
  306. 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)
  307. continue
  308. }
  309. // Remove image. Continue despite errors.
  310. klog.Infof("[imageGCManager]: Removing image %q to free %d bytes", image.id, image.size)
  311. err := im.runtime.RemoveImage(container.ImageSpec{Image: image.id})
  312. if err != nil {
  313. deletionErrors = append(deletionErrors, err)
  314. continue
  315. }
  316. delete(im.imageRecords, image.id)
  317. spaceFreed += image.size
  318. if spaceFreed >= bytesToFree {
  319. break
  320. }
  321. }
  322. if len(deletionErrors) > 0 {
  323. 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))
  324. }
  325. return spaceFreed, nil
  326. }
  327. type evictionInfo struct {
  328. id string
  329. imageRecord
  330. }
  331. type byLastUsedAndDetected []evictionInfo
  332. func (ev byLastUsedAndDetected) Len() int { return len(ev) }
  333. func (ev byLastUsedAndDetected) Swap(i, j int) { ev[i], ev[j] = ev[j], ev[i] }
  334. func (ev byLastUsedAndDetected) Less(i, j int) bool {
  335. // Sort by last used, break ties by detected.
  336. if ev[i].lastUsed.Equal(ev[j].lastUsed) {
  337. return ev[i].firstDetected.Before(ev[j].firstDetected)
  338. }
  339. return ev[i].lastUsed.Before(ev[j].lastUsed)
  340. }
  341. func isImageUsed(imageID string, imagesInUse sets.String) bool {
  342. // Check the image ID.
  343. if _, ok := imagesInUse[imageID]; ok {
  344. return true
  345. }
  346. return false
  347. }