目录

  • 1、CRD (CustomResourceDefinition) 介绍

    • 1.1 client-go 组件
    • 1.2 Custom Controller 组件
  • 2、环境、软件准备
  • 3、Kubernetes CRD 示例 sample-controller 使用
  • 4、源码分析 CRD Contorller 的实现
    • 4.1 main 主启动程序
    • 4.2 定义 Controller 结构体
    • 4.3 初始化 Controller
    • 4.4 Deployment & Foo informer 监控资源 CRUD 操作的回调函数
    • 4.5 启动 Controller
    • 4.6 runWorker 队列处理函数

1、CRD (CustomResourceDefinition) 介绍

我们知道,Kubernetes 中一切都可视为资源,它提供了很多默认资源类型,如 Pod、Deployment、Service、Volume等一系列资源,能够满足大多数日常系统部署和管理的需求。但是,在一些特殊的需求场景下,这些现有资源类型就满足不了,那么这些就可以抽象为 Kubernetes 的自定义资源,在 Kubernetes 1.7 之后增加了对 CRD 自定义资源二次开发能力来扩展 Kubernetes API,通过 CRD 我们可以向 Kubernetes API 中增加新资源类型,而不需要修改 Kubernetes 源码或创建自定义的 API server,该功能大大提高了 Kubernetes 的扩展能力。它是 (TPR) ThirdPartyResource 的替代者,在 1.9 以上版本 TPR 将被废弃。

下图展示了 client-go 库各组件如何工作以及同自定义 Container 的交互。

通过图示,我们可以看到几个核心组件以及交互流程,以上蓝色部分是 client-go 组件,黄色部分是自定义 Controller 组件,各组件作用介绍如下:

1.1 client-go 组件

  • Reflector:该组件是用来监测指定资源类型的 Kubernetes API,当监测 API 接收到新资源类型实例变化时,它将通过 List API 来获取新创建的 Object,并将其放入到 Delta Fifo queue(一个先进先出队列)中。
  • Informer:该组件是用来将 Delta Fifo queue 中的 Object 循环取出,并且保存 Object 供后边索引,并调用我们自定义 Controller 传递 Object。
  • Indexer:该组件是为 Object 提供索引功能,典型的用例就是通过 Object 的标签创建索引,并且使用线程安全的数据存储来存储 Object 以及它的 Keys。

1.2 Custom Controller 组件

  • Informer reference:该组件是知道如何使用自定义资源 Object 的 Informer 实例的引用,我们需要在自定义 Controller 代码中创建适当的 Informer。
  • Indexer reference:该组件是知道如何使用自定义资源 Object 的 Indexer 实例的引用,我们需要在自定义 Controller 代码中创建适当的 Indexer,并且将使用该引用处理后续检索 Object。
  • Resource Event Handlers:该组件是当 Informer 要部署 Object 到我们自定义 Controller 时,调用的 Callback 函数。这些函数可以获取被调度 Object 的 Key,并将 Key 存入工作队列以便进行下一步处理。
  • Work queue:该组件是我们自定义 Controller 中创建用来解耦一个处理中的 Object,也是上边 Resource Event Handlers 存储 Key 的地方。
  • Process Item:该组件是我们自定义 Controller 中创建用来处理 Work queue 的一些列函数,这些方法通常使用 Indexer reference 并检索该 Object 对应的 Key。

简单的说,整个处理流程大概为:Reflector 通过检测 Kubernetes API 来跟踪该扩展资源类型的变化,一旦发现有变化,就将该 Object 存储队列中,Informer 循环取出该 Object 并将其存入 Indexer 进行检索,同时触发 Callback 回调函数,并将变更的 Object Key 信息放入到工作队列中,此时自定义 Controller 里面的 Process Item 就会获取工作队列里面的 Key,并从 Indexer 中获取 Key 对应的 Object,从而进行相关的业务处理。

2、环境、软件准备

