containerGC
# 1.imageManager
# 1.1.简介
kubelet对节点上的所有镜像提供生命周期管理服务,磁盘使用率超出设定上限(HighThresholdPercent),会按照LRU清除策略逐个清理未被容器使用的镜像,直至磁盘使用率降低到设定下限(LowThresholdPercent)或没有空闲镜像可以清理。此外,镜像清理会检查生存年龄,未达到最短生存年龄(MinAge)要求的镜像不予处理。注意
1.
kubelet读取的镜像列表是节点上的镜像,读取的容器列表仅包括节点绑定容器2.
containerRuntime创建的容器才能被管理,手动run的容器对垃圾回收不可见,也就无法阻止相关镜像回收,只是镜像会回收失败
# 1.2.初始化
NewImageGCManager()用于初始化镜像回收管理器,执行节点镜像生命周期管理,周期性检查节点镜像占用情况及触发垃圾回收。// ImageGCManager is an interface for managing lifecycle of all images. // Implementation is thread-safe. type ImageGCManager interface { // 根据策略执行垃圾回收 GarbageCollect() error // 异步执行镜像回收 Start() // 镜像列表 GetImageList() ([]container.Image, error) // 删除未使用镜像 DeleteUnusedImages() error } type realImageGCManager struct { // Container runtime runtime container.Runtime // 维护镜像使用信息 imageRecords map[string]*imageRecord imageRecordsLock sync.Mutex // 回收策略 [80%,85%] 最小保留时间2min policy ImageGCPolicy // 决策来源,提供磁盘使用率/镜像占用大小/容器统计信息 statsProvider StatsProvider ... // node对象引用 nodeRef *v1.ObjectReference // 标记GC管理器初始化 initialized bool // 镜像缓存(最近获取镜像列表) imageCache imageCache // sandbox镜像不会删除 sandboxImage string } // NewImageGCManager instantiates a new ImageGCManager object. func NewImageGCManager(runtime container.Runtime, statsProvider StatsProvider, recorder record.EventRecorder, nodeRef *v1.ObjectReference, policy ImageGCPolicy, sandboxImage string) (ImageGCManager, error) { ... im := &realImageGCManager{ runtime: runtime, policy: policy, imageRecords: make(map[string]*imageRecord), statsProvider: statsProvider, recorder: recorder, nodeRef: nodeRef, initialized: false, sandboxImage: sandboxImage, } return im, nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60注意
1.
ImageGCHighThresholdPercent初始值为85%,磁盘占用率超出阈值持续触发回收2.
ImageGCLowThresholdPercent初始值为80%,磁盘占用率低于阈值或无镜像可回收停止GC3.
ImageMinimumGCAge初始值为2min,镜像年龄低于阈值不会GC
# 1.3.镜像同步
kubelet启动期间会调用imageManager.Start()启动镜像管理器,分别负责镜像监控及更新镜像缓存,维护镜像占用情况供GC阶段回收。func (im *realImageGCManager) Start() { // 周期5min执行 go wait.Until(func() { ... // 检测当前镜像使用情况(镜像回收) _, err := im.detectImages(ts) ... im.initialized = true }, 5*time.Minute, wait.NeverStop) // 周期30s执行 go wait.Until(func() { // 获取运行时镜像 images, err := im.runtime.ListImages() ... // 更新缓存 im.imageCache.set(images) }, 30*time.Second, wait.NeverStop) } // GC回收镜像的数据来源 func (im *realImageGCManager) detectImages(detectTime time.Time) (sets.String, error) { imagesInUse := sets.NewString() // 查询containerd中的pause镜像ID(sandbox pause镜像不回收) imageRef, err := im.runtime.GetImageRef(container.ImageSpec{Image: im.sandboxImage}) // 记录sandbox镜像ID if err == nil && imageRef != "" { imagesInUse.Insert(imageRef) } // 查询containerd的镜像列表 images, err := im.runtime.ListImages() ... // 查询pod列表 pods, err := im.runtime.GetPods(true) ... // 整理pod使用的镜像列表 for _, pod := range pods { for _, container := range pod.Containers { imagesInUse.Insert(container.ImageID) } } ... // 记录所有镜像信息 currentImages := sets.NewString() ... for _, image := range images { // 记录镜像ID currentImages.Insert(image.ID) // 新镜像,更新镜像记录 if _, ok := im.imageRecords[image.ID]; !ok { im.imageRecords[image.ID] = &imageRecord{ firstDetected: detectTime, } } // 镜像正在使用,更新镜像记录检测时间 if isImageUsed(image.ID, imagesInUse) { im.imageRecords[image.ID].lastUsed = now } // 更新镜像Size im.imageRecords[image.ID].size = image.Size // 更新允许回收状态(Pinned为true代表未被使用也不回收) im.imageRecords[image.ID].pinned = image.Pinned } // 更新镜像记录中过期已回收的镜像 for image := range im.imageRecords { if !currentImages.Has(image) { delete(im.imageRecords, image) } } return imagesInUse, nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# 1.4.镜像回收
kubelet会调用GC的接口StartGarbageCollection()启动imageManager协程,间隔5min调用GarbageCollect()执行一次镜像回收。// StartGarbageCollection starts garbage collection threads. func (kl *Kubelet) StartGarbageCollection() { ... // when the high threshold is set to 100, stub the image GC manager if kl.kubeletConfiguration.ImageGCHighThresholdPercent == 100 { return } // 间隔5min执行一次 go wait.Until(func() { // 执行一次回收 kl.imageManager.GarbageCollect() ... }, ImageGCPeriod, wait.NeverStop) } // 镜像回收 func (im *realImageGCManager) GarbageCollect() error { // 获取节点images目录的磁盘占用信息 fsStats, err := im.statsProvider.ImageFsStats() ... // 容量及可用的空间 capacity := int64(fsStats.CapacityBytes) available := int64(fsStats.AvailableBytes) if available > capacity { available = capacity } // Check valid capacity. if capacity == 0 { return goerrors.New("invalid capacity 0 on image filesystem") } // 检查images的磁盘占用率是否超出85% usagePercent := 100 - int(available*100/capacity) if usagePercent >= im.policy.HighThresholdPercent { // 计算需释放空间 amountToFree := capacity*int64(100-im.policy.LowThresholdPercent)/100 - available // 释放空间 freed, err := im.freeSpace(amountToFree, time.Now()) ... // 释放空间低于预期,返回错误触发事件推送 if freed < amountToFree { return err } } return nil } // Tries to free bytesToFree worth of images on the disk. func (im *realImageGCManager) freeSpace(bytesToFree int64, freeTime time.Time) (int64, error) { // 检测镜像更新缓存,获取正在使用的镜像 imagesInUse, err := im.detectImages(freeTime) ... im.imageRecordsLock.Lock() defer im.imageRecordsLock.Unlock() // Get all images in eviction order. images := make([]evictionInfo, 0, len(im.imageRecords)) // 获取所有的镜像 for image, record := range im.imageRecords { // 正在使用中 if isImageUsed(image, imagesInUse) { continue } // 镜像标记固定 if record.pinned { 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 { // 上次使用时间位于回收之后 if image.lastUsed.Equal(freeTime) || image.lastUsed.After(freeTime) { continue } // 镜像年龄不足2min if freeTime.Sub(image.firstDetected) < im.policy.MinAge { continue } // Remove image. Continue despite errors. im.runtime.RemoveImage(container.ImageSpec{Image: image.id}) ... // 清理缓存镜像 delete(im.imageRecords, image.id) // 记录回收空间 spaceFreed += image.size // 释放空间达到预期 if spaceFreed >= bytesToFree { break } } ... return spaceFreed, nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
注意
imageManager会周期性执行镜像回收,evictManager驱逐应用也会主动调用执行一次镜像回收
# 2.containerGC
# 2.1.简介
pod删除不会立即清理底层数据,依赖containerGC周期性完成磁盘文件清理,避免影响系统运行速度。containerGC执行回收的目标有普通容器、sandbox容器及容器日志目录。注意
1.
containerGC会清理pod一定数量死亡容器2.
containerGC会清理pod的sandbox容器,只保留一个3.
containerGC会清理没有关联pod的日志目录
# 2.2.初始化
NewContainerGC()用于初始化容器回收管理器,执行节点容器生命周期管理,周期性检查节点容器情况及触发垃圾回收。// GC manages garbage collection of dead containers. // Implementation is thread-compatible. type GC interface { // Garbage collect containers. GarbageCollect() error // Deletes all unused containers, including containers belonging to pods that are terminated but not deleted DeleteAllUnusedContainers() error } type realContainerGC struct { // 运行时 runtime Runtime // 回收策略 policy GCPolicy // sourcesReadyProvider provides the readiness of kubelet configuration sources. sourcesReadyProvider SourcesReadyProvider } // NewContainerGC creates a new instance of GC with the specified policy. func NewContainerGC(runtime Runtime, policy GCPolicy, sourcesReadyProvider SourcesReadyProvider) (GC, error) { ... return &realContainerGC{ runtime: runtime, policy: policy, sourcesReadyProvider: sourcesReadyProvider, }, nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29注意
1.
MaxContainerCount初始值为100,标记集群最大支持的container数量2.
MaxPerPodContainerCount初始值为2,标记每个容器最大保留的已退出实例3.
MinimumGCAge初始值为1min,标记container结束的停留时间
# 2.3.容器回收
kubelet会调用GC的接口StartGarbageCollection()启动containerGC协程,间隔1min调用GarbageCollect()执行一次容器回收。// StartGarbageCollection starts garbage collection threads. func (kl *Kubelet) StartGarbageCollection() { // 1min执行一次 go wait.Until(func() { // 执行容器回收 kl.containerGC.GarbageCollect() ... }, ContainerGCPeriod, wait.NeverStop) ... } // 调用containerRuntime执行回收 func (cgc *realContainerGC) GarbageCollect() error { return cgc.runtime.GarbageCollect(cgc.policy, cgc.sourcesReadyProvider.AllReady(), false) } // GarbageCollect removes dead containers using the specified container gc policy. func (m *kubeGenericRuntimeManager) GarbageCollect(...) error { return m.containerGC.GarbageCollect(gcPolicy, allSourcesReady, evictNonDeletedPods) } // GarbageCollect removes dead containers using the specified container gc policy. func (cgc *containerGC) GarbageCollect(gcPolicy kubecontainer.GCPolicy, allSourcesReady bool, evictNonDeletedPods bool) error { // 普通容器回收 cgc.evictContainers(gcPolicy, allSourcesReady, evictNonDeletedPods) ... // sandbox容器回收 cgc.evictSandboxes(evictNonDeletedPods) ... // sandbox日志清理 cgc.evictPodLogsDirectories(allSourcesReady) ... return utilerrors.NewAggregate(errors) }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
注意
1.
pod终止后,container只是停止运行,不会立即删除2.
container真正清理由containerGC周期触发,evictManager驱逐pod也会触发一次垃圾回收3.
containerLog清理依赖containerManager