kubernetes垃圾回收器GarbageCollector源码分析(一)(1)

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
简介: kubernetes垃圾回收器GarbageCollector源码分析(一)


kubernetes版本:1.13.2

背景

由于operator创建的 redis集群,在kubernetes apiserver重启后,redis集群被异常删除(包括redis exporter statefulset、redis statefulset)。删除后operator将其重建,重新组建集群,实例IP发生变更(中间件容器化,我们开发了固定IP,当statefulset删除后,IP会被回收),导致创建集群失败,最终集群不可用。 经多次复现,apiserver重启后,通过查询redis operator日志,并没有发现主动去删除redis集群(redis statefulset)、监控实例(redis exporter)。进一步去查看kube-controller-manager的日志,将其日志级别设置--v=5,继续复现,最终在kube-controller-manager日志中发现如下日志: 可以看到是garbage collector触发删除操作的。这个问题在apiserver正常的时候是不存在,要想弄其究竟,就得 看看kube-controller-manager内置组件garbage collector这个控制器的逻辑。

由于内容偏长,分为多节来讲:

①、monitors作为生产者将变化的资源放入graphChanges队列;同时restMapper定期检测集群内资源类型,刷新monitors

②、runProcessGraphChangesgraphChanges队列中取出变化的item,根据情况放入attemptToDelete队列;runAttemptToDeleteWorker取出处理垃圾资源;

③、runProcessGraphChangesgraphChanges队列中取出变化的item,根据情况放入attemptToOrphan队列;runAttemptToOrphanWorker取出处理该该孤立的资源;


正文

想要启用GC,需要在kube-apiserverkube-controller-manager的启动参数中都设置--enable-garbage-collectortrue,1.13.2版本中默认开启GC

需要注意:两组件该参数必须保持同步。


kube-controller-manager启动入口,app.NewControllerManagerCommand()中加载controller manager默认启动参数,创建* cobra.Command对象:

func main() {
        rand.Seed(time.Now().UnixNano())
        //加载controller manager默认启动参数,创建* cobra.Command对象
        command := app.NewControllerManagerCommand()
        //......省略.......
        //执行cobra.command,并启动controller-manager
        if err := command.Execute(); err != nil {
            fmt.Fprintf(os.Stderr, "%v\n", err)
            os.Exit(1)
        }
}

以下代码处去启动kube-controller-manager

NewDefaultComponentConfig(ports.InsecureKubeControllerManagerPort)加载各个控制器的配置:

//NewKubeControllerManagerOptions使用默认配置创建一个新的KubeControllerManagerOptions
func NewKubeControllerManagerOptions() (*KubeControllerManagerOptions, error) {
    //加载各个控制器的默认配置
    componentConfig, err := NewDefaultComponentConfig(ports.InsecureKubeControllerManagerPort)
    if err != nil {
        return nil, err
    }
 
    s := KubeControllerManagerOptions{
        Generic:         cmoptions.NewGenericControllerManagerConfigurationOptions(componentConfig.Generic),
        //.....省略
        GarbageCollectorController: &GarbageCollectorControllerOptions{
            ConcurrentGCSyncs:      componentConfig.GarbageCollectorController.ConcurrentGCSyncs,
            EnableGarbageCollector: componentConfig.GarbageCollectorController.EnableGarbageCollector,
        },
        //.....省略
    }
    //gc忽略的资源对象列表
    gcIgnoredResources := make([]kubectrlmgrconfig.GroupResource, 0, len(garbagecollector.DefaultIgnoredResources()))
    for r := range garbagecollector.DefaultIgnoredResources() {
        gcIgnoredResources = append(gcIgnoredResources, kubectrlmgrconfig.GroupResource{Group: r.Group, Resource: r.Resource})
    }
    s.GarbageCollectorController.GCIgnoredResources = gcIgnoredResources
    return &s, nil
}
// NewDefaultComponentConfig返回kube-controller管理器配置对象
func NewDefaultComponentConfig(insecurePort int32) (kubectrlmgrconfig.KubeControllerManagerConfiguration, error) {
    scheme := runtime.NewScheme()
    if err := kubectrlmgrschemev1alpha1.AddToScheme(scheme); err != nil {
        return kubectrlmgrconfig.KubeControllerManagerConfiguration{}, err
    }
    if err := kubectrlmgrconfig.AddToScheme(scheme); err != nil {
        return kubectrlmgrconfig.KubeControllerManagerConfiguration{}, err
    }
 
    versioned := kubectrlmgrconfigv1alpha1.KubeControllerManagerConfiguration{}
    //加载默认参数
    scheme.Default(&versioned)
 
    internal := kubectrlmgrconfig.KubeControllerManagerConfiguration{}
    if err := scheme.Convert(&versioned, &internal, nil); err != nil {
        return internal, err
    }
    internal.Generic.Port = insecurePort
    return internal, nil
}
// 根据Object,获取提供的默认参数
func (s *Scheme) Default(src Object) {
    if fn, ok := s.defaulterFuncs[reflect.TypeOf(src)]; ok {
        fn(src)
    }
}

