认识 kube-controller-manager

简单认识:

Kubernetes 控制器管理器是一个守护进程,内嵌随 Kubernetes 一起发布的核心控制回路。 在机器人和自动化的应用中,控制回路是一个永不休止的循环,用于调节系统状态。 在 Kubernetes 中,每个控制器是一个控制回路,通过 API 服务器监视集群的共享状态, 并尝试进行更改以将当前状态转为期望状态。 目前,Kubernetes 自带的控制器例子包括副本控制器、节点控制器、命名空间控制器和服务账号控制器等。

上述介绍来源官网:kube-controller-manager | Kubernetes

从上述的介绍中,可以理解到:

1、kube-controller-manager是一个控制器的集合。

2、里面包含常用的控制器:deployment、statefulset、daementset、endpoint、service、namespace、pod等控制器。其他控制器可以查阅:kubernetes github

3、当apiserver获取到kubectl发起的yaml文件请求时,会根据不同的kind类型执行相应的控制器,如下:

下面图片介绍:

  • 使用 kubectl get ns 获取 namespace 对象列表
  • 使用simple这个namespace对象,输出为yaml并且转存为ns.yaml
  • 查看刚生成的文件

kubespher controller起不来 kubernetes controller manager_容器

 接下来,我们把一些参数去掉,然后apply这个namespace对象

kubespher controller起不来 kubernetes controller manager_容器_02

 可以看到manager这个namespace对象已经创建完成。这就是控制器所做的事情。

图形介绍:

图片来自 极客时间 - 云原生训练营

kubespher controller起不来 kubernetes controller manager_kubernetes_03

图1

 如上图所示,流程如下:

  • api-server接收yaml文件的请求。
  • 保存初始状态到etcd。如果已经存在的对象则通过etcd的watcher机制进行监听。
  • controller manager 根据 yaml 文件里的 kind 执行对应的 controller 的事件(AddFunc-添加事件、DeleteFunc - 删除事件、UpdateFunc - 更新事件)
  • 将对象添加到事件队列,Controller弹出事件队列的对象,交给worker执行。
  • worker然后根据期望状态和当前状态进行判断,如果当前状态!=期望状态,则一直循环。

Controller认识:

在机器人技术和自动化领域,控制回路(Control Loop)是一个非终止回路,用于调节系统状态。

这是一个控制环的例子:房间里的温度自动调节器。

当你设置了温度,告诉了温度自动调节器你的期望状态(Desired State)。 房间的实际温度是当前状态(Current State)。 通过对设备的开关控制,温度自动调节器让其当前状态接近期望状态。

在 Kubernetes 中,控制器通过监控集群 的公共状态,并致力于将当前状态转变为期望的状态。

上述介绍来源官网:控制器 | Kubernetes

从上面介绍可以看出:

1、它接收apiserver接收到的yaml文件请求

kubectl apply -f pod.yaml

2、它是由一个期望状态,当前状态来维护,如果当前状态!=期望状态,则一直循环。

从控制面认识:

kubespher controller起不来 kubernetes controller manager_容器_04


可以看到kube-controller-manager是在主节点运行的(我这里只有两个节点,0-2-ubuntu是主节点)

代码走读: 

接下来,我们根据namespace这个控制器走读一下代码,其他控制器的逻辑差不多的。可以举一反三!其实我们到时候写CRD (CustomResourceSubresources)- Operator 也是类似的。

controller manager

func startNamespaceController(ctx context.Context, controllerContext ControllerContext) (controller.Interface, bool, error) {
	// the namespace cleanup controller is very chatty.  It makes lots of discovery calls and then it makes lots of delete calls
	// the ratelimiter negatively affects its speed.  Deleting 100 total items in a namespace (that's only a few of each resource
	// including events), takes ~10 seconds by default.
	nsKubeconfig := controllerContext.ClientBuilder.ConfigOrDie("namespace-controller")
	nsKubeconfig.QPS *= 20
	nsKubeconfig.Burst *= 100
	namespaceKubeClient := clientset.NewForConfigOrDie(nsKubeconfig)
	return startModifiedNamespaceController(ctx, controllerContext, namespaceKubeClient, nsKubeconfig)
}

