概述
Kubernetes 和内置垃圾回收编程语言 (例如 Go, Java) 一样,内部也有垃圾回收机制,用于清理集群中的下列资源:
StorageClass 回收策略为 Delete 的 PV 卷
和编程语言中的 GC 运行机制一样,Kubernetes 中的垃圾回收周期自动执行,集群内每个运行 kubelet 的节点上都会有一个垃圾回收器在运行,
可以简单将其理解为一个独立运行的进程甚至一个 goroutine, 事实上,Kubernetes 的垃圾回收是以
控制器资源
的形式存在和运行的。
Owner, Dependent
Kubernetes 中被依赖的资源对象称之为 Owner (属主资源), 依赖其他资源的对象称之为 Dependent (依赖资源), 例如我们创建了一个副本数量为 3 的 Deployment。
# 官方示例 controllers/nginx-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment labels: app: nginx spec: replicas: 3 # 副本数量,可以根据实际情况修改 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.14.2 ports: - containerPort: 80
那么会产生如下的依赖关系:
Pod 作为 ReplicaSet 的 Dependent, 它的 Owner 为 ReplicaSet (Deployment 底层实现需要 ReplicaSet)
ReplicaSet 作为 Owner 的同时也同样作为 Dependent, 它的 Owner 为 Deployment, Dependent 为 3 个依赖它的 Pod
Deployment 作为 Owner, 它的 Dependent 为依赖的 ReplicaSet
Owner 和 Dependent 依赖关系图
回收机制
默认情况下,垃圾回收采用的是级联删除机制,例如删除 ReplicaSet 资源对象 R1 之后,会删除依赖 R1 资源对象的 Pod 资源对象,
级联删除有两种类型:
前台级联删除
和
后台级联删除
。
前台级联删除:
当资源对象进入垃圾回收过程,垃圾回收控制器先删除其全部依赖 (Dependent) 对象,然后删除该资源 (Owner) 对象。
后台级联删除:
API Server 立即删除该资源 (Owner) 对象,然后由垃圾回收控制器在后台清理其全部依赖 (Dependent) 对象,这是 Kubernetes 默认使用的级联删除方案。
孤儿资源对象
当 Kubernetes 删除某个资源 (Owner) 对象时,其全部依赖 (Dependent) 对象被称作被遗弃的 (Orphaned) 孤儿资源对象,该功能由下文中的
Finalizer
来实现。
Finalizer
Finalizer 用于防止误操作删除了集群依赖的正常运行资源,Finalizer 可以作为资源对象的属性和资源进行绑定,用于执行资源对象在删除之前的逻辑,
所有对象在删除之前,其 Finalizer 属性字段必须为
nil
, API Server 才会删除该对象,这样就可以防止级联删除了。
例如,现在试图删除一个正在被多个 Pod 使用的 PersistentVolume (持久卷) 资源,那么该 PersistentVolume 资源不会被理解删除,
因为 PersistentVolume 资源注册了 Finalizer 。
除了防止级联删除之外,还可以在资源对象删除之前执行指定的钩子函数,例如在一些场景中只想删除当前资源对象,而不想级联删除其依赖对象,
这时就可以在该资源对象上注册 OrphanFinalizer, 那么垃圾回收控制器在删除该资源对象之后,会忽略其依赖对象,这样给开发者自定义实现提供了更强的灵活性。
源码说明
本文着重从源代码的角度分析一下 ReplicaSet 的实现原理,ReplicaSet 功能对应的源代码位于 Kubernetes 项目的
pkg/controller/garbagecollector/
目录,本文以 Kubernetes
v1.28
版本源代码进行分析。
GC 源代码目录
流程图
下面我们跟着流程图一起看下源代码的具体实现。
GC 流程图
GarbageCollector
GarbageCollector
对象表示垃圾回收控制器,是实现垃圾回收功能的核心对象。
通过监听 Informer 来获取资源变化并将结果构造为 DAG (有向无环图) 结构,DAG 对象存储了集群中不同资源对象之间的从属关系,当 DAG 发生变化时,
对应的资源对象可能会被垃圾回收加入到
attemptToDelete
队列,并将对象依赖的对象加入到
attemptToOrphan
队列。
type GarbageCollector struct { ... // attemptToDelete 队列 // 存储垃圾回收尝试删除的资源对象 attemptToDelete workqueue.RateLimitingInterface // attemptToOrphan 队列 // 存储垃圾回收尝试删除的资源对象所依赖的对象 attemptToOrphan workqueue.RateLimitingInterface // 资源对象 DAG 构造器 dependencyGraphBuilder *GraphBuilder }
初始化
NewGarbageCollector
方法通过参数对象构造一个新的
GarbageCollector
对象并返回。
func NewGarbageCollector (...) (*GarbageCollector, error) { ... gc := &GarbageCollector{ ... } gc.dependencyGraphBuilder = &GraphBuilder{ ... } return gc, nil }
启动入口
垃圾回收的启动入口位于 Kubernetes 项目的
/cmd/kube-controller-manager/app/core.go
文件中。
func startGarbageCollectorController (ctx context.Context, ...) (controller.Interface, bool , error) { // 初始化 NewGarbageCollector 对象需要的各项参数 ... // 设置垃圾回收需要忽视的资源类型 ignoredResources := make (map [schema.GroupResource]struct {}) for _, r := range controllerContext.ComponentConfig.GarbageCollectorController.GCIgnoredResources { ignoredResources[schema.GroupResource{Group: r.Group, Resource: r.Resource}] = struct {}{} } // 创建一个 NewGarbageCollector 对象 garbageCollector, err := garbagecollector.NewGarbageCollector( ... ) // 获取垃圾回收并发的 goroutine 数量 (默认为 20 个) workers := int (controllerContext.ComponentConfig.GarbageCollectorController.ConcurrentGCSyncs) // 启动垃圾回收 go garbageCollector.Run(ctx, workers) // 监听集群内的资源对象变化并同步需要被删除的资源对象 go garbageCollector.Sync(ctx, discoveryClient, 30 *time.Second) return garbageCollector, true , nil }
从上面的源代码可以看到,启动垃圾回收器时,调用的核心方法为
GarbageCollector.Run
和
GarbageCollector.Sync
。
开始执行 GC
GarbageCollector.Run
方法作为垃圾回收器的入口方法,主要做两件事情:
单独启动一个 goroutine 执行资源对象的 DAG 构造和同步
根据配置启动相应数量的 goroutine 来处理存储将被回收的资源对象的
attemptToDelete
队列
根据配置启动相应数量的 goroutine 来处理存储将被回收的资源对象所依赖对象的
attemptToOrphan
队列
func (gc *GarbageCollector) Run (ctx context.Context, workers int ) { ... // 启动一个 goroutine 执行 DAG 的构建 go gc.dependencyGraphBuilder.Run(ctx) // 等待所有资源对象的 DAG 构建完成 if !cache.WaitForNamedCacheSync("garbage collector" , ctx.Done(), func () bool { return gc.dependencyGraphBuilder.IsSynced(logger) }) { return } // 所有准备工作就绪之后,就可以执行垃圾回收了 // 根据配置启动多个 goroutine 来执行垃圾回收 for i := 0 ; i go wait.UntilWithContext(ctx, gc.runAttemptToDeleteWorker, 1 *time.Second) go wait.Until(func () { gc.runAttemptToOrphanWorker(logger) }, 1 *time.Second, ctx.Done()) } }
回收对象队列
GarbageCollector.runAttemptToDeleteWorker
方法负责回收队列中要被删除的对象,内部是一个无限循环,通过调用
processAttemptToDeleteWorker
方法来决定退出的具体条件。
func (gc *GarbageCollector) runAttemptToDeleteWorker (ctx context.Context) { for gc.processAttemptToDeleteWorker(ctx) { } }func (gc *GarbageCollector) processAttemptToDeleteWorker (ctx context.Context) bool { // 从队列中取出一个资源对象 item, quit := gc.attemptToDelete.Get() ... // 调用 attemptToDeleteWorker 方法实现删除操作 // 限于篇幅,该方法的实现细节不做具体分析 action := gc.attemptToDeleteWorker(ctx, item) // 根据返回值做具体的操作 switch action { case forgetItem: // 从队列中删除资源对象 gc.attemptToDelete.Forget(item) case requeueItem: // 将资源对象重新入队 gc.attemptToDelete.AddRateLimited(item) } return true }
回收对象依赖队列
回收对象依赖队列
的处理方法和
回收对象队列
方法类似,不同之处只是操作的队列不同和调用的
资源删除方法
不同,这里跳过源代码分析。
func (gc *GarbageCollector) runAttemptToOrphanWorker (logger klog.Logger) { for gc.processAttemptToOrphanWorker(logger) { } }func (gc *GarbageCollector) processAttemptToOrphanWorker (logger klog.Logger) bool { ... }
同步
GarbageCollector.Sync
方法是垃圾回收功能实现的另一个核心方法,主要负责定期同步监听的集群中的资源对象,并过滤出需要删除的资源对象。
func (gc *GarbageCollector) Sync (ctx context.Context, ...) { // 获取可以被回收的资源对象 newResources, err := GetDeletableResources(logger, discoveryClient) ... // 检测可回收对象是否发生变化 // 如果没有任何对象发生变化,意味着本轮垃圾回收无需执行 if reflect.DeepEqual(oldResources, newResources) { return } // 代码执行到这里,说明需要进行一轮垃圾回收操作 // 因为垃圾回收过程中涉及到重建资源 DAG // 所以需要加锁,暂停异步执行的 goroutine gc.workerLock.Lock() defer gc.workerLock.Unlock() attempt := 0 wait.PollImmediateUntilWithContext(ctx, 100 *time.Millisecond, func (ctx context.Context) (bool , error) { attempt++ // 每一轮, 重新获取可以被回收的资源对象 if attempt > 1