tags:
categories:
Third Party Resource
的概念,允许开发者根据业务需求以插件化形式扩展出相应的 K8s API 对象模型,同时提出了自定义 controller 的概念用于编写面向领域知识的业务控制逻辑,基于 Third Party Resource,Kubernetes 社区在 1.7 版本中提出了custom resources and controllers
的概念,(CRD+控制器)这正是** Operator 的核心概念**。etcd-operator
。它可以让用户通过短短的几条命令就快速的部署一个 etcd 集群,并且基于 kubectl 命令行一个普通的开发者就可以实现 etcd 集群滚动更新、灾备、备份恢复等复杂的运维操作,极大的降低了 etcd 集群的使用门槛,在很短的时间就成为当时 K8s 社区关注的焦点项目。Custom Resource Definition
(简称 CRD)这个如今已经广为人知的资源模型范式代替。controller-runtime
项目实现的 Controller 逻辑,不同的是 CRD 资源的创建过程。# 下载 kubebuilder 并解压
wget https://github.com/kubernetes-sigs/kubebuilder/releases/download/v2.3.1/kubebuilder_2.3.1_linux_amd64.tar.gz
# 将 kubebuilder 移动 PATH 路径中
sudo mv kubebuilder /usr/local/bin/kubebuilder
# 查看版本
kubebuilder version
kubebuilder init
命令,初始化一个新项目。示例如下。mkdir gitee.com/qnk8s/builder-demo
cd gitee.com/qnk8s/builder-demo
# 开启 go modules
export GO111MODULE=on
export GOPROXY=https://goproxy.cn
# 初始化项目 domian公司域名 owner作者 repo git地址
kubebuilder init --domain ydzs.io --owner qnhyn --repo gitee.com/qnk8s/builder-demo
# 创建CRD webapp/v1 Book
# y y
kubebuilder create api --group webapp --version v1 --kind Book
api/v1/guestbook_types.go
,该文件中定义相关 API ,而针对于这一类型 (CRD) 的业务逻辑生成在 controller/guestbook_controller.go
文件中。api/v1/guestbook_types.go
文件:** 修改完成后,make重新生成代码**。// 修改结构体字段
type BookSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
// Foo is an example field of Book. Edit Book_types.go to remove/update
Price int32 `json:"price"`
Title string `json:"title"`
}
controllers/book_controller.go
下Reconcile写一些业务逻辑。这里只是日志输出测试流程。func (r *BookReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
_ = context.Background()
log := r.Log.WithValues("book", req.NamespacedName)
// your logic here
log.Info("book reconciling")
return ctrl.Result{}, nil
}
config/samples/webapp_v1_book.yaml
修改,想集群中注册CRD。apiVersion: webapp.ydzs.io/v1
kind: Book
metadata:
name: book-sample
spec:
# Add fields here
price: 100
title: kubernetes
# 安装CRD到本地集群
make install
kubectl get crd
# make run执行go run main.go控制器安装在集群中
make run
config/samples/webapp_v1_book.yaml
查看日志输出。kubectl apply -f webapp_v1_book.yaml
kubectl delete -f webapp_v1_book.yaml
# docker-build 构建镜像
# docker-push 上传到镜像仓库
make docker-build docker-push IMG=<some-registry>/<project-name>:tag
# deploy 部署到k8s的pod中
make deploy IMG=<some-registry>/<project-name>:tag
# uninstall 删除CRD
make uninstall
// pkg/internal/controller/controller.go
type Controller struct {
// Name 用于跟踪、记录和监控中控制器的唯一标识,必填字段
Name string
// 可以运行的最大并发 Reconciles 数量,默认值为1
MaxConcurrentReconciles int
// Reconciler 是一个可以随时调用对象的 Name/Namespace 的函数
// 确保系统的状态与对象中指定的状态一致,默认为 DefaultReconcileFunc 函数
Do reconcile.Reconciler
// 一旦控制器准备好启动,MakeQueue 就会为这个控制器构造工作队列
MakeQueue func() workqueue.RateLimitingInterface
// 队列通过监听来自 Infomer 的事件,添加对象键到队列中进行处理
// MakeQueue 属性就是来构造这个工作队列的
// 也就是前面我们讲解的工作队列,我们将通过这个工作队列来进行消费
Queue workqueue.RateLimitingInterface
// SetFields 用来将依赖关系注入到其他对象,比如 Sources、EventHandlers 以及 Predicates
SetFields func(i interface{}) error
// 控制器同步信号量
mu sync.Mutex
// 允许测试减少 JitterPeriod,使其更快完成
JitterPeriod time.Duration
// 控制器是否已经启动
Started bool
// TODO(community): Consider initializing a logger with the Controller Name as the tag
// startWatches 维护了一个 sources、handlers 以及 predicates 列表以方便在控制器启动的时候启动
startWatches []watchDescription
// 日志记录
Log logr.Logger
}
// pkg/internal/controller/controller.go
// watchDescription 包含所有启动 watch 操作所需的信息
type watchDescription struct {
src source.Source
handler handler.EventHandler
predicates []predicate.Predicate
}
// pkg/internal/controller/controller.go
func (c *Controller) Watch(src source.Source, evthdler handler.EventHandler, prct ...predicate.Predicate) error {
c.mu.Lock()
defer c.mu.Unlock()
// 注入 Cache 到参数中
if err := c.SetFields(src); err != nil {
return err
}
if err := c.SetFields(evthdler); err != nil {
return err
}
for _, pr := range prct {
if err := c.SetFields(pr); err != nil {
return err
}
}
// Controller 还没启动,把 watches 存放到本地然后返回
//
// 这些 watches 会被保存到控制器结构体中,直到调用 Start(...) 函数
if !c.Started {
c.startWatches = append(c.startWatches, watchDescription{src: src, handler: evthdler, predicates: prct})
return nil
}
c.Log.Info("Starting EventSource", "source", src)
// 调用 src 的 Start 函数
return src.Start(evthdler, c.Queue, prct...)
}
event.EventHandlers
将 reconcile.Requests
入队列进行处理。
// pkg/source/source.go
type Source interface {
// Start 是一个内部函数
// 只应该由 Controller 调用,向 Informer 注册一个 EventHandler
// 将 reconcile.Request 放入队列
Start(handler.EventHandler, workqueue.RateLimitingInterface, ...predicate.Predicate) error
}
source.Source
是一个接口,它是 Controller.Watch
的一个参数,所以要看具体的如何实现的 Source.Start 函数,我们需要去看传入 Controller.Watch
的参数,在 controller-runtime 中调用控制器的 Watch 函数的入口实际上位于 pkg/builder/controller.go
文件中的 doWatch()
函数:// pkg/builder/controller.go
func (blder *Builder) doWatch() error {
// Reconcile type
src := &source.Kind{Type: blder.forInput.object}
hdler := &handler.EnqueueRequestForObject{}
allPredicates := append(blder.globalPredicates, blder.forInput.predicates...)
err := blder.ctrl.Watch(src, hdler, allPredicates...)
if err != nil {
return err
}
......
return nil
}
source.Kind
的类型,该结构体就实现了上面的 source.Source
接口:// pkg/source/source.go
// Kind 用于提供来自集群内部的事件源,这些事件来自于 Watches(例如 Pod Create 事件)
type Kind struct {
// Type 是 watch 对象的类型,比如 &v1.Pod{}
Type runtime.Object
// cache 用于 watch 的 APIs 接口
cache cache.Cache
}
// 真正的 Start 函数实现
func (ks *Kind) Start(handler handler.EventHandler, queue workqueue.RateLimitingInterface,
prct ...predicate.Predicate) error {
// Type 在使用之前必须提前指定
if ks.Type == nil {
return fmt.Errorf("must specify Kind.Type")
}
// cache 也应该在调用 Start 之前被注入了
if ks.cache == nil {
return fmt.Errorf("must call CacheInto on Kind before calling Start")
}
// 从 Cache 中获取 Informer
// 并添加一个事件处理程序来添加队列
i, err := ks.cache.GetInformer(context.TODO(), ks.Type)
if err != nil {
if kindMatchErr, ok := err.(*meta.NoKindMatchError); ok {
log.Error(err, "if kind is a CRD, it should be installed before calling Start",
"kind", kindMatchErr.GroupKind)
}
return err
}
i.AddEventHandler(internal.EventHandler{Queue: queue, EventHandler: handler, Predicates: prct})
return nil
}
Controller.Watch
函数就是实现的获取资源对象的 Informer 以及注册事件监听函数。Informer 是通过 cache 获取的,cache 是在调用 Start 函数之前注入进来的,这里其实我们不用太关心;下面的 AddEventHandler 函数中是一个 internal.EventHandler
结构体,那这个结构体比如会实现 client-go 中提供的 ResourceEventHandler
接口,也就是我们熟悉的 OnAdd、OnUpdate、OnDelete 几个函数:// pkg/source/internal/eventsource.go
// EventHandler 实现了 cache.ResourceEventHandler 接口
type EventHandler struct {
EventHandler handler.EventHandler
Queue workqueue.RateLimitingInterface
Predicates []predicate.Predicate
}
func (e EventHandler) OnAdd(obj interface{}) {
// kubernetes 对象被创建的事件
c := event.CreateEvent{}
// 获取对象 metav1.Object
if o, err := meta.Accessor(obj); err == nil {
c.Meta = o
} else {
log.Error(err, "OnAdd missing Meta",
"object", obj, "type", fmt.Sprintf("%T", obj))
return
}
// 断言 runtime.Object
if o, ok := obj.(runtime.Object); ok {
c.Object = o
} else {
log.Error(nil, "OnAdd missing runtime.Object",
"object", obj, "type", fmt.Sprintf("%T", obj))
return
}
// Predicates 用于事件过滤,循环调用 Predicates 的 Create 函数
for _, p := range e.Predicates {
if !p.Create(c) {
return
}
}
// 调用 EventHander 的 Create 函数
e.EventHandler.Create(c, e.Queue)
}
func (e EventHandler) OnUpdate(oldObj, newObj interface{}) {
u := event.UpdateEvent{}
// Pull metav1.Object out of the object
if o, err := meta.Accessor(oldObj); err == nil {
u.MetaOld = o
} else {
log.Error(err, "OnUpdate missing MetaOld",
"object", oldObj, "type", fmt.Sprintf("%T", oldObj))
return
}
// Pull the runtime.Object out of the object
if o, ok := oldObj.(runtime.Object); ok {
u.ObjectOld = o
} else {
log.Error(nil, "OnUpdate missing ObjectOld",
"object", oldObj, "type", fmt.Sprintf("%T", oldObj))
return
}
// Pull metav1.Object out of the object
if o, err := meta.Accessor(newObj); err == nil {
u.MetaNew = o
} else {
log.Error(err, "OnUpdate missing MetaNew",
"object", newObj, "type", fmt.Sprintf("%T", newObj))
return
}
// Pull the runtime.Object out of the object
if o, ok := newObj.(runtime.Object); ok {
u.ObjectNew = o
} else {
log.Error(nil, "OnUpdate missing ObjectNew",
"object", oldObj, "type", fmt.Sprintf("%T", oldObj))
return
}
for _, p := range e.Predicates {
if !p.Update(u) {
return
}
}
// 调用 EventHandler 的 Update 函数
e.EventHandler.Update(u, e.Queue)
}
func (e EventHandler) OnDelete(obj interface{}) {
d := event.DeleteEvent{}
// Deal with tombstone events by pulling the object out. Tombstone events wrap the object in a
// DeleteFinalStateUnknown struct, so the object needs to be pulled out.
// Copied from sample-controller
// This should never happen if we aren't missing events, which we have concluded that we are not
// and made decisions off of this belief. Maybe this shouldn't be here?
var ok bool
if _, ok = obj.(metav1.Object); !ok {
// 假设对象没有 Metadata,假设是一个 DeletedFinalStateUnknown 类型的对象
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok {
log.Error(nil, "Error decoding objects. Expected cache.DeletedFinalStateUnknown",
"type", fmt.Sprintf("%T", obj),
"object", obj)
return
}
// Set obj to the tombstone obj
obj = tombstone.Obj
}
// Pull metav1.Object out of the object
if o, err := meta.Accessor(obj); err == nil {
d.Meta = o
} else {
log.Error(err, "OnDelete missing Meta",
"object", obj, "type", fmt.Sprintf("%T", obj))
return
}
// Pull the runtime.Object out of the object
if o, ok := obj.(runtime.Object); ok {
d.Object = o
} else {
log.Error(nil, "OnDelete missing runtime.Object",
"object", obj, "type", fmt.Sprintf("%T", obj))
return
}
for _, p := range e.Predicates {
if !p.Delete(d) {
return
}
}
// 调用 EventHandler 的 delete 函数
e.EventHandler.Delete(d, e.Queue)
}
ResourceEventHandler
接口,实现过程中我们可以看到调用了 Predicates 进行事件过滤,过滤后才是真正的事件处理,不过其实真正的事件处理也不是在这里去实现的,而是通过 Controller.Watch
函数传递进来的 handler.EventHandler
处理的,这个函数通过前面的 doWatch() 函数可以看出来它是一个 &handler.EnqueueRequestForObject{}
对象,所以真正的事件处理逻辑是这个函数去实现的:// pkg/handler/enqueue.go
// EnqueueRequestForObject 是一个包含了作为事件源的对象的 Name 和 Namespace 的入队列的 Request
//(例如,created/deleted/updated 对象的 Name 和 Namespace)
// handler.EnqueueRequestForObject 几乎被所有关联资源(如 CRD)的控制器使用,以协调关联的资源
type EnqueueRequestForObject struct{}
// Create 函数实现
func (e *EnqueueRequestForObject) Create(evt event.CreateEvent, q workqueue.RateLimitingInterface) {
if evt.Meta == nil {
enqueueLog.Error(nil, "CreateEvent received with no metadata", "event", evt)
return
}
// 添加一个 Request 对象到工作队列
q.Add(reconcile.Request{NamespacedName: types.NamespacedName{
Name: evt.Meta.GetName(),
Namespace: evt.Meta.GetNamespace(),
}})
}
// Update 函数实现
func (e *EnqueueRequestForObject) Update(evt event.UpdateEvent, q workqueue.RateLimitingInterface) {
if evt.MetaOld != nil {
// 如果旧的meta对象不为空,添加到工作队列中
q.Add(reconcile.Request{NamespacedName: types.NamespacedName{
Name: evt.MetaOld.GetName(),
Namespace: evt.MetaOld.GetNamespace(),
}})
} else {
enqueueLog.Error(nil, "UpdateEvent received with no old metadata", "event", evt)
}
if evt.MetaNew != nil {
// 如果新的meta对象不为空,添加到工作队列中
q.Add(reconcile.Request{NamespacedName: types.NamespacedName{
Name: evt.MetaNew.GetName(),
Namespace: evt.MetaNew.GetNamespace(),
}})
} else {
enqueueLog.Error(nil, "UpdateEvent received with no new metadata", "event", evt)
}
}
// Delete 函数实现
func (e *EnqueueRequestForObject) Delete(evt event.DeleteEvent, q workqueue.RateLimitingInterface) {
if evt.Meta == nil {
enqueueLog.Error(nil, "DeleteEvent received with no metadata", "event", evt)
return
}
// 因为前面关于对象的删除状态已经处理了,所以这里直接放入队列中即可
q.Add(reconcile.Request{NamespacedName: types.NamespacedName{
Name: evt.Meta.GetName(),
Namespace: evt.Meta.GetNamespace(),
}})
}
EnqueueRequestForObject
的 Create/Update/Delete 实现可以看出我们放入到工作队列中的元素不是以前默认的元素唯一的 KEY,而是经过封装的 reconcile.Request
对象,当然通过这个对象也可以很方便获取对象的唯一标识 KEY。Controller.Watch
函数就是来实现之前自定义控制器中的 Informer 初始化以及事件监听函数的注册。Controller.Start
函数的实现。// pkg/internal/controller/controller.go
func (c *Controller) Start(stop <-chan struct{}) error {
c.mu.Lock()
// 调用 MakeQueue() 函数生成工作队列
c.Queue = c.MakeQueue()
// 函数退出后关闭队列
defer c.Queue.ShutDown()
err := func() error {
defer c.mu.Unlock()
// TODO(pwittrock): Reconsider HandleCrash
defer utilruntime.HandleCrash()
// NB(directxman12): 在试图等待缓存同步之前启动 sources
// 这样他们有机会注册他们的目标缓存
for _, watch := range c.startWatches {
c.Log.Info("Starting EventSource", "source", watch.src)
if err := watch.src.Start(watch.handler, c.Queue, watch.predicates...); err != nil {
return err
}
}
// 启动 SharedIndexInformer 工厂,开始填充 SharedIndexInformer 缓存
c.Log.Info("Starting Controller")
for _, watch := range c.startWatches {
syncingSource, ok := watch.src.(source.SyncingSource)
if !ok {
continue
}
// 等待 Informer 同步完成
if err := syncingSource.WaitForSync(stop); err != nil {
err := fmt.Errorf("failed to wait for %s caches to sync: %w", c.Name, err)
c.Log.Error(err, "Could not wait for Cache to sync")
return err
}
}
// 所有的 watches 已经启动,重置
c.startWatches = nil
if c.JitterPeriod == 0 {
c.JitterPeriod = 1 * time.Second
}
// 启动 workers 来处理资源
c.Log.Info("Starting workers", "worker count", c.MaxConcurrentReconciles)
for i := 0; i < c.MaxConcurrentReconciles; i++ {
go wait.Until(c.worker, c.JitterPeriod, stop)
}
c.Started = true
return nil
}()
if err != nil {
return err
}
<-stop
c.Log.Info("Stopping workers")
return nil
}
// pkg/internal/controller/controller.go
// worker 运行一个工作线程,从队列中弹出元素处理,并标记为完成
// 强制要求永远不和同一个对象同时调用 reconcileHandler
func (c *Controller) worker() {
for c.processNextWorkItem() {
}
}
// processNextWorkItem 将从工作队列中弹出一个元素,并尝试通过调用 reconcileHandler 来处理它
func (c *Controller) processNextWorkItem() bool {
// 从队列中弹出元素
obj, shutdown := c.Queue.Get()
if shutdown {
// 队列关闭了,直接返回 false
return false
}
// 标记为处理完成
defer c.Queue.Done(obj)
// 调用 reconcileHandler 进行元素处理
return c.reconcileHandler(obj)
}
func (c *Controller) reconcileHandler(obj interface{}) bool {
// 处理完每个元素后更新指标
reconcileStartTS := time.Now()
defer func() {
c.updateMetrics(time.Since(reconcileStartTS))
}()
// 确保对象是一个有效的 request 对象
req, ok := obj.(reconcile.Request)
if !ok {
// 工作队列中的元素无效,所以调用 Forget 函数
// 否则会进入一个循环尝试处理一个无效的元素
c.Queue.Forget(obj)
c.Log.Error(nil, "Queue item was not a Request", "type", fmt.Sprintf("%T", obj), "value", obj)
// 直接返回 true
return true
}
log := c.Log.WithValues("name", req.Name, "namespace", req.Namespace)
// RunInformersAndControllers 的 syncHandler,传递给它要同步的资源的 namespace/Name 字符串
// 调用 Reconciler 函数来处理这个元素,也就是我们真正去编写业务逻辑的地方
if result, err := c.Do.Reconcile(req); err != nil {
// 如果业务逻辑处理出错,重新添加到限速队列中去
c.Queue.AddRateLimited(req)
log.Error(err, "Reconciler error")
// Metrics 指标记录
ctrlmetrics.ReconcileErrors.WithLabelValues(c.Name).Inc()
ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, "error").Inc()
return false
} else if result.RequeueAfter > 0 {
// 如果调谐函数 Reconcile 处理结果中包含大于0的 RequeueAfter
//
// 需要注意如果 result.RequeuAfter 与一个非 nil 的错误一起返回,则 result.RequeuAfter 会丢失。
// 忘记元素
c.Queue.Forget(obj)
// 加入队列
c.Queue.AddAfter(req, result.RequeueAfter)
ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, "requeue_after").Inc()
return true
} else if result.Requeue {
// 重新加入队列
c.Queue.AddRateLimited(req)
ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, "requeue").Inc()
return true
}
// 最后如果没有发生错误,我们就会 Forget 这个元素
// 这样直到发送另一个变化它就不会再被排队了
c.Queue.Forget(obj)
// TODO(directxman12): What does 1 mean? Do we want level constants? Do we want levels at all?
log.V(1).Info("Successfully Reconciled")
// metrics 指标记录
ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, "success").Inc()
// 直接返回true
return true
}
c.Do.Reconcile(req)
暴露给开发者的,所以对于开发者来说,只需要在 Reconcile 函数中去处理业务逻辑就可以了。c.Do.Reconcile(req)
函数的返回值来判断是否需要将元素重新加入队列进行处理:
result.RequeueAfter > 0
,则先将元素忘记,然后在 result.RequeueAfter
时间后加入到队列中result.Requeue
,则直接将元素重新加入到限速队列中https://github.com/kubernetes-sigs/controller-runtime/tree/master/examples/crd
,示例中关于 Manager 的使用步骤为:
// 根据 config 实例化 Manager
// config.GetConfigOrDie() 使用默认的配置~/.kube/config
manager.New(config.GetConfigOrDie(), manager.Options{})
// 将 api 注册到 Scheme,Scheme 提供了 GVK 到 go type 的映射。
// 如果多个 crd,需要多次调用 AddToScheme
api.AddToScheme(mgr.GetScheme())
// 注册 Controller 到 Manager
// For:监控的资源,相当于调用 Watches(&source.Kind{Type: apiType},&handler.EnqueueRequestForObject{})
// Owns:拥有的下属资源,如果 corev1.Pod{} 资源属于 api.ChaosPod{},也将会被监控,相当于调用 Watches(&source.Kind{Type: }, &handler.EnqueueRequestForOwner{OwnerType: apiType, IsController: true})
// reconciler 结构体:继承 Reconciler,需要实现该结构体和 Reconcile 方法
// mgr.GetClient()、mgr.GetScheme() 是 Client 和 Scheme,前面的 manager.New 初始化了
err = builder.ControllerManagedBy(mgr).
For(&api.ChaosPod{}).
Owns(&corev1.Pod{}).
Complete(&reconciler{
Client: mgr.GetClient(),
scheme: mgr.GetScheme(),
})
// 构建webhook
err = builder.WebhookManagedBy(mgr).For(&api.ChaosPod{}).Complete()
// 启动manager,实际上是启动controller
mgr.Start(ctrl.SetupSignalHandler())
// pkg/manager/manager.go
// Manager 初始化共享的依赖关系,比如 Caches 和 Client,并将他们提供给 Runnables
type Manager interface {
// Add 将在组件上设置所需的依赖关系,并在调用 Start 时启动组件
// Add 将注入接口的依赖关系 - 比如 注入 inject.Client
// 根据 Runnable 是否实现了 LeaderElectionRunnable 接口判断
// Runnable 可以在非 LeaderElection 模式(始终运行)或 LeaderElection 模式(如果启用了 LeaderElection,则由 LeaderElection 管理)下运行
Add(Runnable) error
// SetFields 设置对象上的所有依赖关系,而该对象已经实现了 inject 接口
// 比如 inject.Client
SetFields(interface{}) error
// Start 启动所有已注册的控制器,并一直运行,直到停止通道关闭
// 如果启动任何控制器都出错,则返回错误。
// 如果使用了 LeaderElection,则必须在此返回后立即退出二进制,否则需要 Leader 选举的组件可能会在 Leader 锁丢失后继续运行
Start(<-chan struct{}) error
......
}
6. Manager 可以管理 Runnable 的生命周期(添加/启动),如果您不通过 Manager 启动(需要处理各种常见的依赖关系)。
7. Manager 还保持共同的依赖性:client、cache、scheme 等。
- 提供了getter(例如GetClient())
- 还有一个简单的依赖注入机制(runtime/inject)
8. 此外还支持领导人选举,只需用选项指定即可,还提供了一个用于优雅关闭的信号处理程序。
// 返回一个新的 Manager,用于创建 Controllers
func New(config *rest.Config, options Options) (Manager, error) {
if config == nil {
return nil, fmt.Errorf("must specify Config")
}
// 设置 options 属性的默认值
options = setOptionsDefaults(options)
......
return &controllerManager{
......
}, nil
}
setOptionsDefaults
函数为 Options 属性设置了默认的一些参数值,最后返回的是一个 controllerManager 的实例,这是因为该结构体是 Manager 接口的一个实现,所以 Manager 的真正操作都是这个结构体去实现的。err = builder.ControllerManagedBy(mgr).
For(&api.ChaosPod{}).
Owns(&corev1.Pod{}).
Complete(&reconciler{
Client: mgr.GetClient(),
scheme: mgr.GetScheme(),
})
builder.ControllerManagedBy
函数返回一个新的控制器构造器 Builder 对象,生成的控制器将由所提供的管理器 Manager 启动,函数实现很简单:// pkg/builder/controller.go
// 控制器构造器
type Builder struct {
forInput ForInput
ownsInput []OwnsInput
watchesInput []WatchesInput
mgr manager.Manager
globalPredicates []predicate.Predicate
config *rest.Config
ctrl controller.Controller
ctrlOptions controller.Options
log logr.Logger
name string
}
// ControllerManagedBy 返回一个新的控制器构造器
// 它将由提供的 Manager 启动
func ControllerManagedBy(m manager.Manager) *Builder {
return &Builder{mgr: m}
}
For
函数:// pkg/builder/controller.go
// ForInput 表示 For 方法设置的信息
type ForInput struct {
object runtime.Object
predicates []predicate.Predicate
}
// For 函数定义了被调谐的对象类型
// 并配置 ControllerManagedBy 通过调谐对象来响应 create/delete/update 事件
// 调用 For 函数相当于调用:
// Watches(&source.Kind{Type: apiType}, &handler.EnqueueRequestForObject{})
func (blder *Builder) For(object runtime.Object, opts ...ForOption) *Builder {
input := ForInput{object: object}
for _, opt := range opts {
opt.ApplyToFor(&input)
}
blder.forInput = input
return blder
}
For
函数就是用来定义我们要处理的对象类型的,接着调用了 Owns
函数:// pkg/builder/controller.go
// OwnsInput 表示 Owns 方法设置的信息
type OwnsInput struct {
object runtime.Object
predicates []predicate.Predicate
}
// Owns 定义了 ControllerManagedBy 生成的对象类型
// 并配置 ControllerManagedBy 通过调谐所有者对象来响应 create/delete/update 事件
// 这相当于调用:
// Watches(&source.Kind{Type: }, &handler.EnqueueRequestForOwner{OwnerType: apiType, IsController: true})
func (blder *Builder) Owns(object runtime.Object, opts ...OwnsOption) *Builder {
input := OwnsInput{object: object}
for _, opt := range opts {
opt.ApplyToOwns(&input)
}
blder.ownsInput = append(blder.ownsInput, input)
return blder
}
Owns
函数就是来配置我们监听的资源对象的子资源,如果想要协调资源则需要调用 Owns
函数进行配置,然后就是最重要的 Complete
函数了:// pkg/builder/controller.go
func (blder *Builder) Complete(r reconcile.Reconciler) error {
// 调用 Build 函数构建 Controller
_, err := blder.Build(r)
return err
}
// Build 构建应用程序 ControllerManagedBy 并返回它创建的 Controller
func (blder *Builder) Build(r reconcile.Reconciler) (controller.Controller, error) {
if r == nil {
return nil, fmt.Errorf("must provide a non-nil Reconciler")
}
if blder.mgr == nil {
return nil, fmt.Errorf("must provide a non-nil Manager")
}
// 配置 Rest Config
blder.loadRestConfig()
// 配置 ControllerManagedBy
if err := blder.doController(r); err != nil {
return nil, err
}
// 配置 Watch
if err := blder.doWatch(); err != nil {
return nil, err
}
return blder.ctrl, nil
}
Complete
函数通过调用 Build 函数来构建 Controller,其中比较重要的就是 doController
和 doWatch
两个函数,doController
就是去真正实例化 Controller 的函数:// pkg/builder/controller.go
// 根据 GVK 获取控制器名称
func (blder *Builder) getControllerName(gvk schema.GroupVersionKind) string {
if blder.name != "" {
return blder.name
}
return strings.ToLower(gvk.Kind)
}
func (blder *Builder) doController(r reconcile.Reconciler) error {
ctrlOptions := blder.ctrlOptions
if ctrlOptions.Reconciler == nil {
ctrlOptions.Reconciler = r
}
// 从我们正在调谐的对象中检索 GVK
gvk, err := getGvk(blder.forInput.object, blder.mgr.GetScheme())
if err != nil {
return err
}
// 配置日志 Logger
if ctrlOptions.Log == nil {
ctrlOptions.Log = blder.mgr.GetLogger()
}
ctrlOptions.Log = ctrlOptions.Log.WithValues("reconcilerGroup", gvk.Group, "reconcilerKind", gvk.Kind)
// 构造 Controller
// var newController = controller.New
blder.ctrl, err = newController(blder.getControllerName(gvk), blder.mgr, ctrlOptions)
return err
}
// pkg/controller/controller.go
// New 返回一个 Manager 处注册的 Controller
// Manager 将确保共享缓存在控制器启动前已经同步
func New(name string, mgr manager.Manager, options Options) (Controller, error) {
c, err := NewUnmanaged(name, mgr, options)
if err != nil {
return nil, err
}
// 将 controller 作为 manager 的组件
return c, mgr.Add(c)
}
// NewUnmanaged 返回一个新的控制器,而不将其添加到 manager 中
// 调用者负责启动返回的控制器
func NewUnmanaged(name string, mgr manager.Manager, options Options) (Controller, error) {
if options.Reconciler == nil {
return nil, fmt.Errorf("must specify Reconciler")
}
if len(name) == 0 {
return nil, fmt.Errorf("must specify Name for Controller")
}
if options.MaxConcurrentReconciles <= 0 {
options.MaxConcurrentReconciles = 1
}
if options.RateLimiter == nil {
options.RateLimiter = workqueue.DefaultControllerRateLimiter()
}
if options.Log == nil {
options.Log = mgr.GetLogger()
}
// 在 Reconciler 中注入依赖关系
if err := mgr.SetFields(options.Reconciler); err != nil {
return nil, err
}
// 创建 Controller 并配置依赖关系
return &controller.Controller{
Do: options.Reconciler,
MakeQueue: func() workqueue.RateLimitingInterface {
return workqueue.NewNamedRateLimitingQueue(options.RateLimiter, name)
},
MaxConcurrentReconciles: options.MaxConcurrentReconciles,
SetFields: mgr.SetFields,
Name: name,
Log: options.Log.WithName("controller").WithValues("controller", name),
}, nil
}
NewUnmanaged
函数才是真正实例化 Controller 的地方,终于和前文的 Controller 联系起来来,Controller 实例化完成后,又通过 mgr.Add(c)
函数将控制器添加到 Manager 中去进行管理,所以我们还需要去查看下 Manager 的 Add 函数的实现,当然是看 controllerManager 中的具体实现:// pkg/manager/manager.go
// Runnable 允许一个组件被启动
type Runnable interface {
Start(<-chan struct{}) error
}
// pkg/manager/internal.go
// Add 设置i的依赖,并将其他添加到 Runnables 列表中启动
func (cm *controllerManager) Add(r Runnable) error {
cm.mu.Lock()
defer cm.mu.Unlock()
if cm.stopProcedureEngaged {
return errors.New("can't accept new runnable as stop procedure is already engaged")
}
// 设置对象的依赖
if err := cm.SetFields(r); err != nil {
return err
}
var shouldStart bool
// 添加 runnable 到 leader election 或者非 leaderelection 列表
if leRunnable, ok := r.(LeaderElectionRunnable); ok && !leRunnable.NeedLeaderElection() {
shouldStart = cm.started
cm.nonLeaderElectionRunnables = append(cm.nonLeaderElectionRunnables, r)
} else {
shouldStart = cm.startedLeader
cm.leaderElectionRunnables = append(cm.leaderElectionRunnables, r)
}
if shouldStart {
// 如果已经启动,启动控制器
cm.startRunnable(r)
}
return nil
}
func (cm *controllerManager) startRunnable(r Runnable) {
cm.waitForRunnable.Add(1)
go func() {
defer cm.waitForRunnable.Done()
if err := r.Start(cm.internalStop); err != nil {
cm.errChan <- err
}
}()
}
leaderElectionRunnables
列表中,否则加入到 nonLeaderElectionRunnables
列表中,这点非常重要,涉及到后面控制器的启动方式。startRunnable
函数启动控制器,startRunnable 函数就是在一个 goroutine 中去调用 Runnable 的 Start 函数,这里就相当于调用 Controller 的 Start 函数来启动控制器了。doController
函数调用完成,接着是 doWatch
函数的实现:// pkg/builder/controller.go
func (blder *Builder) doWatch() error {
// 调谐类型
src := &source.Kind{Type: blder.forInput.object}
hdler := &handler.EnqueueRequestForObject{}
allPredicates := append(blder.globalPredicates, blder.forInput.predicates...)
// 执行 Watch 操作
err := blder.ctrl.Watch(src, hdler, allPredicates...)
if err != nil {
return err
}
// Watches 管理的类型(子类型)
for _, own := range blder.ownsInput {
src := &source.Kind{Type: own.object}
hdler := &handler.EnqueueRequestForOwner{
OwnerType: blder.forInput.object,
IsController: true,
}
allPredicates := append([]predicate.Predicate(nil), blder.globalPredicates...)
allPredicates = append(allPredicates, own.predicates...)
if err := blder.ctrl.Watch(src, hdler, allPredicates...); err != nil {
return err
}
}
// 执行 watch 请求
for _, w := range blder.watchesInput {
allPredicates := append([]predicate.Predicate(nil), blder.globalPredicates...)
allPredicates = append(allPredicates, w.predicates...)
if err := blder.ctrl.Watch(w.src, w.eventhandler, allPredicates...); err != nil {
return err
}
}
return nil
}
// pkg/manager/internal.go
func (cm *controllerManager) Start(stop <-chan struct{}) (err error) {
stopComplete := make(chan struct{})
defer close(stopComplete)
// stopComplete 关闭后必须在 deferer 执行下面的操作,否则会出现死锁
defer func() {
// https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/gettyimages-459889618-1533579787.jpg
stopErr := cm.engageStopProcedure(stopComplete)
if stopErr != nil {
if err != nil {
err = utilerrors.NewAggregate([]error{err, stopErr})
} else {
err = stopErr
}
}
}()
cm.errChan = make(chan error)
// Metrics 服务
if cm.metricsListener != nil {
go cm.serveMetrics(cm.internalStop)
}
// 健康检测的服务
if cm.healthProbeListener != nil {
go cm.serveHealthProbes(cm.internalStop)
}
// 启动非 LeaderElection 的 Runnables
go cm.startNonLeaderElectionRunnables()
go func() {
if cm.resourceLock != nil {
// 启动 LeaderElection 选举
err := cm.startLeaderElection()
if err != nil {
cm.errChan <- err
}
} else {
close(cm.elected)
// 启动 LeaderElection 的 Runnables
go cm.startLeaderElectionRunnables()
}
}()
select {
case <-stop:
// We are done
return nil
case err := <-cm.errChan:
// Error starting or running a runnable
return err
}
}
// pkg/manager/internal.go
func (cm *controllerManager) waitForCache() {
if cm.started {
return
}
// Start the Cache. Allow the function to start the cache to be mocked out for testing
if cm.startCache == nil {
cm.startCache = cm.cache.Start
}
cm.startRunnable(RunnableFunc(func(stop <-chan struct{}) error {
return cm.startCache(stop)
}))
cm.cache.WaitForCacheSync(cm.internalStop)
cm.started = true
}
// 启动非 LeaderElection Runnables
func (cm *controllerManager) startNonLeaderElectionRunnables() {
cm.mu.Lock()
defer cm.mu.Unlock()
// 等待缓存同步完成
cm.waitForCache()
// 开始启动所有的非 leaderelection 的 Runnables
for _, c := range cm.nonLeaderElectionRunnables {
cm.startRunnable(c)
}
}
func (cm *controllerManager) startLeaderElectionRunnables() {
cm.mu.Lock()
defer cm.mu.Unlock()
// 等待缓存同步完成
cm.waitForCache()
for _, c := range cm.leaderElectionRunnables {
cm.startRunnable(c)
}
cm.startedLeader = true
}
// 真正的启动一个 Runnable
func (cm *controllerManager) startRunnable(r Runnable) {
cm.waitForRunnable.Add(1)
go func() {
defer cm.waitForRunnable.Done()
if err := r.Start(cm.internalStop); err != nil {
cm.errChan <- err
}
}()
}
6. 可以看到最终还是去调用的 Runnable 的 Start 函数来启动,这里其实也就是 Controller 的 Start 函数,前文我们已经详细介绍过,这个函数相当于启动一个控制循环不断从工作队列中消费数据,然后给到一个 Reconciler 接口进行处理,也就是我们要去实现的 Reconcile(Request) (Result, error)
这个业务逻辑函数。
7. 到这里我们就完成了 Manager 的整个启动过程,包括 Manager 是如何初始化,如何和 Controller 进行关联以及如何启动 Controller 的,了解了整个 controller-runtime 的原理过后,我们再去使用 kubebuilder 来编写 Operator 就更加容易了。