s.defaulterFuncs类型为map[reflect.Type]func(interface{}),用于根据指针类型获取默认值函数。该map中的数据从哪里来的呢?

代码位于src\k8s.io\kubernetes\pkg\controller\apis\config\v1alpha1\zz_generated.defaults.go

可以看到默认参数中garbage collector中默认开启gc(EnableGarbageCollector),并发数为20(ConcurrentGCSyncs)

func SetDefaults_GarbageCollectorControllerConfiguration(obj *kubectrlmgrconfigv1alpha1.GarbageCollectorControllerConfiguration) {
    if obj.EnableGarbageCollector == nil {
        obj.EnableGarbageCollector = utilpointer.BoolPtr(true)
    }
    if obj.ConcurrentGCSyncs == 0 {
        obj.ConcurrentGCSyncs = 20
    }
}

回到Run函数,里面调用了NewControllerInitializers启动所有控制器:

重点来到启动garbage collector的startGarbageCollectorController函数:

func startGarbageCollectorController(ctx ControllerContext) (http.Handler, bool, error) {
    //k8s 1.13.2中默认为true,可在kube-apiserver和kube-controller-manager的启动参数中加--enable-garbage-conllector=false设置
    //需保证这两个组件中参数值一致
    if !ctx.ComponentConfig.GarbageCollectorController.EnableGarbageCollector {
        return nil, false, nil
    }
 
    //k8s各种原生资源对象客户端集合(默认启动参数中用SimpleControllerClientBuilder构建)
    gcClientset := ctx.ClientBuilder.ClientOrDie("generic-garbage-collector")
    discoveryClient := cacheddiscovery.NewMemCacheClient(gcClientset.Discovery())
 
    //生成rest config
    config := ctx.ClientBuilder.ConfigOrDie("generic-garbage-collector")
    dynamicClient, err := dynamic.NewForConfig(config)
    if err != nil {
        return nil, true, err
    }
 
    // Get an initial set of deletable resources to prime the garbage collector.
    //获取一组初始可删除资源以填充垃圾收集器。
    deletableResources := garbagecollector.GetDeletableResources(discoveryClient)
    ignoredResources := make(map[schema.GroupResource]struct{})
 
    //忽略gc的资源类型
    for _, r := range ctx.ComponentConfig.GarbageCollectorController.GCIgnoredResources {
        ignoredResources[schema.GroupResource{Group: r.Group, Resource: r.Resource}] = struct{}{}
    }
    garbageCollector, err := garbagecollector.NewGarbageCollector(
        dynamicClient,
        ctx.RESTMapper,
        deletableResources,
        ignoredResources,
        ctx.InformerFactory,
        ctx.InformersStarted,
    )
    if err != nil {
        return nil, true, fmt.Errorf("Failed to start the generic garbage collector: %v", err)
    }
 
    // Start the garbage collector.
    //启动参数中默认是20个协程
    workers := int(ctx.ComponentConfig.GarbageCollectorController.ConcurrentGCSyncs)
    //启动monitors和deleteWorkers、orphanWorkers
    go garbageCollector.Run(workers, ctx.Stop)
 
    // Periodically refresh the RESTMapper with new discovery information and sync
    // the garbage collector.
    //使用新的发现信息定期刷新RESTMapper并同步垃圾收集器。
    go garbageCollector.Sync(gcClientset.Discovery(), 30*time.Second, ctx.Stop)
 
    //gc提供debug dot grap依赖关系图接口
    return garbagecollector.NewDebugHandler(garbageCollector), true, nil
}

该函数主要作用有:

1、deletableResources := garbagecollector.GetDeletableResources(discoveryClient)获取集群内所有可删除的资源对象;排除掉忽略的资源对象。

2、构建garbageCollector结构体对象;

3、garbageCollector.Run(workers, ctx.Stop)启动一个monitors用来监听资源对象的变化(对应的由runProcessGraphChanges死循环处理),和默认20个deleteWorkers协程处理可删除的资源对象、20个orphanWorkers协程处理孤儿对象。

4、garbageCollector.Sync(gcClientset.Discovery(), 30*time.Second, ctx.Stop) 定时去获取一个集群内是否有新类型的资源对象的加入,并重新刷新monitors,以监听新类型的资源对象。

5、garbagecollector.NewDebugHandler(garbageCollector)注册debug接口,用来提供获取dot流程图接口:

curl http://127.0.0.1:10252/debug/controllers/garbagecollector/graph?uid=11211212edsaddkqedmk12

使用graphviz提供的dot.exe可以生成svg格式的图,可用google浏览器查看如下:

