认识 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
- 查看刚生成的文件
接下来,我们把一些参数去掉,然后apply这个namespace对象
可以看到manager这个namespace对象已经创建完成。这就是控制器所做的事情。
图形介绍:
图片来自 极客时间 - 云原生训练营
图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、它是由一个期望状态,当前状态来维护,如果当前状态!=期望状态,则一直循环。
从控制面认识:
可以看到kube-controller-manager是在主节点运行的(我这里只有两个节点,0-2-ubuntu是主节点)
代码走读:
接下来,我们根据namespace这个控制器走读一下代码,其他控制器的逻辑差不多的。可以举一反三!其实我们到时候写CRD (CustomResourceSubresources)- Operator 也是类似的。
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。
这时候,我们在想,如果删除一个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的对象如下图:
replicaset的对象如下图:
pod对象如下图:
可以发现replicaset里面的ownerReferences对象的uid、kind、name 对应的都是上层deployment的信息。
pod对象的uid、kind、name 对应的都是上层replicaset的信息。
- 当删除deployment的时侯,会检查有没replicaset的ownerReferences指向当前对象。
- 如果有,会把replicaset也删除,replicaset也会检查有没pod的ownerReferences指向当前对象。
- 如果有,会把pod也删除。
这得益于Kubernetes的garbage collector,也可以在contoller manager里面找到它,代码我就不讲了。
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 先作为网络底座。
Endpoint
Endpoint对象是指向Pod的IP,作为一个接入点,方便给service对象使用
可能你会疑问,为什么我的my-nginx的容器数量都是2/2,其实是加了Istio-inject。这里后面再介绍。
Service
Service 后面会起一个文章介绍,这里简单看一下效果图。
当service对象创建后,也会创建一个同名的endpoint对象