暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Kubernetes之CRD

深夜IT研究猿 2019-02-14
2770

K8sCRD




上一节主要讲述了K8s的动态准入控制器Initializers,这一节我们介绍另一种扩展K8sAPI的方式,CRD。


一、CRD概述


Kubernetes中将一切都视为资源,资源是Kubernetes API中的一个Endpoint,存储了某种类型的API对象,例如PodDeploymentConfigmapVolume等都是一种资源,不过这些都属于内置资源,是Kubernetes默认提供的资源类型,在Kubernetes V1.7之后提供了一种自定义资源,代表某种自定义的配置或者独立运行的服务,这种自定义资源类型就是CRDCustomerResourceDefinition)。

CRD作为一种Kubernetes扩展资源存在,就是为了满足Kubernetes自身不具备的一些功能,可以通过CRD保证新的资源快速的注册和使用,要使用CRD,你可以把它理解成两部分,Customer ResourceCustomer ControllerCustomer Resource可以让用户简单的存储和获取结构化数据,将这种对象动态的注册到Kubernetes集群中。而Customer Controller可以把上面自定义的资源更新成用户想要的状态。



二、CRD的使用场景


1.提供、管理外部数据存储,因为Customer Resource就是一种存在Etcd中的数据结构,你可以将外部数据存储到Etcd中而不使用单独的数据库,用Kubernetes的声明式API去管理他的生命周期。

2.Kubernetes的基础资源进行更高层次的抽象,例如一些自定义控制器,你既可以管理自定义的资源状态,也可以改变Kubernetes原有资源,如Ingress-controller



三、Customer Controller工作机制


下面是官方的一张控制器的架构图,其中蓝色部分为client-go提供的框架,红色部分是我们需要自己实现扩展业务的逻辑。


我们先来介绍下蓝色组件

1.Clients:负责与APIServer交互的客户端

2.Informer:监控目标资源的变化

3.Workqueue:暂存资源变更事件的工作队列

我们详细介绍一下Informer,他依赖于KubernetesList/Watch API,是一个可监听事件并触发回调函数的二级缓存工具包。Informer主要包含ControllerReflectorDeltaFiFOLocalStoreListerProcessor六个组件,这个ControllerCustomer Controller没有任何关系,他的作用是管理Informer的生命周期,记录其缓存信息,controller的数据结构如下:

ReFlector的作用是通过KubernetesWatch API监听某个资源下的所有事件,DeltaFIFOLocalStoreInformer的二级缓存,DeltaFIFO用来存储Reflector Watch的各种事件,而LocalStore存储的是Reflector List的所有事件,Lister用来做ListGet操作,Processor中记录了所有的回调函数,也就是ResourceEventHandler。这个回调函数的触发就会调用上面架构图中红色部分CallBacksProcessor的结构如下:



不明白没关系,我们用一个Deployment的创建来串一下整个Informer的逻辑

Informer初始化的时候,Reflector组件会调用List API将所有Deployment资源存储到LocalStore中,当你创建一个新的Deployment的时候,Reflector会通过Watch API获取此次事件并将其放到DeltaFIFO队列中,然后查看LocalStore中是否存在这个deployment,如果没有DeltaFIFOpop这个事件到Controller中,Controller触发Processor的回调函数OnAdd。如果是Get一个已有的DeploymentLister会直接在LocalStoreGet而不是直接去访问APIServer,从而实现二级缓存的作用。

Workqueue就是处理资源变化事件的队列和重试机制,它包括延迟队列和限速队列,由上面得架构图可以看出来是回调函数ResourceEventHandler调用的WorkqueueReourceEventHandler会在Workqueue中添加一个以命名空间/资源构成的key,通过queue.add(key)的方式入队后,经过queue.Get()获取第一个Key进行处理,处理成功调用queue.Forget(key)清除key,并且调用queue.Done()彻底删除该事件,处理失败调用queue.AddRateLimited(key)重新入队。