// curl http://127.0.0.1:10252/debug/controllers/garbagecollector/graph?uid=11211212edsaddkqedmk12
func (h *debugHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    if req.URL.Path != "/graph" {
        http.Error(w, "", http.StatusNotFound)
        return
    }
 
    var graph graph.Directed
    if uidStrings := req.URL.Query()["uid"]; len(uidStrings) > 0 {
        uids := []types.UID{}
        for _, uidString := range uidStrings {
            uids = append(uids, types.UID(uidString))
        }
        graph = h.controller.dependencyGraphBuilder.uidToNode.ToGonumGraphForObj(uids...)
 
    } else {
        graph = h.controller.dependencyGraphBuilder.uidToNode.ToGonumGraph()
    }
 
    //生成dot流程图数据,用graphviz工具中的dot.exe工具转换为svg图(用google浏览器打开)或者png图
    //API参考:https://godoc.org/gonum.org/v1/gonum/graph
    //graphviz下载地址:https://graphviz.gitlab.io/_pages/Download/Download_windows.html
    //dot.exe test.dot -T svg -o test.svg
    data, err := dot.Marshal(graph, "full", "", "  ", false)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    w.Write(data)
    w.WriteHeader(http.StatusOK)
}


kubernetes垃圾回收器GarbageCollector源码分析(一)(2)

相关实践学习
容器服务Serverless版ACK Serverless 快速入门:在线魔方应用部署和监控
通过本实验,您将了解到容器服务Serverless版ACK Serverless 的基本产品能力,即可以实现快速部署一个在线魔方应用,并借助阿里云容器服务成熟的产品生态,实现在线应用的企业级监控,提升应用稳定性。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
2天前
|
缓存 Kubernetes Java
kubernetes垃圾回收器GarbageCollector源码分析(一)(2)
kubernetes垃圾回收器GarbageCollector源码分析(一)
|
缓存 Prometheus 监控
Kubernetes容器监控原理与源码分析(一)——API与数据来源
## 前言 本系列主要基于v1.24.0版本的Kubelet部分源代码,进行Kubernetes中容器监控的底层原理介绍与代码分析。 ## Kubelet中的监控API 在Kubelet Server提供的监控API中,大致可以分为两类:stats(统计数据)和metrics(指标数据)。从命名和实际作用来看,前者提供了粗粒度的基础监控能力,目前用于各种内置组件;而后者用于持久化地进行细粒度的容器
780 2
Kubernetes容器监控原理与源码分析(一)——API与数据来源
|
网络协议 应用服务中间件 网络安全
Kubernetes Nginx Ingress Controller源码分析
main controllers/nginx/pkg/cmd/controller/main.go:29 func main() { // start a new nginx controller ngx := newNGINXController() // create a custom .
2496 0
|
Kubernetes 容器
Kubernetes Client-go Informer 源码分析
几乎所有的Controller manager 和CRD Controller 都会使用Client-go 的Informer 函数,这样通过Watch 或者Get List 可以获取对应的Object,下面我们从源码分析角度来看一下Client go Informer 的机制。
4536 0
|
Kubernetes 网络协议 数据格式
kubeadm源码分析(kubernetes离线安装包,三步安装)
kubeadm源码分析 说句实在话,kubeadm的代码写的真心一般,质量不是很高。 几个关键点来先说一下kubeadm干的几个核心的事: kubeadm 生成证书在/etc/kubernetes/pki目录下 kubeadm 生成static pod yaml配置,全部在/etc/kuberne.
1738 0
|
Kubernetes JavaScript 调度
深入K8S Job(三):cronJob controller源码分析
源码流程图 概述 cronJob controller 的实现比较简单,使用 Cron - Wikipedia 的方法,确定调度规则,底层的调度对象就是依赖了 job,它不会去检查任何 Pod。 该 controller 也没有依赖各种 informer,就简单创建了一个循环运行的协程,每次遍历现有的 jobs & cronJobs,整理它们的关系并进行管理。
1962 0
|
Kubernetes Go 网络安全
k8s与网络--Flannel源码分析
之前在k8s与网络--Flannel解读一文中,我们主要讲了Flannel整体的工作原理。今天主要针对Flannel v0.10.0版本进行源码分析。首先需要理解三个比较重要的概念: 网络(Network):整个集群中分配给 flannel 要管理的网络地址范围 子网(Subnet):flanne.
2102 0
|
Kubernetes API Go
Kubernetes1.5源码分析apiServer之go-restful的使用
源码版本 Kubernetes v1.5.0 go-restful 简介 go-restful是用于构建REST-style web服务的golang包。 它是出现时因为一个javaer在golang中没找到顺手的REST-based服务构建包,所以就按照他在java里常用的JAX-RS的设计,在golang中造了一个轮子。
1515 0
|
Kubernetes 算法 调度
Kubernetes 1.8 kube-scheduler的源码分析
很长时间没有写文章,一直在啃kubernetes文档,本来立志一定要读完所有的文档。还有它的最佳实践openshift的文档。但目前为止,我并没有读完kubernetes的文档。当前,我们有需求需要客制化kubernetes的调度函数,所以开始研究kube-scheduler的代码。
2710 0
|
28天前
|
Kubernetes 微服务 容器
Aspire项目发布到远程k8s集群
Aspire项目发布到远程k8s集群
379 2
Aspire项目发布到远程k8s集群