本次演示环境,我是在本机 MAC OS 上操作,以下是安装的软件及版本:

  • Docker: 17.09.0-ce
  • Oracle VirtualBox: 5.1.20 r114628 (Qt5.6.2)
  • Minikube: v0.28.2
  • Kubernetes: v1.10.0
  • Kubectl:
    • Client Version: v1.10.0
    • Server Version: v1.10.0

注意:这里 Kubernetes 集群搭建使用 Minikube 来完成,Minikube 启动的单节点 k8s Node 实例是需要运行在本机的 VM 虚拟机里面,所以需要提前安装好 VM,这里我选择 Oracle VirtualBox。k8s 运行底层使用 Docker 容器,所以本机需要安装好 Docker 环境,这里忽略 Docker、VirtualBox、Minikube、Kubectl 的安装过程,可以参考之前文章 Minikube & kubectl 升级并配置, 这里着重介绍下 Kubernetes CRD 示例 sample-controller 的使用以及源码分析。

3、Kubernetes CRD 示例 sample-controller 使用

上一篇文章 部署 Prometheus Operator 监控 Kubernetes 集群 中,我们讲到 Prometheus Operator 部署了几种自定义资源类型,如 Alertmanager、Prometheus、ServiceMonitor,通过这些 CRD 资源,很轻松就能部署完整个监控系统,当时,就勾引了我的兴趣,通过几天的摸索和实践,也慢慢了解了 CRD 的工作机制和原理,接下来,我们通过官方示例 sample-controller 来演示下如何使用 Kubernetes CRD。

通过该示例 sample-controller,我们可以清楚的了解到:

  • 如何通过 CRD 创建一个新的自定义资源类型 Foo
  • 如何创建、获取、List 该新资源类型 Foo 的实例
  • 如何在资源处理创建、更新、删除事件上设置 Controller

废话少说,直接操作一下吧!首先进入到本地 $GOPATH 目录,编译并启动该项目。

$ cd $GOPATH/src/k8s.io/sample-controller/
$ go build -o sample-controller .
$./sample-controller -kubeconfig=$HOME/.kube/config

然后,直接使用该示例提供的 CRD 模板文件来创建一个新资源类型 Foo,并创建一个 Foo 类型的资源实例。

# 创建 kind 为 Foo 的 CRD 类型
$ kubectl create -f artifacts/examples/crd.yaml
customresourcedefinition.apiextensions.k8s.io/foos.samplecontroller.k8s.io created
$ kubectl get crd
NAME                                    CREATED AT
foos.samplecontroller.k8s.io            2018-08-17T06:53:25Z# 创建一个 Foo 类型的资源实例
$ kubectl create -f artifacts/examples/example-foo.yaml
foo.samplecontroller.k8s.io/example-foo created
$ kubectl get deployments
NAME          DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
example-foo   1         1         1            1           1m
$ kubectl get pods
NAME                          READY     STATUS    RESTARTS   AGE
example-foo-d74cd7fbc-2dnwn   1/1       Running   0          1m
$ kubectl get foo
NAME          CREATED AT
example-foo   1m

启动完毕,不过,大家肯定该是不懂,为什么就这么简单操作,就可以完成 Foo 资源类型的创建,并且创建了 example-foo deployments 实例呢?那么,我们看下这两个配置文件,到底配置了什么?

$ cat artifacts/examples/crd.yaml
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:name: foos.samplecontroller.k8s.io
spec:group: samplecontroller.k8s.ioversion: v1alpha1names:kind: Fooplural: foosscope: Namespaced