obj, shutdown := c.workqueue.Get()
if shutdown {
  
return false
}
err :=
func(obj interface{}) error {
  
defer c.workqueue.Done(obj)
  
var key string
  
var ok bool
  
if key, ok = obj.(string); !ok {
      c.workqueue.Forget(obj)
      utilruntime.HandleError(fmt.Errorf(
"expected string in workqueue but got %#v", obj))
     
return nil
   }
  
if err := c.syncHandler(key); err != nil {
      c.workqueue.AddRateLimited(key)
     
return fmt.Errorf("error syncing '%s': %s, requeuing", key, err.Error())
   }
   c.workqueue.Forget(obj)
   klog.Infof(
"Successfully synced '%s'", key)
  
return nil

控制器会调用Worker去处理workqueue中的事件(即架构图中右侧的红色部分),上面代码中的scyncHandler(key)即为用户自定义的Worker,下面这个官方demo中给出的业务逻辑,它实现了创建一个deploment名为Foo中定义的deploymentNamedeployment的功能

func (c *Controller) syncHandler(key string) error {
  
// Convert the namespace/name string into a distinct namespace and name
  
namespace, name, err := cache.SplitMetaNamespaceKey(key)
  
if err != nil {
      utilruntime.HandleError(fmt.Errorf(
"invalid resource key: %s", key))
     
return nil
   }

   
// Get the Foo resource with this namespace/name
  
foo, err := c.foosLister.Foos(namespace).Get(name)
  
if err != nil {
     
// The Foo resource may no longer exist, in which case we stop
      processing.
     
if errors.IsNotFound(err) {
         utilruntime.HandleError(fmt.Errorf(
"foo '%s' in work queue no longer exists", key))
        
return nil
      }

     
return err
   }

   deploymentName := foo.Spec.DeploymentName
  
if deploymentName == "" {
     
// We choose to absorb the error here as the worker would requeue the
      resource otherwise. Instead, the next time the resource is updated
      the resource will be queued again.
     
utilruntime.HandleError(fmt.Errorf("%s: deployment name must be specified", key))
     
return nil
   }

  
// Get the deployment with the name specified in Foo.spec
  
deployment, err := c.deploymentsLister.Deployments(foo.Namespace).Get(deploymentName)
  
// If the resource doesn't exist, we'll create it
  
if errors.IsNotFound(err) {
      deployment
, err = c.kubeclientset.AppsV1().Deployments(foo.Namespace).Create(newDeployment(foo))
   }

  
// If an error occurs during Get/Create, we'll requeue the item so we can
   attempt processing again later. This could have been caused by a
   temporary network failure, or any other transient reason.
  
if err != nil {
     
return err
   }

  
// If the Deployment is not controlled by this Foo resource, we should log
   a warning to the event recorder and ret
  
if !metav1.IsControlledBy(deployment, foo) {
      msg := fmt.Sprintf(
MessageResourceExists, deployment.Name)
      c.recorder.Event(foo
, corev1.EventTypeWarning, ErrResourceExists, msg)
     
return fmt.Errorf(msg)
   }

  
// If this number of the replicas on the Foo resource is specified, and the
   number does not equal the current desired replicas on the Deployment, we
   should update the Deployment resource.
  
if foo.Spec.Replicas != nil && *foo.Spec.Replicas != *deployment.Spec.Replicas {
      klog.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))
   }

  
// If an error occurs during Update, we'll requeue the item so we can
   attempt processing again later. THis could have been caused by a
   temporary network failure, or any other transient reason.
  
if err != nil {
     
return err
   }

  
// Finally, we update the status block of the Foo resource to reflect the
   current state of the world
  
err = c.updateFooStatus(foo, deployment)
  
if err != nil {
     
return err
   }

   c.recorder.Event(foo
, corev1.EventTypeNormal, SuccessSynced, MessageResourceSynced)
  
return nil
}



四、CRD的配置和使用


官方提供了一个CRDdemo帮助我们更好地理解CRD

使用go get下载项目https://github.com/kubernetes/sample-controller

go build -o sample-controller .
./sample-controller
-kubeconfig=$HOME/.kube/config

2.定义一个资源类型

kubectl create -f crd.yaml

apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:name: foos.samplecontroller.k8s.io
spec:group: samplecontroller.k8s.io
version: v1alpha1
names:  kind: Foo
  plural: foos
scope: Namespaced

3.创建一个资源实例

kubectl create -f example.yaml

apiVersion: samplecontroller.k8s.io/v1alpha1
kind: Foo
metadata:name: example-foo
spec:deploymentName: example-foo
replicas: 1

kubectl get deploy会发现自动创建出来一个example-foodeployment




文章转载自深夜IT研究猿,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论