Kubernetes 控制器管理总结
2024-01-07 Cloud Native Kubernetes 读代码
控制器管理逻辑
Kubernetes 中,控制器通过监控集群的资源状态,保证资源的当前状态向期望值收敛,单个控制器至少管理一种类型资源。
如果单从逻辑上划分,每个控制器应该是一个独立的进程,但是为了降低管理复杂度和代码的实现难度,Kubernetes 中所有的控制器会在同一个进程中运行。 当然,不同的控制器主体执行逻辑位于单独的 goroutine 中,每个控制器的内部可以被简化为一个无限循环,不断执行自身相关的控制逻辑,使系统资源收敛于期望状态。
控制器之间不会直接通信,它们甚至不知道其他控制器的存在,每个控制器内部的处理逻辑都是独立且类似的: 通过订阅 Informer 资源事件,并根据事件来执行对应的资源操作。
通过 API Server 控制
需要注意的是: 所有控制器并不会主动创建并运行资源, 而是通过创建对应的资源 声明并发送到 API Server, 最后由控制面板其余组件配合完成剩下的操作, 例如 Deployment 控制器创建 Pod 时,由调度器分配 Node 并由 Node 上面的 kubelet 创建并运行指定的 Pod。
下面以经常使用的任务控制器 JobController 为例来进行说明。
Job 是集群中用于运行任务的资源,它通过一个或多个 Pod 来执行一个具体的任务,当 JobController 获取到新的运行任务时,保证一组节点上面的 kubelet 可以运行正确的 Pod 来完成任务。 JobController 自身不会去运行 Pod 或者容器,而是通过请求告知 API Server 具体的操作 (例如创建或删除 Pod), 当 API Server 执行完对应的请求操作后会返回消息, 控制面板中的其他组件会根据返回消息做出对应操作。
Controller Manager
顾名思义,Controller Manager 的角色就是作为一个大管家,管理集群中的各类控制器运行。
初始化
在控制器管理器执行 初始化操作 时,可以看到 Kubernetes 中常见的控制器全部会被注册:
func NewControllerInitializers(loopMode ControllerLoopMode) map[string]InitFunc {
// 注册表结构
// 控制器名称 => 启动方法
controllers := map[string]InitFunc{}
// 注册方法会逐个注册控制器
// 注册时需要保证每个控制器的名称是唯一的
// 如果出现重复的名称,直接 panic
register := func(name string, fn InitFunc) {
if _, found := controllers[name]; found {
panic(fmt.Sprintf("controller name %q was registered twice", name))
}
controllers[name] = fn
}
// 注册各种控制器
register(names.EndpointsController, startEndpointController)
register(names.DaemonSetController, startDaemonSetController)
register(names.JobController, startJobController)
register(names.DeploymentController, startDeploymentController)
register(names.HorizontalPodAutoscalerController, startHPAController)
...
return controllers
}
Informer 存在的必然性
如果从交互流程简单来看,控制器完全可以绕开 Informer, 直接和 API Server 进行通信获取资源的变更情况,然后根据结果执行对应的操作, 但是这种通信模式下为了保证即时获取资源变化情况,控制器的访问方式必须是类似轮询方式,那么所有控制器都采用这个方式的话,API Server 将会面对极大的负载压力, 因此通过使用 Informer 提供的 List & Watch 方式缓存增量事件数据,可以非常有效地降低 API Server 等控制面板相关组件的压力。
此外,通过增加一个 Informer “中间缓存层”,开发者只需要订阅 Informer 事件本身,然后专注于处理控制器的具体业务逻辑,而无需考虑事件数据源访问和并发等问题。
执行框架和流程
API Server 的工作只是负责存储资源对象到 etcd, 并将资源对象的变化以事件形式通知到 Informer,控制器初始化后启动时会从 Informer 订阅相关的事件, 根据事件创建对应的资源声明并发送到 API Server, 最后由控制面板其余组件配合完成剩下的操作。
每个控制器内部的处理逻辑都是独立且类似的: 通过订阅 Informer 资源事件, 注册资源对应的 Add/Update/Delete 等事件回调方法. 资源事件会被放入控制器关联的具体的队列中,然后控制器会开启多个 goroutine 从队列中获取资源事件并消费 (执行相关的操作),最后将期望状态声明回填到 API Server。