func startModifiedNamespaceController(ctx context.Context, controllerContext ControllerContext, namespaceKubeClient clientset.Interface, nsKubeconfig *restclient.Config) (controller.Interface, bool, error) {

	metadataClient, err := metadata.NewForConfig(nsKubeconfig)
	if err != nil {
		return nil, true, err
	}

	discoverResourcesFn := namespaceKubeClient.Discovery().ServerPreferredNamespacedResources

	namespaceController := namespacecontroller.NewNamespaceController(
		namespaceKubeClient,
		metadataClient,
		discoverResourcesFn,
		controllerContext.InformerFactory.Core().V1().Namespaces(),
		controllerContext.ComponentConfig.NamespaceController.NamespaceSyncPeriod.Duration,
		v1.FinalizerKubernetes,
	)
	go namespaceController.Run(int(controllerContext.ComponentConfig.NamespaceController.ConcurrentNamespaceSyncs), ctx.Done())

	return nil, true, nil
}
  • startNamespaceController:namespace控制器关联着其他对象类似deployment、pod等对象,所以在删除或者的时候需要检查其他对象的引用,另外还有一个默认10秒的ratelimiter-请求控制。
  • startModifiedNamespaceController:开始修改namespace的时候,获取引用该namespace的对象列表

然后进入调用 NewNamespaceController

// NewNamespaceController creates a new NamespaceController
func NewNamespaceController(
	kubeClient clientset.Interface,
	metadataClient metadata.Interface,
	discoverResourcesFn func() ([]*metav1.APIResourceList, error),
	namespaceInformer coreinformers.NamespaceInformer,
	resyncPeriod time.Duration,
	finalizerToken v1.FinalizerName) *NamespaceController {

	// create the controller so we can inject the enqueue function
	namespaceController := &NamespaceController{
		queue:                      workqueue.NewNamedRateLimitingQueue(nsControllerRateLimiter(), "namespace"),
		namespacedResourcesDeleter: deletion.NewNamespacedResourcesDeleter(kubeClient.CoreV1().Namespaces(), metadataClient, kubeClient.CoreV1(), discoverResourcesFn, finalizerToken),
	}

	if kubeClient != nil && kubeClient.CoreV1().RESTClient().GetRateLimiter() != nil {
		ratelimiter.RegisterMetricAndTrackRateLimiterUsage("namespace_controller", kubeClient.CoreV1().RESTClient().GetRateLimiter())
	}

	// configure the namespace informer event handlers
	namespaceInformer.Informer().AddEventHandlerWithResyncPeriod(
		cache.ResourceEventHandlerFuncs{
			AddFunc: func(obj interface{}) {
				namespace := obj.(*v1.Namespace)
				namespaceController.enqueueNamespace(namespace)
			},
			UpdateFunc: func(oldObj, newObj interface{}) {
				namespace := newObj.(*v1.Namespace)
				namespaceController.enqueueNamespace(namespace)
			},
		},
		resyncPeriod,
	)
	namespaceController.lister = namespaceInformer.Lister()
	namespaceController.listerSynced = namespaceInformer.Informer().HasSynced

	return namespaceController
}

AddEventHandlerWithResyncPeriod 把添加事件和更新时间注入到 informer,informer的流程就如上面的图1。

然后我们看一下worker:

它从队列里面取出对象然后一个个执行。

// worker processes the queue of namespace objects.
// Each namespace can be in the queue at most once.
// The system ensures that no two workers can process
// the same namespace at the same time.
func (nm *NamespaceController) worker() {
	workFunc := func() bool {
		key, quit := nm.queue.Get()
		if quit {
			return true
		}
		defer nm.queue.Done(key)

		err := nm.syncNamespaceFromKey(key.(string))
		if err == nil {
			// no error, forget this entry and return
			nm.queue.Forget(key)
			return false
		}

		if estimate, ok := err.(*deletion.ResourcesRemainingError); ok {
			t := estimate.Estimate/2 + 1
			klog.V(4).Infof("Content remaining in namespace %s, waiting %d seconds", key, t)
			nm.queue.AddAfter(key, time.Duration(t)*time.Second)
		} else {
			// rather than wait for a full resync, re-add the namespace to the queue to be processed
			nm.queue.AddRateLimited(key)
			utilruntime.HandleError(fmt.Errorf("deletion of namespace %v failed: %v", key, err))
		}
		return false
	}

	for {
		quit := workFunc()

		if quit {
			return
		}
	}
}

