3. controller-manager
controller-manager
是整个k8s
的大脑,
其由kube-conrtoller-manager
和cloud-controller-manager
组成。其中clound-controller-manager
用来配合云服务提供商的控制,因此本章将不对其做详细描述。controller-manager
中包含众多控制器见附件^1,通过这些控制器来完成集群管理的作用。监控集群状态,并确保集群处于预期的状态。
本节,将从如下几个方面来讲解controller-manager
的工作原理:
- 启动过程
- 单次部署的执行过程
- pod自动横向伸缩工作原理
3.1 启动过程
controller-manager
启动入口cmd/kube-controller-manager/controller-manager.go
,与apiserver
一样其也是基于CLI
框架Cobra
来实现。
其初始化过程如下:
- 首先创建一个带有默认参数的
KubeControllerManagerOption
,用于构建控制器管理器;- 其中带有各控制器创建的配置参数
- 通过
cobra
将输入参数应用到KubeControllerManagerOption
上,运行前,会先对参数进行校验; - 跟据
KubeControllerManagerOptions
构建创建众多控制器的配置参数kubecontrollerconfig.Config
- 跟据输入参数构建与
apiserver
交互的client
; - 创建用于选主的通信客户端
leaderElectionClient
; - 创建一个事件记录器,用于记录事件并将事件以
v1.Event
发送给apiserver
; - 将
KubeControllerManagerOptions
中的配置应用到kubecontrollerconfig.Config
。
- 跟据输入参数构建与
- 开启运行
控制器
管理器。- 为
controller-manager
创建两个http服务,分别用来做健康检查、配置查询; - 若配置需要选主,则先选主(没有那么复杂, 只是先到先得的原则)。若选主成功则开始初始化其内部组件,众多控制器;
- 运行组件
- 创建控制器上下文
ControllerContext
,实际是提前构建一些通用的组件- 分别创建两个
Informer
工厂,shared-informers
以及metadata-informers
; - 等待
apiserver
就绪,最多等待10s
; - 查询
apiserver
中所有可用的资源; - 创建云提供商工具
- 跟据控制器上下文来开启控制器
- 开启
ServiceAccount
控制器; app/controllermanager.go.NewControllerInitializer
方法中保存了所有控制器的初始化函数,遍历并执行所有控制器的初始化函数,开启这些控制器的执行。比如DeploymentController
、ReplicaSetController
等;
- 开始两个
Informer
工厂,开始同步并监听资源更新。
- 分别创建两个
- 创建控制器上下文
- 为
到此就完成了controller-manager
的初始化过程。
下面我们以DeploymentController
来讲解其初始化详细过程,DeploymentController
的初始化函数是startDeploymentController
:
1 | dc, err := deployment.NewDeploymentController( |
可以看到,其关注监听Deployment
、ReplicaSets
和Pod
资源。再继续查看DeploymentController
的创建过程,其关注这三种资源,并且添加事件监听器,当Deployment
资源更新时回调addDeplotment\updateDeployment\deleteDeployment
方法,ReplicaSets
和Pod
的更新也会回调DeploymentController
的相应方法。
1 | dc := &DeploymentController{ |
创建完成后,调用Run
方法开始运行DeploymentController
,当然运行前需要这三种资源都从apiserver
同步到其本地,然后开启多个协程执行worker
逻辑。worker
从Controller
自身的queue
中阻塞式等待数据,然后执行syncDeploymet
方法,这里则是其的核心处理逻辑。
3.2 pod单次部署执行过程
单次执行Deployment
进行部署时,其经历主过程如下:DeploymenetController
接收创建Deployment
的请求,进行解析创建ReplicaSet
资源,而后ReplicaSetController
接收到创建ReplicaSet
的请求则创建响应的Pod
资源信息。
除了简单部署之外,各控制器组件同时需要保障部署运行实情与期望
一致。以下就是部署过程的简要流程图:
下面将详细介绍其执行流程:
kubectl
执行create deployment.yml
命令后,apiserver
接收请求,并将其保存到etcd
中;- 由于
controller-manager
中的DeploymentController
,所以变更会推送到DeploymentController
的deploymentInformer
中,而deploymentInformer
则会回调DeploymentController.addDeployment
方法; addDeployment
方法并不会立即处理Deployment
,而是将该Deployment
生成的唯一key 入队列queue
(该队列支持限流);DeploymentController.processBexrWorkItem
会阻塞等待队列中的资源更新(系统可以启动多个工作协程同时监听该队列,以提升吞吐),最后会调用核心方法syncDeployment
进行处理;DeploymentController.syncDeployment
方法不仅处理初次部署的情况,同时也负责更新以及由其发起的部署集群发生变化的处理逻辑:- 通过
key
获取发生变更的Deployment
; - 获取该
Deployment
创建的ReplicaSet
,这里因为是新创建的Deployment
所以查询不到ReplicaSet
; - 获取该
Deployment
间接创建的所有Pod
- 若
Deployment
的属性DeletionTimestamp
不为空时表示该Deployment
被删除,则只同步状态; - 检查
Deployment
是否被暂停或者恢复,并设置相应的状态,若被暂停则进一步进行清理; - 若该
Deployment
是进行回滚操作,则执行rollback
回滚相应逻辑; - 检查
Deployment
是否需要进行扩缩容(通过检查其对应的ReplicaSet
所有的分片数与理想状态是否一致来判断) - 若是扩缩容事件,则执行同步方法
sync
- 若不是,则是初次创建,部署时有两种策略:Recreate、RollingUpdate(针对升级的情况,默认是RollingUpdate),这里我们就以
RollingUpdate
为例来查看其执行过程(DeploymentController.rolloutRolling
):- 首先会获取所有新老
ReplicaSet
。当不存在ReplicaSet
时,则跟据Deployment
属性创建一个ReplicaSet
,并发送到apiserver
(DeploymentController.getNewReplicaSet
); - 更新
Deployment
的状态
- 首先会获取所有新老
- 通过
ReplicaSetController
监听ReplicaSet
的更新,上面DeploymentController
创建ReplicaSet
后,ReplicaSetController
会受到通知(与DeploymentController
执行方式一样,也通过queue
和多个worker
来处理更新)并最终调用ReplicaSetController.syncReplicaSet
方法:- 获取同一命名空间内的所有存活的
Pod
; - 并通过
ReplicaSet
的Selector
筛选出由其创建的Pod
; manageReplicas
方法管理ReplicaSet
,计算已经创建的Pod
数和ReplicaSet
的预期的Pod
差值- 当
Pod
数不足时,则依次跟据ReplicaSet
的配置PodTemplateSepc
创建Pod
,同时设置上Pod
的OwnerRefernce
(用于gc
); - 当
Pod
数过多时,则并发删除多余的Pod
。
- 当
- 更新
ReplicaSet
的状态。
- 获取同一命名空间内的所有存活的
到此就完成了Pod
的创建,但创建的Pod
还不能工作,需要被调度到某个Node
上才能运行。至于应该被调度到哪个节点上,这就涉及到调度器的实现逻辑,将在下节scheduler
中介绍。
3.3 pod自动横向伸缩
k8s
中负责pod
自动横向伸缩的控制器叫HorizontalController
。首先看其控制器初始化:
1 | func startHPAController(ctx ControllerContext) (http.Handler, bool, error) { |
HorizontalController
的初始化过程流程主要做了以下几件事:
- 创建
Metrics
查询客户端; - 创建分片计算器;
- 创建
hpa
资源informer
,监听该资源变更并回调相应的函数enqueueHPA
、updateHPA
、deleteHPA
下面将来详细介绍其运行原理:
通过kubectl
执行autoscale
命令kubectl autoscale deployment nginx-deployment --cpu-percent=30 --min=7 --max=8
后,HorizontalController会接收到HorizontalPodAutoscaler
新增消息,最终会执行HorizontalController.reconcileAutoscaler
方法。该方法是控制器的核心逻辑,其执行过程如下:
- 通过
hpa
的属性获取对应资源(可以是Deployment
也可以是其他)的Scale
中的分片数信息; - 进行逻辑判断:
- 若当前分片数为0但最小分片数不为0,则不进行自动扩容;
- 若当前分片数大于
hpa
定义的最大分片数则将目标分片数(desiredReplicas
)设置成最大分片数; - 若当前分片数小于
hpa
定义的最小分片数则将目标分片数设置成该最小分片数; - 都不满足的话则执行第三步跟据
Metrics
信息计算需要部署的分片数
- 执行
HorizontalController.computeReplicasForMetrics
方法计算目标分片数:- 定义
hpa
时可以定义多个指标维度的扩缩容策略,比如cpu
等。因此这里会按每个指标信息依次计算目标分片数,最后取最大值作为最终的目标值; - 对于指标(
MetricSpec
),k8s
对齐进行了分类,主要分为四类,没类的计算方式也不同:- Object: 描述
k8s
对象,如hits-per-second
; - Pods: 描述目标中每个
Pod
信息,如transactions-processed-per-second
,而这些值在比较前会进行求平均; - Resource:是
k8s
中一个知名度量信息,在request
和limit
中进行定义的资源; - External:拓展指标信息,来自于
k8s
集群之外的信息。- 下面以
Resource
为例,来讲解其计算过程(HorizontalController.computeStatusForResourceMetric
):
- 下面以
- 最终调用的是
ReplicaCalculator
分片计算器的GetRawResourceReplicas
方法; - 首先,通过
metricsClient
查询每个Pod
的Metrics
信息; - 进行以下条件判断和计算目标分片数
- 首先计算当前测量的
metric
的平均值和目标值的比例(usageRatio
); - 若没有未就绪的
pod
且当前指标大于目标值时:- 当差值在10%(默认
可容忍值
)之内,则直接返回原值; - 若大于10%时,则返回分片数:
usageRatio
乘以当前Pod
数;
- 当差值在10%(默认
- 如有
Pod
未收集到指标信息- 当
usageRatio
小于0,将未收集到的指标设置零时为目标值
; - 若
usageRatio
大于0,将未收集的指标设置零时为0
;- 当
usageRatio
大于0,将所有未就绪的Pod
指标设置为0
- 对修改过的数据重新计算
usageRatio
; - 当新的
usageRatio<1.1
(0.1是容忍度)或者oldUageRatio<1&&newUsageRatio>1
或者oldUageRatio>1&&newUsageRatio<1
时,直接返回原值; - 否则返回
newUsageRatio
乘以所有pod
数量(也及以newUsageRatio
比例来扩容)
- 当
- 当
- 首先计算当前测量的
- 并不是计算出来就用这个值,还需要对伸缩速率做一次调整(
normalizeDesiredReplicas
):- 根据历史值来调整该值(
stabilizeRecommendation
):HPA
中保存每次计算结果和时间戳,timestampedRecommendation
;HPA
启动时会设置一个窗口期downscaleStabilisationWindow
(默认5min
)- 找到窗口期之内历史计算结果,若历史推荐分片数比当前推荐的大,则覆盖当前的
- 速率限制(
convertDesiredReplicasWithRules
)- 防止扩容过快,限制最大不能超过当前实例数的两倍;
- 获取目标分片数时,若与原值不相等则通过
Scales
接口将分片数修改成目标值(实际,Scales
只是一个包装,其实质是更新Deployment
的Replica
只) - 当
DeploymentController
接收到更新后,就会引发其扩缩容操作,上一小节中已经介绍。
到此,HPA
的单次操作执行流程就介绍完了,下面以一张图来简要描述该过程:
- 获取目标分片数时,若与原值不相等则通过
- 防止扩容过快,限制最大不能超过当前实例数的两倍;
- 根据历史值来调整该值(
- Object: 描述
- 定义
附件
1. 控制器列表
1 | controllers := map[string]InitFunc{} |