image_gc_manager.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  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. "fmt"
  16. "math"
  17. "sort"
  18. "sync"
  19. "time"
  20. "github.com/golang/glog"
  21. "k8s.io/kubernetes/pkg/api"
  22. "k8s.io/kubernetes/pkg/client/record"
  23. "k8s.io/kubernetes/pkg/kubelet/cadvisor"
  24. "k8s.io/kubernetes/pkg/kubelet/container"
  25. kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
  26. "k8s.io/kubernetes/pkg/kubelet/events"
  27. "k8s.io/kubernetes/pkg/util/errors"
  28. "k8s.io/kubernetes/pkg/util/sets"
  29. "k8s.io/kubernetes/pkg/util/wait"
  30. )
  31. // Manages lifecycle of all images.
  32. //
  33. // Implementation is thread-safe.
  34. type ImageGCManager interface {
  35. // Applies the garbage collection policy. Errors include being unable to free
  36. // enough space as per the garbage collection policy.
  37. GarbageCollect() error
  38. // Start async garbage collection of images.
  39. Start() error
  40. GetImageList() ([]kubecontainer.Image, error)
  41. // Delete all unused images and returns the number of bytes freed. The number of bytes freed is always returned.
  42. DeleteUnusedImages() (int64, error)
  43. }
  44. // A policy for garbage collecting images. Policy defines an allowed band in
  45. // which garbage collection will be run.
  46. type ImageGCPolicy struct {
  47. // Any usage above this threshold will always trigger garbage collection.
  48. // This is the highest usage we will allow.
  49. HighThresholdPercent int
  50. // Any usage below this threshold will never trigger garbage collection.
  51. // This is the lowest threshold we will try to garbage collect to.
  52. LowThresholdPercent int
  53. // Minimum age at which an image can be garbage collected.
  54. MinAge time.Duration
  55. }
  56. type realImageGCManager struct {
  57. // Container runtime
  58. runtime container.Runtime
  59. // Records of images and their use.
  60. imageRecords map[string]*imageRecord
  61. imageRecordsLock sync.Mutex
  62. // The image garbage collection policy in use.
  63. policy ImageGCPolicy
  64. // cAdvisor instance.
  65. cadvisor cadvisor.Interface
  66. // Recorder for Kubernetes events.
  67. recorder record.EventRecorder
  68. // Reference to this node.
  69. nodeRef *api.ObjectReference
  70. // Track initialization
  71. initialized bool
  72. }
  73. // Information about the images we track.
  74. type imageRecord struct {
  75. // Time when this image was first detected.
  76. firstDetected time.Time
  77. // Time when we last saw this image being used.
  78. lastUsed time.Time
  79. // Size of the image in bytes.
  80. size int64
  81. }
  82. func NewImageGCManager(runtime container.Runtime, cadvisorInterface cadvisor.Interface, recorder record.EventRecorder, nodeRef *api.ObjectReference, policy ImageGCPolicy) (ImageGCManager, error) {
  83. // Validate policy.
  84. if policy.HighThresholdPercent < 0 || policy.HighThresholdPercent > 100 {
  85. return nil, fmt.Errorf("invalid HighThresholdPercent %d, must be in range [0-100]", policy.HighThresholdPercent)
  86. }
  87. if policy.LowThresholdPercent < 0 || policy.LowThresholdPercent > 100 {
  88. return nil, fmt.Errorf("invalid LowThresholdPercent %d, must be in range [0-100]", policy.LowThresholdPercent)
  89. }
  90. if policy.LowThresholdPercent > policy.HighThresholdPercent {
  91. return nil, fmt.Errorf("LowThresholdPercent %d can not be higher than HighThresholdPercent %d", policy.LowThresholdPercent, policy.HighThresholdPercent)
  92. }
  93. im := &realImageGCManager{
  94. runtime: runtime,
  95. policy: policy,
  96. imageRecords: make(map[string]*imageRecord),
  97. cadvisor: cadvisorInterface,
  98. recorder: recorder,
  99. nodeRef: nodeRef,
  100. initialized: false,
  101. }
  102. return im, nil
  103. }
  104. func (im *realImageGCManager) Start() error {
  105. go wait.Until(func() {
  106. // Initial detection make detected time "unknown" in the past.
  107. var ts time.Time
  108. if im.initialized {
  109. ts = time.Now()
  110. }
  111. err := im.detectImages(ts)
  112. if err != nil {
  113. glog.Warningf("[imageGCManager] Failed to monitor images: %v", err)
  114. } else {
  115. im.initialized = true
  116. }
  117. }, 5*time.Minute, wait.NeverStop)
  118. return nil
  119. }
  120. // Get a list of images on this node
  121. func (im *realImageGCManager) GetImageList() ([]kubecontainer.Image, error) {
  122. images, err := im.runtime.ListImages()
  123. if err != nil {
  124. return nil, err
  125. }
  126. return images, nil
  127. }
  128. func (im *realImageGCManager) detectImages(detectTime time.Time) error {
  129. images, err := im.runtime.ListImages()
  130. if err != nil {
  131. return err
  132. }
  133. pods, err := im.runtime.GetPods(true)
  134. if err != nil {
  135. return err
  136. }
  137. // Make a set of images in use by containers.
  138. imagesInUse := sets.NewString()
  139. for _, pod := range pods {
  140. for _, container := range pod.Containers {
  141. glog.V(5).Infof("Pod %s/%s, container %s uses image %s(%s)", pod.Namespace, pod.Name, container.Name, container.Image, container.ImageID)
  142. imagesInUse.Insert(container.ImageID)
  143. }
  144. }
  145. // Add new images and record those being used.
  146. now := time.Now()
  147. currentImages := sets.NewString()
  148. im.imageRecordsLock.Lock()
  149. defer im.imageRecordsLock.Unlock()
  150. for _, image := range images {
  151. glog.V(5).Infof("Adding image ID %s to currentImages", image.ID)
  152. currentImages.Insert(image.ID)
  153. // New image, set it as detected now.
  154. if _, ok := im.imageRecords[image.ID]; !ok {
  155. glog.V(5).Infof("Image ID %s is new", image.ID)
  156. im.imageRecords[image.ID] = &imageRecord{
  157. firstDetected: detectTime,
  158. }
  159. }
  160. // Set last used time to now if the image is being used.
  161. if isImageUsed(image, imagesInUse) {
  162. glog.V(5).Infof("Setting Image ID %s lastUsed to %v", image.ID, now)
  163. im.imageRecords[image.ID].lastUsed = now
  164. }
  165. glog.V(5).Infof("Image ID %s has size %d", image.ID, image.Size)
  166. im.imageRecords[image.ID].size = image.Size
  167. }
  168. // Remove old images from our records.
  169. for image := range im.imageRecords {
  170. if !currentImages.Has(image) {
  171. glog.V(5).Infof("Image ID %s is no longer present; removing from imageRecords", image)
  172. delete(im.imageRecords, image)
  173. }
  174. }
  175. return nil
  176. }
  177. func (im *realImageGCManager) GarbageCollect() error {
  178. // Get disk usage on disk holding images.
  179. fsInfo, err := im.cadvisor.ImagesFsInfo()
  180. if err != nil {
  181. return err
  182. }
  183. capacity := int64(fsInfo.Capacity)
  184. available := int64(fsInfo.Available)
  185. if available > capacity {
  186. glog.Warningf("available %d is larger than capacity %d", available, capacity)
  187. available = capacity
  188. }
  189. // Check valid capacity.
  190. if capacity == 0 {
  191. err := fmt.Errorf("invalid capacity %d on device %q at mount point %q", capacity, fsInfo.Device, fsInfo.Mountpoint)
  192. im.recorder.Eventf(im.nodeRef, api.EventTypeWarning, events.InvalidDiskCapacity, err.Error())
  193. return err
  194. }
  195. // If over the max threshold, free enough to place us at the lower threshold.
  196. usagePercent := 100 - int(available*100/capacity)
  197. if usagePercent >= im.policy.HighThresholdPercent {
  198. amountToFree := capacity*int64(100-im.policy.LowThresholdPercent)/100 - available
  199. glog.Infof("[imageGCManager]: 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)
  200. freed, err := im.freeSpace(amountToFree, time.Now())
  201. if err != nil {
  202. return err
  203. }
  204. if freed < amountToFree {
  205. err := fmt.Errorf("failed to garbage collect required amount of images. Wanted to free %d, but freed %d", amountToFree, freed)
  206. im.recorder.Eventf(im.nodeRef, api.EventTypeWarning, events.FreeDiskSpaceFailed, err.Error())
  207. return err
  208. }
  209. }
  210. return nil
  211. }
  212. func (im *realImageGCManager) DeleteUnusedImages() (int64, error) {
  213. return im.freeSpace(math.MaxInt64, time.Now())
  214. }
  215. // Tries to free bytesToFree worth of images on the disk.
  216. //
  217. // Returns the number of bytes free and an error if any occurred. The number of
  218. // bytes freed is always returned.
  219. // Note that error may be nil and the number of bytes free may be less
  220. // than bytesToFree.
  221. func (im *realImageGCManager) freeSpace(bytesToFree int64, freeTime time.Time) (int64, error) {
  222. err := im.detectImages(freeTime)
  223. if err != nil {
  224. return 0, err
  225. }
  226. im.imageRecordsLock.Lock()
  227. defer im.imageRecordsLock.Unlock()
  228. // Get all images in eviction order.
  229. images := make([]evictionInfo, 0, len(im.imageRecords))
  230. for image, record := range im.imageRecords {
  231. images = append(images, evictionInfo{
  232. id: image,
  233. imageRecord: *record,
  234. })
  235. }
  236. sort.Sort(byLastUsedAndDetected(images))
  237. // Delete unused images until we've freed up enough space.
  238. var deletionErrors []error
  239. spaceFreed := int64(0)
  240. for _, image := range images {
  241. glog.V(5).Infof("Evaluating image ID %s for possible garbage collection", image.id)
  242. // Images that are currently in used were given a newer lastUsed.
  243. if image.lastUsed.Equal(freeTime) || image.lastUsed.After(freeTime) {
  244. glog.V(5).Infof("Image ID %s has lastUsed=%v which is >= freeTime=%v, not eligible for garbage collection", image.id, image.lastUsed, freeTime)
  245. break
  246. }
  247. // Avoid garbage collect the image if the image is not old enough.
  248. // In such a case, the image may have just been pulled down, and will be used by a container right away.
  249. if freeTime.Sub(image.firstDetected) < im.policy.MinAge {
  250. 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)
  251. continue
  252. }
  253. // Remove image. Continue despite errors.
  254. glog.Infof("[imageGCManager]: Removing image %q to free %d bytes", image.id, image.size)
  255. err := im.runtime.RemoveImage(container.ImageSpec{Image: image.id})
  256. if err != nil {
  257. deletionErrors = append(deletionErrors, err)
  258. continue
  259. }
  260. delete(im.imageRecords, image.id)
  261. spaceFreed += image.size
  262. if spaceFreed >= bytesToFree {
  263. break
  264. }
  265. }
  266. if len(deletionErrors) > 0 {
  267. return spaceFreed, fmt.Errorf("wanted to free %d, but freed %d space with errors in image deletion: %v", bytesToFree, spaceFreed, errors.NewAggregate(deletionErrors))
  268. }
  269. return spaceFreed, nil
  270. }
  271. type evictionInfo struct {
  272. id string
  273. imageRecord
  274. }
  275. type byLastUsedAndDetected []evictionInfo
  276. func (ev byLastUsedAndDetected) Len() int { return len(ev) }
  277. func (ev byLastUsedAndDetected) Swap(i, j int) { ev[i], ev[j] = ev[j], ev[i] }
  278. func (ev byLastUsedAndDetected) Less(i, j int) bool {
  279. // Sort by last used, break ties by detected.
  280. if ev[i].lastUsed.Equal(ev[j].lastUsed) {
  281. return ev[i].firstDetected.Before(ev[j].firstDetected)
  282. } else {
  283. return ev[i].lastUsed.Before(ev[j].lastUsed)
  284. }
  285. }
  286. func isImageUsed(image container.Image, imagesInUse sets.String) bool {
  287. // Check the image ID.
  288. if _, ok := imagesInUse[image.ID]; ok {
  289. return true
  290. }
  291. return false
  292. }