volcano.sh/volcano@v1.9.0/docs/design/event-handler-framework.md (about)

     1  EventHandler Framework
     2  
     3  [@sivanzcw](https://github.com/sivanzcw); Dec 1, 2021
     4  
     5  ## Table of Contents
     6  
     7  ## Summary
     8  
     9  As a monolithic scheduler, the volcano use a single, centralized scheduling algorithm for all jobs. It schedules 
    10  jobs based on resources managed by kubernetes. When there is a need for scheduling for resources managed outsider
    11  of kubernetes, there is no mechanism to do it today.
    12  
    13  The proposal is to make volcano extensible by adding the capability to replace the resource manager to help achieve this 
    14  functionality.
    15  
    16  ## Motivation
    17  
    18  There are two ways to add new scheduling rules to volcano:
    19  - Update existing or add new scheduler plugins and recompiling, whether to use the plugin is determined by the 
    20  configuration file
    21  - Secondary development based on the volcano, implementing a new scheduler framework that encapsulate the volcano
    22  
    23  This document describes the second approach. If you want to expand the ability of volcano add new policies and 
    24  specialized implementations, and have certain R & D capabilities, then this approach is likely the better choice.
    25  
    26  The rise of secure containers makes it possible to deploy both containerized applications and virtual machine applications 
    27  on physical machines. In the scene of a hybrid deployment of containerized applications and virtual machine applications, 
    28  scheduling jobs based on resources managed by kubernetes alone will lead to resource conflicts. In such scenario, the 
    29  organizations will want to run multiple frameworks in the same cluster and a central resource manager above frameworks 
    30  is needed to coordinate and allocate resources uniformly. This two-level scheduling approach is used by a number of systems.
    31  
    32  In two-level scheduler, like `Mesos`, a centralized resource allocator dynamically partitions a cluster, allocating 
    33  resources to different scheduler frameworks. Resources distributed to the frameworks contain only `avaliable` resources, 
    34  ones that are currently unused. Because only one framework is examining a resource at a time, resource conflicts is avoided.
    35  
    36  To access volcano as one framework to a two-level scheduler, the volcano needs to provide access for external modifications 
    37  to its cached resource objects.
    38  
    39  ## Goals
    40  
    41  - volcano provides an entrance for external modification of its cached resource objects
    42  
    43  ## Design Details
    44  
    45   ![event-handler-arch](./images/event-handler-arch.png)
    46  
    47  ### In-tree SharedIndexInformer
    48  
    49  As the native implementation of kubernetes, the In-tree SharedIndexInformer provides eventually consistent linkage of 
    50  volcano to the authoritative state of a given collection of objects from kubernetes cluster. It watches specified 
    51  resources and causes all changes to be reflected in the given store, also trigger event handler to handle the object.
    52  
    53  ### Custom SharedIndexInformer
    54  
    55  The Custom SharedIndexInformer provides eventually consistent linkage of volcano to the authoritative state of a given 
    56  collection of objects from non-kubernetes cluster. Different from the original sharedIndexInformer, custom 
    57  sharedIndexInformer does not get resource objects from kubernetes cluster, but an external resource manager service. 
    58  The Custom sharedIndexInformer uses the same local data caching and event distribution mechanism as the native informer.
    59  
    60  ### Resource EventHandler Framework
    61  
    62  Both the native and the custom informers use the same resource event handler framework. The framework provides a set of 
    63  event handler interfaces, including the processing of `ADD`, `MODIFIED`, `DELETED` events of objects involved. Volcano 
    64  provides a default event handling suite. The developers can customize the event handling processing to replace the 
    65  default implementation. 
    66  
    67  ### Custom SharedIndexInformer implementation
    68  
    69  Add a customInformers array to the `SchedulerCache` struct as the entry to the custom SharedIndexInformer implementation.
    70  
    71  ```go
    72  // SchedulerCache cache for the kube batch
    73  type SchedulerCache struct {
    74     ......
    75     CustomInformers     []cache.SharedIndexInformer
    76     ......
    77  }
    78  ```
    79  
    80  Start the custom informer when `SchedulerCache` starts
    81  
    82  ```go
    83  // Run  starts the schedulerCache
    84  func (sc *SchedulerCache) Run(stopCh <-chan struct{}) {
    85     go sc.podInformer.Informer().Run(stopCh)
    86     ......
    87     for _, i := range sc.CustomInformers {
    88        go i.Run(stopCh)
    89     }
    90     ......
    91  }
    92  ```
    93  
    94  Wait the custom informer to synced before the starting of scheduling loop
    95  
    96  ```go
    97  // WaitForCacheSync sync the cache with the api server
    98  func (sc *SchedulerCache) WaitForCacheSync(stopCh <-chan struct{}) bool {
    99     return cache.WaitForCacheSync(stopCh,
   100        func() []cache.InformerSynced {
   101           informerSynced := []cache.InformerSynced{
   102              sc.podInformer.Informer().HasSynced,
   103              ......
   104           }
   105           ......
   106        }()...,
   107     ) && func() bool {
   108           for _, i := range sc.CustomInformers {
   109              if !i.HasSynced() {
   110                 return false
   111              }
   112           }
   113           
   114           return true
   115     }
   116  }
   117  ```
   118  
   119  ### EventHandler Factory
   120  
   121  - define an interface, including object event processing function set
   122  - provide a factory to realize the object event processing function
   123  - provide a factory registration mechanism
   124  
   125  ```go
   126  // Interface containers all involved object event processing interfaces.
   127  type Interface interface {
   128     AddPod(obj interface{})
   129     // other object event processing interfaces.
   130  }
   131  
   132  type Factory func(cache *SchedulerCache, config io.Reader, restConfig *rest.Config) (Interface, error)
   133  
   134  var eventHandlers = make(map[string]Factory)
   135  var eventHandlerMutex sync.Mutex
   136  
   137  func RegisterEventHandler(name string, eventHandler Factory) {
   138     eventHandlerMutex.Lock()
   139     defer eventHandlerMutex.Unlock()
   140     if _, found := eventHandlers[name]; found {
   141        klog.Fatalf("EventHandler %q was registered twice.", name)
   142     }
   143     klog.V(1).Infof("Registered eventHandler %q.", name)
   144     eventHandlers[name] = eventHandler
   145  }
   146  
   147  func GetEventHandler(name, configFilePath string, cache *SchedulerCache, restConfig *rest.Config) (Interface, error) {
   148     eventHandlerMutex.Lock()
   149     defer eventHandlerMutex.Unlock()
   150     f, found := eventHandlers[name]
   151     if !found {
   152        return nil, nil
   153     }
   154     
   155     var config *os.File
   156     if len(configFilePath) != 0 {
   157        config, err = os.Open(configFilePath) 
   158     }
   159     return f(cache, config, restConfig), nil
   160  }
   161  ```
   162  
   163  #### Implement the default eventHandler
   164  
   165  ```go
   166  type defaultEventHandler struct{
   167     cache *SchedulerCache
   168  }
   169  
   170  func init() {
   171     RegisterEventHandler("default", func(cache *SchedulerCache, config io.Reader, restConfig *rest.Config) (i Interface, err error) {
   172        return &defaultEventHandler{cache:cache}, nil
   173     })
   174  }
   175  
   176  func (eh *defaultEventHandler) AddPod(obj interface{}) {
   177     pod, ok := obj.(*v1.Pod)
   178     if !ok {
   179        klog.Errorf("Cannot convert to *v1.Pod: %v", obj)
   180        return
   181     }
   182  
   183     sc := eh.cache
   184  
   185     sc.Mutex.Lock()
   186     defer sc.Mutex.Unlock()
   187  
   188     err := sc.addPod(pod)
   189     if err != nil {
   190        klog.Errorf("Failed to add pod <%s/%s> into cache: %v",
   191           pod.Namespace, pod.Name, err)
   192        return
   193     }
   194     klog.V(3).Infof("Added pod <%s/%v> into cache.", pod.Namespace, pod.Name)
   195  }
   196  ```
   197  
   198  #### Dynamically obtain eventHandler processing object
   199  
   200  - Add `--event-handler` command to declare the eventHandler processing object
   201  - Add `--config-path` command to declare the configuration path of eventHandler
   202  
   203  ```go
   204  eventHadler := GetEventHandler(eventHandlerName, configFilePath, sc, config)
   205  sc.nodeInformer.Informer().AddEventHandlerWithResyncPeriod(
   206     cache.FilteringResourceEventHandler{
   207        FilterFunc: func(obj interface{}) bool {
   208           switch v := obj.(type) {
   209           case *v1.Node:
   210              return responsibleForNode(v.Name, mySchedulerPodName, c)
   211           default:
   212              return false
   213           }
   214        },
   215        Handler: cache.ResourceEventHandlerFuncs{
   216           AddFunc:    eventHadler.AddNode,
   217           UpdateFunc: eventHadler.UpdateNode,
   218           DeleteFunc: eventHadler.DeleteNode,
   219        },
   220     },
   221     0,
   222  )
   223  ```