// Run starts observing the system with the specified number of workers.
func (nm *NamespaceController) Run(workers int, stopCh <-chan struct{}) {
	defer utilruntime.HandleCrash()
	defer nm.queue.ShutDown()

	klog.Infof("Starting namespace controller")
	defer klog.Infof("Shutting down namespace controller")

	if !cache.WaitForNamedCacheSync("namespace", stopCh, nm.listerSynced) {
		return
	}

	klog.V(5).Info("Starting workers of namespace controller")
	for i := 0; i < workers; i++ {
		go wait.Until(nm.worker, time.Second, stopCh)
	}
	<-stopCh
}

常用Controller介绍:

Deployment

  • 首先是GVK属性,即apiVersion,kind,metadata。
  • 适合放无状态应用。
  • 里面包含 replicaset (副本集),replicaset控制着pod的对象个数。
  • spec里面的template属性写的就是pod的属性。
  • selector其实就是label selector,选择run=my-nginx的pod对象。

yaml实例如下:

apiVersion: apps/v1
kind: Deployment
metadata:
 name: my-nginx
spec:
 selector:
   matchLabels:
     run: my-nginx
 replicas: 2
 template:
  metadata:
   labels:
    run: my-nginx
  spec:
   containers:
   - name: my-nginx
     image: daocloud.io/library/nginx:1.7.9
     ports:
     - containerPort: 80

apply之后的效果就是 Deployment -> replicaset -> pod。

kubespher controller起不来 kubernetes controller manager_容器_05

kubespher controller起不来 kubernetes controller manager_容器_06

这时候,我们在想,如果删除一个deployment,会不会把deployment下面的replicaset和pod删除掉?

带着这个问题我们再探索一下: 

我们分别edit一下这三个刚刚介绍的对象,会发现,他们中间有个ownerReferences。

kubectl edit deployment my-nginx -oyaml

kubectl edit rs my-nginx-5fdc96f9b4 -oyaml

kubectl edit pod my-nginx-5fdc96f9b4-8j7ph -oyaml

 deployment的对象如下图:

kubespher controller起不来 kubernetes controller manager_控制回路_07

 replicaset的对象如下图:

 

pod对象如下图:

kubespher controller起不来 kubernetes controller manager_容器_08

 可以发现replicaset里面的ownerReferences对象的uid、kind、name 对应的都是上层deployment的信息。

pod对象的uid、kind、name 对应的都是上层replicaset的信息。

  • 当删除deployment的时侯,会检查有没replicaset的ownerReferences指向当前对象。
  • 如果有,会把replicaset也删除,replicaset也会检查有没pod的ownerReferences指向当前对象。
  • 如果有,会把pod也删除。

这得益于Kubernetes的garbage collector,也可以在contoller manager里面找到它,代码我就不讲了。

kubespher controller起不来 kubernetes controller manager_kubelet_09

Kubernetes garbage collector即垃圾收集器,存在于kube-controller-manger中,它负责回收kubernetes中的资源对象,监听资源对象事件,更新对象之间的依赖关系,并根据对象的删除策略来决定是否删除其关联对象。

关于删除关联对象,细一点说就是,使用级联删除策略去删除一个owner时,会连带这个owner对象的dependent对象也一起删除掉。

关于对象的关联依赖关系,garbage collector会监听资源对象事件,根据资源对象中ownerReference的值,来构建对象间的关联依赖关系,也即owner与dependent之间的关系。

Statefulset

  • 适合放有状态应用,类似数据库。
  • 逻辑跟deployment类似,也有replicaset和pod。
  • 但是它是放有状态应用,最大的区别就是创建的时候,它的名字会按照顺序创建(从0开始),而deployment的名字则是hash值。

Daementset

Daementset是在每个node节点机器上都会运行的pod。

类似calico这种需要每个node节点执行的CNI,它必须用 Daementset 先作为网络底座。

kubespher controller起不来 kubernetes controller manager_nginx_10

 

kubespher controller起不来 kubernetes controller manager_控制回路_11

Endpoint

Endpoint对象是指向Pod的IP,作为一个接入点,方便给service对象使用

kubespher controller起不来 kubernetes controller manager_kubernetes_12

可能你会疑问,为什么我的my-nginx的容器数量都是2/2,其实是加了Istio-inject。这里后面再介绍。

Service

Service 后面会起一个文章介绍,这里简单看一下效果图。

当service对象创建后,也会创建一个同名的endpoint对象

kubespher controller起不来 kubernetes controller manager_nginx_13