Controller 是 Controller-runtime 的 核心结构,Controller 管理一个工作队列,并从 source.Sources 中获取 reconcile.Requests 加入队列, 通过执行 reconcile.Reconciler 来处理队列中的每项 reconcile.Requests, 而 reconcile. Reconciler 可以通过读写 Kubernetes 资源来确保集群状态与期望状态一致。
1、Controller 接口定义:
Controller 接口中很重要的两个函数 Watch 与 Start。
// pkg/controller/controller.go
type Controller interface {
// 匿名接口,定义了 Reconcile(context.Context,Request)(Result,error)。
reconcile.Reconciler
// Watch() 方法会从 source.Source 中获取 Event
// 根据参数 Eventhandler 来决定如何入队
// 根据 predictes 进行 Even 过滤
Watch(src source.Source, eventhandler handler.EventHandler, predicates ...predicate.Predicate) error
// Controller 的启动方法。实现了 Controller 和 Runnable 接口,则可被Manager管理
Start(ctx context.Context) error
// 日志输出
GetLogger() logr.Logger
}
2、Controller 结构体定义:
// pkg/internal/controller/controller.go
type Controller struct {
// Name 用于跟踪、记录和监控中控制器的唯一标识,必填字段
Name string
// 运行最大并发 Reconciles 数量,默认值为1
MaxConcurrentReconciles int
// Reconcile() 能在任意时刻被调用,接收一个对象的 Name 与 Namespace,并同步集 群当前实际状态至该对象被设置的期望状态。
Do reconcile.Reconciler
// 一旦控制器准备好启动,MakeQueue 就会为这个控制器构造工作队列
MakeQueue func() workqueue.RateLimitingInterface
// 队列通过监听来自 Infomer 的事件,添加对象键到队列中进行处理
Queue workqueue.RateLimitingInterface
// SetFields 用来将依赖关系注入到其他对象,比如 Sources、EventHandlers 以及 Predicates
SetFields func(i interface{}) error
// mu is used to synchronize Controller setup
mu sync.Mutex
// Started is true if the Controller has been Started
Started bool
ctx context.Context
CacheSyncTimeout time.Duration
// 维护了一个 sources、handlers 以及 predicates 列表以方便在控制器启动的时候启动
startWatches []watchDescription
LogConstructor func(request *reconcile.Request) logr.Logger
// RecoverPanic 指示是否应恢复由 reconcile 引起的恐慌
RecoverPanic bool
}
Controller 的主要逻辑 Controller.Start() 方法
3、Watch 方法
// watch 就是启动 source.start
func (c *Controller) Watch(src source.Source, evthdler handler.EventHandler, prct ...predicate.Predicate) error {
c.mu.Lock()
defer c.mu.Unlock()
// Inject Cache into arguments
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 存放到本地然后返回
if !c.Started {
c.startWatches = append(c.startWatches, watchDescription{src: src, handler: evthdler, predicates: prct})
return nil
}
c.LogConstructor(nil).Info("Starting EventSource", "source", src)
// 调用 src 的 Start 函数
return src.Start(c.ctx, evthdler, c.Queue, prct...)
}
4、Start 方法
start 由manager.Start 触发,消费 workqueue,当 Manager 调用 Start() 方法后,它会 进入 Controller 的启动流程, 经过选举等预处理 Controller 进入 Start() 方法。
func (c *Controller) Start(ctx context.Context) error {
// ...
// 生产工作队列
c.Queue = c.MakeQueue()
go func() {
<-ctx.Done()
c.Queue.ShutDown()
}()
wg := &sync.WaitGroup{}
err := func() error {
defer c.mu.Unlock()
// TODO(pwittrock): Reconsider HandleCrash
defer utilruntime.HandleCrash()
// 启动源之前尝试等待 要同步的缓存,以便他们有机会注册其预期的cache
for _, watch := range c.startWatches {
c.LogConstructor(nil).Info("Starting EventSource", "source", fmt.Sprintf("%s", watch.src))
if err := watch.src.Start(ctx, watch.handler, c.Queue, watch.predicates...); err != nil {
return err
}
}
// Start the SharedIndexInformer factories to begin populating the SharedIndexInformer caches
c.LogConstructor(nil).Info("Starting Controller")
// ...
c.startWatches = nil
// 启动 worker
// Controller 根据 MaxConcurrentReconciles 启动多个 Worker 程序, 用于处理队 列中的对象
c.LogConstructor(nil).Info("Starting workers", "worker count", c.MaxConcurrentReconciles)
wg.Add(c.MaxConcurrentReconciles)
for i := 0; i < c.MaxConcurrentReconciles; i++ {
go func() {
defer wg.Done()
// 根据MaxConcurrentReconciles并发处理
for c.processNextWorkItem(ctx) {
} }()
}
c.Started = true
return nil }()
...
}
processNextWorkItem 方法
Work 程序先从工作队列中获取需要处理的对象,然后调用 reconcileHandler 方法的 Reconcile() 方法进行处理 Reconcile 方法里面再调用 c.Do.Reconcile。
// processNextWorkItem 函数,并发处理
func (c *Controller) processNextWorkItem(ctx context.Context) bool {
// 从对了取出元素
obj, shutdown := c.Queue.Get()
// 如果队列关闭了,返回false
if shutdown {
// Stop working
return false
}
// 标记为处理完毕
defer c.Queue.Done(obj)
ctrlmetrics.ActiveWorkers.WithLabelValues(c.Name).Add(1)
defer ctrlmetrics.ActiveWorkers.WithLabelValues(c.Name).Add(-1)
// 元素处理
c.reconcileHandler(ctx, obj)
return true
}
reconcileHandler 方法
func (c *Controller) reconcileHandler(ctx context.Context, obj interface{}) {
// Update metrics after processing each item
reconcileStartTS := time.Now()
defer func() {
c.updateMetrics(time.Since(reconcileStartTS))
}()
// Make sure that the object is a valid request.
req, ok := obj.(reconcile.Request)
if !ok {
// As the item in the workqueue is actually invalid, we call
// Forget here else we'd go into a loop of attempting to // process a work item that is invalid. c.Queue.Forget(obj)
c.LogConstructor(nil).Error(nil, "Queue item was not a Request", "type", fmt.Sprintf("%T", obj), "value", obj)
// Return true, don't take a break
return
}
log := c.LogConstructor(&req)
log = log.WithValues("reconcileID", uuid.NewUUID())
ctx = logf.IntoContext(ctx, log)
// RunInformersAndControllers the syncHandler, passing it the Namespace/Name string of the
// resource to be synced.
result, err := c.Reconcile(ctx, req)
switch {
case err != nil:
c.Queue.AddRateLimited(req)
ctrlmetrics.ReconcileErrors.WithLabelValues(c.Name).Inc()
ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, labelError).Inc()
log.Error(err, "Reconciler error")
case result.RequeueAfter > 0:
// The result.RequeueAfter request will be lost, if it is returned
// along with a non-nil error. But this is intended as
// We need to drive to stable reconcile loops before queuing due
// to result.RequestAfter
c.Queue.Forget(obj)
c.Queue.AddAfter(req, result.RequeueAfter)
ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, labelRequeueAfter).Inc()
case result.Requeue:
c.Queue.AddRateLimited(req)
ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, labelRequeue).Inc()
default:
// Finally, if no error occurs we Forget this item so it does not
// get queued again until another change happens. c.Queue.Forget(obj)
ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, labelSuccess).Inc()
}
}
根据 Reconcile() 方法返回的结果, 将对象重新入队列或从队列中删除。 重新 入队列的方法可以是带有一定延迟的 Queue.AddAfter(), 也可以是有限速的 Queue. AddRateLimited()。重新加入的次数无限制。
func (c *Controller) Reconcile(ctx context.Context, req reconcile.Request) (_ reconcile.Result, err error) {
defer func() {
if r := recover(); r != nil {
if c.RecoverPanic {
for _, fn := range utilruntime.PanicHandlers {
fn(r)
}
err = fmt.Errorf("panic: %v [recovered]", r)
return
}
log := logf.FromContext(ctx)
log.Info(fmt.Sprintf("Observed a panic in reconciler: %v", r))
panic(r)
}
}()
// Reconcile 是个接口里面只有一个方法,也就是Reconcile(协调函数),提供给controller,需要开发者自己去实现的。
return c.Do.Reconcile(ctx, req)
}
5、Reconciler
Reconciler( 协调器)是提供给 Controller 的一个函数, 可以随时使用对象的 Name 和 Namespace 对其进行调用。当它被调用时,Reconciler 将确保集群中资源的状态和 预设的状态保持一致。例如:ReplicaSet 指定 3 个副本, 但系统中仅存在 2 个 Pod 时, Reconciler 将再创建 1 个 Pod,并向 Pod 的 OwnerReference 中添加该 ReplicaSet 的名称,同时设置“controller=true”属性。
// pkg/reconcile/reconcile.go
type Reconciler interface {
Reconcile(context.Context, Request) (Result, error)
}
Request 包含了需要处理的对象 Name 和 Namespace,而 Result 是决定是否需要将对象重新入队及如何加入队列。
// pkg/reconcile/reconcile.go
type Request struct {
// NamespacedName is the name and namespace of the object to reconcile.
types.NamespacedName
}
// pkg/types/namespacedname.go
type NamespacedName struct {
Namespace string
Name string
}
type Result struct {
// 通知 Controller 是否需要重新将对象加⼊队列,默认为 False
Requeue bool
// RequeueAfter 值大于 0 表示 Controller 需要在设置的时间间隔后将对象重新加⼊队列
RequeueAfter time.Duration
}
6、Predicate 过滤器
Predicate 主要有以下特性:
- 接受一个事件,并将该事件是否通过过滤条件的结果返回。如果通过,该事件将 被加入待处理事件队列中。
- Predicate 是可选项,可以不设置。如果不设置,默认事件都将被加入待处理事件 队列中。
- 用户可以使用内置的 Predicate,但是可以设置自定义 Predicate。
7、EventHandler(事件句柄)
源码位置:pkg/handler/eventhandler.go
它是 Controller.Watch 的参数,当事件产生时,EventHandler 将返回对象的 Name 和 Namespace, 作为 Request 被添加到待处理事件队列中。
// pkg/predicate/predicate.go
type EventHandler interface {
// Create is called in response to an create event - e.g. Pod Creation.
Create(event.CreateEvent, workqueue.RateLimitingInterface)
// Update is called in response to an update event - e.g. Pod Updated.
Update(event.UpdateEvent, workqueue.RateLimitingInterface)
// Delete is called in response to a delete event - e.g. Pod Deleted.
Delete(event.DeleteEvent, workqueue.RateLimitingInterface)
// Generic is called in response to an event of an unknown type or a synthetic event triggered as a cron or
// external trigger request - e.g. reconcile Autoscaling, or a Webhook.
Generic(event.GenericEvent, workqueue.RateLimitingInterface)
}
8、resource.Source(事件源)
它提供了一个事件流,事件通常来自 Watch Kubernetes API(如 Pod Create、Update、Delete)。
// pkg/source/source.go
type Source interface {
// Start() 是 Controller-runtime 的内部⽅法,应该仅由 Controller 调⽤
Start(context.Context, handler.EventHandler, workqueue.RateLimitingInterface, ...predicate.Predicate) error
}
Source 的实现在 pkg/source/source.go 下,主要包括 3 种:
(1)Informer:用于提供来自集群内部的事件,例如,Pod Create 事件。
func (is *Informer) Start(ctx context.Context, handler handler.EventHandler, queue workqueue.RateLimitingInterface,
prct ...predicate.Predicate) error {
// Informer should have been specified by the user.
if is.Informer == nil {
return fmt.Errorf("must specify Informer.Informer")
}
is.Informer.AddEventHandler(internal.EventHandler{Queue: queue, EventHandler: handler, Predicates: prct})
return nil
}
(2)Kind:与 Informer 类似 , 也用于提供来自集群内部的事件。 不同的是 Informer 结构直接继承 cache.Informer,需要开发者自己设置;而 Kind 会根据设置的资源对象类型, 自动生成 cache.Informer。
Builder 创 建 Controller 时, 会 根 据 Builder.For()、Builder.Owns()、Builder.Watches() 方法中设置的资源对象类型在 Builder.Build() 中创建相应的 Kind, 并调用 Controller. Watch() 方法将 Kind 传入 Controller。
func (ks *Kind) Start(ctx context.Context, handler handler.EventHandler, queue workqueue.RateLimitingInterface,
prct ...predicate.Predicate) error {
// Type should have been specified by the user.
if ks.Type == nil {
return fmt.Errorf("must specify Kind.Type")
}
// cache should have been injected before Start was called
if ks.cache == nil {
return fmt.Errorf("must call CacheInto on Kind before calling Start")
}
ctx, ks.startCancel = context.WithCancel(ctx)
ks.started = make(chan error)
go func() {
var (
i cache.Informer
lastErr error
)
// Tries to get an informer until it returns true,
// an error or the specified context is cancelled or expired.
if err := wait.PollImmediateUntilWithContext(ctx, 10*time.Second, func(ctx context.Context) (bool, error) {
// Lookup the Informer from the Cache and add an EventHandler which populates the Queue
i, lastErr = ks.cache.GetInformer(ctx, ks.Type)
if lastErr != nil {
kindMatchErr := &meta.NoKindMatchError{}
switch {
case errors.As(lastErr, &kindMatchErr):
log.Error(lastErr, "if kind is a CRD, it should be installed before calling Start",
"kind", kindMatchErr.GroupKind)
case runtime.IsNotRegisteredError(lastErr):
log.Error(lastErr, "kind must be registered to the Scheme")
default:
log.Error(lastErr, "failed to get informer from cache")
}
return false, nil // Retry.
}
return true, nil
}); err != nil {
if lastErr != nil {
ks.started <- fmt.Errorf("failed to get informer from cache: %w", lastErr)
return
}
ks.started <- err
return
}
i.AddEventHandler(internal.EventHandler{Queue: queue, EventHandler: handler, Predicates: prct})
if !ks.cache.WaitForCacheSync(ctx) {
// Would be great to return something more informative here
ks.started <- errors.New("cache did not sync")
}
close(ks.started)
}()
return nil
}
(3)Channel:用于提供集群外部的事件,例如,GitHub WebHook 回调。Channel 要 求用户连接外部的源(如 Http Handler), 以便将 GenericEvents 写入底层的 Channel 结 构中。
func (cs *Channel) Start(
ctx context.Context,
handler handler.EventHandler,
queue workqueue.RateLimitingInterface,
prct ...predicate.Predicate) error {
// Source should have been specified by the user.
if cs.Source == nil {
return fmt.Errorf("must specify Channel.Source")
}
// stop should have been injected before Start was called
if cs.stop == nil {
return fmt.Errorf("must call InjectStop on Channel before calling Start")
}
// use default value if DestBufferSize not specified
if cs.DestBufferSize == 0 {
cs.DestBufferSize = defaultBufferSize
}
dst := make(chan event.GenericEvent, cs.DestBufferSize)
cs.destLock.Lock()
cs.dest = append(cs.dest, dst)
cs.destLock.Unlock()
cs.once.Do(func() {
// Distribute GenericEvents to all EventHandler / Queue pairs Watching this source
go cs.syncLoop(ctx)
})
go func() {
for evt := range dst {
shouldHandle := true
for _, p := range prct {
if !p.Generic(evt) {
shouldHandle = false
break }
}
if shouldHandle {
handler.Generic(evt, queue)
}
}
}()
return nil
}
1433

被折叠的 条评论
为什么被折叠?