该 CRD 模板定义了一个新的资源管理对象 Foo,在没有修改任何 kubernetes 内核代码条件下,仅仅通过定义 CRD 类型就完成了,非常方便又木有。这样一个新的命名空间 RESTful API 端点就创建了,例如该示例: /apis/samplecontroller.k8s.io/v1alpha1/namespaces/*/foos/...

$ cat example-foo.yaml
apiVersion: samplecontroller.k8s.io/v1alpha1
kind: Foo
metadata:name: example-foo
spec:deploymentName: example-fooreplicas: 1

通过该 yaml 文件,可以创建新资源类型 Foo 的 Pod 实例 example-foo,注意这里 apiVersion: samplecontroller.k8s.io/v1alpha1 要跟 crd.yaml 中配置要匹配 <group>/<version>Kind 指定为新资源类型 Foo。不过这里有人会有疑问,该 yaml 文件没有指定 Deployment 类型,只是指定了 deploymentName 就创建了名称为 example-foo 的 Deployment,而且通过详情可以看到实际上该 Deployment 指定了 nginx:latest 的镜像容器。

$ kubectl describe pod/example-foo-d74cd7fbc-2dnwn
Events:Type    Reason                 Age   From               Message----    ------                 ----  ----               -------Normal  Scheduled              1m    default-scheduler  Successfully assigned example-foo-d74cd7fbc-2dnwn to minikubeNormal  SuccessfulMountVolume  1m    kubelet, minikube  MountVolume.SetUp succeeded for volume "default-token-rnj54"Normal  Pulling                1m    kubelet, minikube  pulling image "nginx:latest"Normal  Pulled                 4s    kubelet, minikube  Successfully pulled image "nginx:latest"Normal  Created                4s    kubelet, minikube  Created containerNormal  Started                4s    kubelet, minikube  Started container

那么这个是怎么实现的呢?其实这就是自定义 CRD Contorller 中定义实现的。接下来通过源码,我们简单分析一下该自定义 Contorller 是如何实现的。

4、源码分析 CRD Contorller 的实现

我们通过源码简要分析一下,自定义 CRD Controller 是如何实现的。首先,在该实例项目根目录下存在两个主要实现文件:main.go 和 controller.go。

main.go 文件主要是作为整个程序的入口主启动程序,使用异步处理,调用 controller.goRun 方法来启动 Foo Controller。

4.1 main 主启动程序

import (# 引入依赖包kubeinformers "k8s.io/client-go/informers""k8s.io/client-go/kubernetes""k8s.io/client-go/tools/clientcmd"clientset "k8s.io/sample-controller/pkg/client/clientset/versioned"informers "k8s.io/sample-controller/pkg/client/informers/externalversions"......
)   # 设置信号标示以便后边异步接收该信号结束进程stopCh := signals.SetupSignalHandler()......# 初始化 kubeInformer、exampleInformer FactorykubeInformerFactory := kubeinformers.NewSharedInformerFactory(kubeClient, time.Second*30)exampleInformerFactory := informers.NewSharedInformerFactory(exampleClient, time.Second*30)# 初始化 Foo controllercontroller := NewController(kubeClient, exampleClient,kubeInformerFactory.Apps().V1().Deployments(),exampleInformerFactory.Samplecontroller().V1alpha1().Foos())# 异步启动 Factorygo kubeInformerFactory.Start(stopCh)go exampleInformerFactory.Start(stopCh)# 调用 Run 启动函数并指定 replica 数量以及异步启动if err = controller.Run(2, stopCh); err != nil {glog.Fatalf("Error running controller: %s", err.Error())}.....

controller.go 是主处理文件,包括 Controller 的定义、初始化、启动、Callback 函数等等操作。

4.2 定义 Controller 结构体

首先需要定义一个 Controller 结构体,包含 deploymentsListerfoosListerworkqueue 等等

type Controller struct {kubeclientset kubernetes.Interfacesampleclientset clientset.InterfacedeploymentsLister appslisters.DeploymentListerdeploymentsSynced cache.InformerSyncedfoosLister        listers.FooListerfoosSynced        cache.InformerSynced# 工作队列workqueue workqueue.RateLimitingInterfacerecorder record.EventRecorder
}

4.3 初始化 Controller

最新版本里,把 kubeInformer 和 fooInformer 初始化放在了 main.go 中,这里进行了 workqueue 初始化。


func NewController(kubeclientset kubernetes.Interface,sampleclientset clientset.Interface,deploymentInformer appsinformers.DeploymentInformer,fooInformer informers.FooInformer) *Controller {utilruntime.Must(samplescheme.AddToScheme(scheme.Scheme))glog.V(4).Info("Creating event broadcaster")eventBroadcaster := record.NewBroadcaster()eventBroadcaster.StartLogging(glog.Infof)eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: kubeclientset.CoreV1().Events("")})recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerAgentName})controller := &Controller{kubeclientset:     kubeclientset,sampleclientset:   sampleclientset,deploymentsLister: deploymentInformer.Lister(),deploymentsSynced: deploymentInformer.Informer().HasSynced,foosLister:        fooInformer.Lister(),foosSynced:        fooInformer.Informer().HasSynced,workqueue:         workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Foos"),recorder:          recorder,}

4.4 Deployment & Foo informer 监控资源 CRUD 操作的回调函数

    fooInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{AddFunc: controller.enqueueFoo,UpdateFunc: func(old, new interface{}) {controller.enqueueFoo(new)},})deploymentInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{AddFunc: controller.handleObject,UpdateFunc: func(old, new interface{}) {newDepl := new.(*appsv1.Deployment)oldDepl := old.(*appsv1.Deployment)if newDepl.ResourceVersion == oldDepl.ResourceVersion {return}controller.handleObject(new)},DeleteFunc: controller.handleObject,})

这里要说下,fooInformer 的处理函数调用 enqueueFoo 方法将对象状态变化事件存入到工作队列中,deploymentInformer 的处理函数调用 handleObject 方法处理对象的 Add、Update、Del 事件,并最终将对象存入到工作队列中。在这里,可以看到 Controller 主要针对以下几种资源事件进行了处理,一个是 Foo 资源的 Add、Update 事件处理,一个是 deployment 资源的 Add、Update、Delete 事件处理。这里 deployment 事件调用 handleObject 方法对所有 deployment 进行过滤,将 Foo 资源实例对应的 deployment 过滤出来,并将对应的事件加入到工作队列中。

4.5 启动 Controller

// workers to finish processing their current work items.
func (c *Controller) Run(threadiness int, stopCh <-chan struct{}) error {defer runtime.HandleCrash()defer c.workqueue.ShutDown()// Start the informer factories to begin populating the informer cachesglog.Info("Starting Foo controller")// Wait for the caches to be synced before starting workersglog.Info("Waiting for informer caches to sync")if ok := cache.WaitForCacheSync(stopCh, c.deploymentsSynced, c.foosSynced); !ok {return fmt.Errorf("failed to wait for caches to sync")}glog.Info("Starting workers")// Launch two workers to process Foo resourcesfor i := 0; i < threadiness; i++ {go wait.Until(c.runWorker, time.Second, stopCh)}glog.Info("Started workers")<-stopChglog.Info("Shutting down workers")return nil
}

在该 Run 函数调用 runWorker 函数运行 Workers 前,需要等待状态的同步完成,然后启动多个 worker 并发的从工作队列中获取待处理的 Item,真正进行业务处理的函数为 runWorker 方法。

4.6 runWorker 队列处理函数

runWorker函数是一个长期运行的函数,它调用 processNextWorkItem 函数来执行读取并处理工作队列上的消息。

func (c *Controller) runWorker() {for c.processNextWorkItem() {}
}func (c *Controller) processNextWorkItem() bool {obj, shutdown := c.workqueue.Get()if shutdown {return false}err := func(obj interface{}) error {defer c.workqueue.Done(obj)var key stringvar ok boolif key, ok = obj.(string); !ok {c.workqueue.Forget(obj)runtime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", obj))return nil}if err := c.syncHandler(key); err != nil {return fmt.Errorf("error syncing '%s': %s", key, err.Error())}c.workqueue.Forget(obj)glog.Infof("Successfully synced '%s'", key)return nil}(obj)
......
}

工作队列中的每一个 item 都要调用 syncHandler 函数进行处理,其中就包括 Foo Deployment 的创建和更新。

func (c *Controller) syncHandler(key string) error {......deployment, err := c.deploymentsLister.Deployments(foo.Namespace).Get(deploymentName)if errors.IsNotFound(err) {deployment, err = c.kubeclientset.AppsV1().Deployments(foo.Namespace).Create(newDeployment(foo))}......if foo.Spec.Replicas != nil && *foo.Spec.Replicas != *deployment.Spec.Replicas {glog.V(4).Infof("Foo %s replicas: %d, deployment replicas: %d", name, *foo.Spec.Replicas, *deployment.Spec.Replicas)deployment, err = c.kubeclientset.AppsV1().Deployments(foo.Namespace).Update(newDeployment(foo))}......err = c.updateFooStatus(foo, deployment)if err != nil {return err}c.recorder.Event(foo, corev1.EventTypeNormal, SuccessSynced, MessageResourceSynced)
}

每个 Deployment 事件都会调用 newDeployment 函数产生一个新的 Deployment 实例配置如下,这里就是上边提到疑问 没有指定 Deployment 类型,只是指定了 deploymentName 就创建了名称为 example-foo 容器镜像为 nginx:latest 的 Deployment,就是在这里定义的。

func newDeployment(foo *samplev1alpha1.Foo) *appsv1.Deployment {labels := map[string]string{"app":        "nginx","controller": foo.Name,}return &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name:      foo.Spec.DeploymentName,Namespace: foo.Namespace,OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(foo, schema.GroupVersionKind{Group:   samplev1alpha1.SchemeGroupVersion.Group,Version: samplev1alpha1.SchemeGroupVersion.Version,Kind:    "Foo",}),},},Spec: appsv1.DeploymentSpec{Replicas: foo.Spec.Replicas,Selector: &metav1.LabelSelector{MatchLabels: labels,},Template: corev1.PodTemplateSpec{ObjectMeta: metav1.ObjectMeta{Labels: labels,},Spec: corev1.PodSpec{Containers: []corev1.Container{{Name:  "nginx",Image: "nginx:latest",},},},},},}

以上简单分析了两个主要的文件,而其中它调用的一些核心自定义函数,位于项目 pkg 目录下,该目录下的函数是基于 client-go 进行的调用以及扩展,建议大家细细研究下。

import (......samplev1alpha1 "k8s.io/sample-controller/pkg/apis/samplecontroller/v1alpha1"clientset "k8s.io/sample-controller/pkg/client/clientset/versioned"samplescheme "k8s.io/sample-controller/pkg/client/clientset/versioned/scheme"informers "k8s.io/sample-controller/pkg/client/informers/externalversions/samplecontroller/v1alpha1"listers "k8s.io/sample-controller/pkg/client/listers/samplecontroller/v1alpha1"
)

参考资料

  • Github sample-controller
  • Kubernetes Custom Resources
  • Extend the Kubernetes API with CustomResourceDefinitions

Kubernetes CRD (CustomResourceDefinition) 自定义资源类型相关推荐

  1. Crd(自定义资源类型)2021.12.05

    目录 文章目录 目录 实验环境 实验软件 1.什么是CRD 2.CRD的定义 3.Controller 4.Operator 5.参考文档 关于我 最后 实验环境 实验环境: 1.win10,vmwr ...

  2. 【云原生】Kubernetes CRD 详解(Custom Resource Definition)

    文章目录 一.概述 二.定制资源 1)定制资源 和 定制控制器 2)定制控制器 3)Operator 介绍 1.Operator Framework 2.Operator 安装 3.安装 Operat ...

  3. VC中使用自定义资源

    前言 在VC环境中除了我们所常用的Dialog.Menu和Bitmap等标准资源类型之外,它还支持自定义资源类型(Custom Resource),我们自定义资源类型能做些什么呢?呵呵,用处多多. 1 ...

  4. k8s自定义资源CRD

    一.概述 在K8S系统扩展点中,开发者可以通过CRD(CustomResourceDefinition)来扩展K8SAPI,其功能主要由APIExtensionServer负责.使用kubernete ...

  5. @kubernetes(k8s)的kubectl的使用及资源类型pod生命周期与资源清单详解

    文章目录 kubernetes 一.kubernetes kubectl的使用 1.kubectl 的概述: 2.kubectl的使用 2.kubectl可操作的资源对象类型 3.kubectl子命令 ...

  6. operator-sdk实战开发K8S CRD自定义资源对象

    环境说明 系统:CentOS Linux release 7.6.1810 (Core) golang:v1.15 operator-sdk:v1.7.0 docker:v1.19 # 因为 oper ...

  7. 通过自定义资源扩展Kubernetes

    原文链接:通过自定义资源扩展Kubernetes 转载于:https://www.cnblogs.com/wangjq19920210/p/11555996.html

  8. Kubernetes CRD开发汇总

    1. Kubernetes CRD开发 1.1 kubernetes 自定义资源(CRD) 在研究 Service Mesh 的过程中,发现 Istio 很多参数都通过 kubernetes CRD ...

  9. Kubernetes CRD开发模式及源码实现深入剖析-Kubernetes商业环境实战

    专注于大数据及容器云核心技术解密,可提供全栈的大数据+云原生平台咨询方案,请持续关注本套博客.如有任何学术交流,可随时联系.留言请关注<数据云技术社区>公众号. 1 CRD资源扩展 CRD ...

最新文章

  1. CVPR禁令出台:审稿期间禁止主动在社交媒体宣传论文!LeCun:阻碍科学交流,简直疯了...
  2. 不是我散漫了,是病了——书于平安夜
  3. python 读取txt
  4. 在dreamweaver mx中它只能对html文件可以进行编辑,Dreamweaver试题
  5. awstats+jawstats自动分析日志
  6. 腾讯AI Lab开源800万中文词的NLP数据集 | 资源
  7. xp 与 windows 7 共享收藏夹
  8. 利用深度迁移学习进行基于图像的植物病害识别
  9. GDAL使用插件方式编译HDF4、HDF5以及NetCDF的bug修改
  10. 坚果云忽略同步文件的设置
  11. allegro中10mil过孔_Allegro中增加过孔的方法
  12. chrome实现屏幕取词并翻译
  13. matlab斑点噪声算法,粒子滤波算法中的噪声问题
  14. html文字闪烁特效代码,HTML最简单的文字闪烁代码
  15. 基于python爬虫数据分析论文_基于Python的招聘网站信息爬取与数据分析
  16. Python笔记-pyautogui 图片定位
  17. 【UVM基础】两种启动 sequence 的方式
  18. 安装 VMWare及VMware下创建的虚拟PC机安装Linux操作系统
  19. 使用JAVA进行ad域身份验证常用属性详解
  20. Centos7 搭建Nginx图片服务超详细新手小白教程

热门文章

  1. 数据库sequence的作用和用法
  2. 【ZYNQ_LINUX】设备树分析1
  3. ZYNQ使用AXI_Ethernet编译系统扩展多网口
  4. 视频教程-Unity2019项目实战到上架AppStore(苹果商店)-Unity3D
  5. c语言程序设计李新华,21世纪高等学校规划教材:C语言程序设计
  6. C++之_In_和_out_
  7. 如何在游戏中实现穿墙功能
  8. 索尼wifi控制相机开发总结(四):HttpSonyCamera的说明及实现
  9. 索尼相机照片误删,有什么办法可以恢复?
  10. AJAX框架衣柜设计师,9张衣柜设计,设计师真的是那么不堪吗?