sigs.k8s.io/controller-runtime@v0.18.2/designs/component-config.md (about)

     1  # ComponentConfig Controller Runtime Support
     2  Author: @christopherhein
     3  
     4  Last Updated on: 03/02/2020
     5  
     6  ## Table of Contents
     7  
     8  <!--ts-->
     9     * [ComponentConfig Controller Runtime Support](#componentconfig-controller-runtime-support)
    10        * [Table of Contents](#table-of-contents)
    11        * [Summary](#summary)
    12        * [Motivation](#motivation)
    13           * [Links to Open Issues](#links-to-open-issues)
    14           * [Goals](#goals)
    15           * [Non-Goals/Future Work](#non-goalsfuture-work)
    16        * [Proposal](#proposal)
    17  		* [ComponentConfig Load Order](#componentconfig-load-order)
    18  		* [Embeddable ComponentConfig Type](#embeddable-componentconfig-type)
    19  		* [Default ComponentConfig Type](#default-componentconfig-type)
    20  		* [Using Flags w/ ComponentConfig](#using-flags-w-componentconfig)
    21  		* [Kubebuilder Scaffolding Example](#kubebuilder-scaffolding-example)
    22        * [User Stories](#user-stories)
    23           * [Controller Author with controller-runtime and default type](#controller-author-with-controller-runtime-and-default-type)
    24           * [Controller Author with controller-runtime and custom type](#controller-author-with-controller-runtime-and-custom-type)
    25           * [Controller Author with kubebuilder (tbd proposal for kubebuilder)](#controller-author-with-kubebuilder-tbd-proposal-for-kubebuilder)
    26           * [Controller User without modifications to config](#controller-user-without-modifications-to-config)
    27           * [Controller User with modifications to config](#controller-user-with-modifications-to-config)
    28        * [Risks and Mitigations](#risks-and-mitigations)
    29        * [Alternatives](#alternatives)
    30        * [Implementation History](#implementation-history)
    31  
    32  <!--te-->
    33  
    34  ## Summary
    35  
    36  Currently controllers that use `controller-runtime` need to configure the `ctrl.Manager` by using flags or hardcoding values into the initialization methods. Core Kubernetes has started to move away from using flags as a mechanism for configuring components and standardized on [`ComponentConfig` or Versioned Component Configuration Files](https://docs.google.com/document/d/1FdaEJUEh091qf5B98HM6_8MS764iXrxxigNIdwHYW9c/edit). This proposal is to bring `ComponentConfig` to `controller-runtime` to allow controller authors to make `go` types backed by `apimachinery` to unmarshal and configure the `ctrl.Manager{}` reducing the flags and allowing code based tools to easily configure controllers instead of requiring them to mutate CLI args.
    37  
    38  ## Motivation
    39  
    40  This change is important because:
    41  - it will help make it easier for controllers to be configured by other machine processes
    42  - it will reduce the required flags required to start a controller
    43  - allow for configuration types which aren't natively supported by flags
    44  - allow using and upgrading older configurations avoiding breaking changes in flags
    45  
    46  ### Links to Open Issues
    47  
    48  - [#518 Provide a ComponentConfig to tweak the Manager](https://github.com/kubernetes-sigs/controller-runtime/issues/518)
    49  - [#207 Reduce command line flag boilerplate](https://github.com/kubernetes-sigs/controller-runtime/issues/207)
    50  - [#722 Implement ComponentConfig by default & stop using (most) flags](https://github.com/kubernetes-sigs/kubebuilder/issues/722)
    51  
    52  ### Goals
    53  
    54  - Provide an interface for pulling configuration data out of exposed `ComponentConfig` types (see below for implementation)
    55  - Provide a new `ctrl.NewFromComponentConfig()` function for initializing a manager
    56  - Provide an embeddable `ControllerManagerConfiguration` type for easily authoring `ComponentConfig` types
    57  - Provide an `DefaultControllerConfig` to make the switching easier for clients
    58  
    59  ### Non-Goals/Future Work
    60  
    61  - `kubebuilder` implementation and design in another PR
    62  - Changing the default `controller-runtime` implementation
    63  - Dynamically reloading `ComponentConfig` object
    64  - Providing `flags` interface and overrides
    65  
    66  ## Proposal
    67  
    68  The `ctrl.Manager` _SHOULD_ support loading configurations from `ComponentConfig` like objects.
    69  An interface for that object with getters for the specific configuration parameters is created to bridge existing patterns.
    70  
    71  Without breaking the current `ctrl.NewManager` which uses an exported `ctrl.Options{}` the `manager.go` can expose a new func, `NewFromComponentConfig()` this would be able to loop through the getters to populate an internal `ctrl.Options{}` and pass that into `New()`.
    72  
    73  ```golang
    74  //pkg/manager/manager.go
    75  
    76  // ManagerConfiguration defines what the ComponentConfig object for ControllerRuntime needs to support
    77  type ManagerConfiguration interface {
    78  	GetSyncPeriod() *time.Duration
    79  
    80  	GetLeaderElection() bool
    81  	GetLeaderElectionNamespace() string
    82  	GetLeaderElectionID() string
    83  
    84  	GetLeaseDuration() *time.Duration
    85  	GetRenewDeadline() *time.Duration
    86  	GetRetryPeriod() *time.Duration
    87  
    88  	GetNamespace() string
    89  	GetMetricsBindAddress() string
    90  	GetHealthProbeBindAddress() string
    91  
    92  	GetReadinessEndpointName() string
    93  	GetLivenessEndpointName() string
    94  
    95  	GetPort() int
    96  	GetHost() string
    97  
    98  	GetCertDir() string
    99  }
   100  
   101  func NewFromComponentConfig(config *rest.Config, scheme *runtime.Scheme, filename string, managerconfig ManagerConfiguration) (Manager, error) {
   102  	codecs := serializer.NewCodecFactory(scheme)
   103      if err := decodeComponentConfigFileInto(codecs, filename, managerconfig); err != nil {
   104  
   105  	}
   106  	options := Options{}
   107  
   108  	if scheme != nil {
   109  		options.Scheme = scheme
   110  	}
   111  
   112  	// Loop through getters
   113  	if managerconfig.GetLeaderElection() {
   114  		options.LeaderElection = managerconfig.GetLeaderElection()
   115  	}
   116  	// ...
   117  
   118  	return New(config, options)
   119  }
   120  ```
   121  
   122  #### ComponentConfig Load Order
   123  
   124  ![ComponentConfig Load Order](/designs/images/component-config-load.png)
   125  
   126  #### Embeddable ComponentConfig Type
   127  
   128  To make this easier for Controller authors `controller-runtime` can expose a set of `config.ControllerConfiguration` type that can be embedded similar to the way that `k8s.io/apimachinery/pkg/apis/meta/v1` works for `TypeMeta` and `ObjectMeta` these could live in `pkg/api/config/v1alpha1/types.go`. See the `DefaultComponentConfig` type below for and example implementation.
   129  
   130  ```golang
   131  // pkg/api/config/v1alpha1/types.go
   132  package v1alpha1
   133  
   134  import (
   135  	"time"
   136  
   137  	configv1alpha1 "k8s.io/component-base/config/v1alpha1"
   138  )
   139  
   140  // ControllerManagerConfiguration defines the embedded RuntimeConfiguration for controller-runtime clients.
   141  type ControllerManagerConfiguration struct {
   142  	Namespace string `json:"namespace,omitempty"`
   143  
   144  	SyncPeriod *time.Duration `json:"syncPeriod,omitempty"`
   145  
   146  	LeaderElection configv1alpha1.LeaderElectionConfiguration `json:"leaderElection,omitempty"`
   147  
   148  	MetricsBindAddress string `json:"metricsBindAddress,omitempty"`
   149  
   150  	Health ControllerManagerConfigurationHealth `json:"health,omitempty"`
   151  
   152  	Port *int   `json:"port,omitempty"`
   153  	Host string `json:"host,omitempty"`
   154  
   155  	CertDir string `json:"certDir,omitempty"`
   156  }
   157  
   158  // ControllerManagerConfigurationHealth defines the health configs
   159  type ControllerManagerConfigurationHealth struct {
   160  	HealthProbeBindAddress string `json:"healthProbeBindAddress,omitempty"`
   161  
   162  	ReadinessEndpointName string `json:"readinessEndpointName,omitempty"`
   163  	LivenessEndpointName  string `json:"livenessEndpointName,omitempty"`
   164  }
   165  ```
   166  
   167  
   168  
   169  #### Default ComponentConfig Type
   170  
   171  To enable `controller-runtime` to have a default `ComponentConfig` struct which can be used instead of requiring each controller or extension to build its own `ComponentConfig` type, we can create a `DefaultControllerConfiguration` type which can exist in `pkg/api/config/v1alpha1/types.go`. This will allow the controller authors to use this before needing to implement their own type with additional configs.
   172  
   173  ```golang
   174  // pkg/api/config/v1alpha1/types.go
   175  package v1alpha1
   176  
   177  import (
   178  	"time"
   179  
   180  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
   181  	configv1alpha1 "sigs.k8s.io/controller-runtime/pkg/apis/config/v1alpha1"
   182  )
   183  
   184  // DefaultControllerManagerConfiguration is the Schema for the DefaultControllerManagerConfigurations API
   185  type DefaultControllerManagerConfiguration struct {
   186  	metav1.TypeMeta `json:",inline"`
   187  
   188  	Spec   configv1alpha1.ControllerManagerConfiguration   `json:"spec,omitempty"`
   189  }
   190  ```
   191  
   192  This would allow a controller author to use this struct with any config that supports the json/yaml structure. For example a controller author could define their `Kind` as `FoobarControllerConfiguration` and have it defined as the following.
   193  
   194  ```yaml
   195  # config.yaml
   196  apiVersion: config.somedomain.io/v1alpha1
   197  kind: FoobarControllerManagerConfiguration
   198  spec:
   199    port: 9443
   200    metricsBindAddress: ":8080"
   201    leaderElection:
   202      leaderElect: false
   203  ```
   204  
   205  Given the following config and `DefaultControllerManagerConfiguration` we'd be able to initialize the controller using the following.
   206  
   207  
   208  ```golang
   209  mgr, err := ctrl.NewManagerFromComponentConfig(ctrl.GetConfigOrDie(), scheme, configname, &defaultv1alpha1.DefaultControllerManagerConfiguration{})
   210  if err != nil {
   211  	// ...
   212  }
   213  ```
   214  
   215  The above example uses `configname` which is the name of the file to load the configuration from and uses `scheme` to get the specific serializer, eg `serializer.NewCodecFactory(scheme)`. This will allow the configuration to be unmarshalled into the `runtime.Object` type and passed into the
   216  `ctrl.NewManagerFromComponentConfig()` as a `ManagerConfiguration` interface.
   217  
   218  #### Using Flags w/ ComponentConfig
   219  
   220  Since this design still requires setting up the initial `ComponentConfig` type and passing in a pointer to `ctrl.NewFromComponentConfig()` if you want to allow for the use of flags, your controller can use any of the different flagging interfaces. eg [`flag`](https://golang.org/pkg/flag/), [`pflag`](https://pkg.go.dev/github.com/spf13/pflag), [`flagnum`](https://pkg.go.dev/github.com/luci/luci-go/common/flag/flagenum) and set values on the `ComponentConfig` type prior to passing the pointer into the `ctrl.NewFromComponentConfig()`, example below.
   221  
   222  ```golang
   223  leaderElect := true
   224  
   225  config := &defaultv1alpha1.DefaultControllerManagerConfiguration{
   226  	Spec: configv1alpha1.ControllerManagerConfiguration{
   227  		LeaderElection: configv1alpha1.LeaderElectionConfiguration{
   228  			LeaderElect: &leaderElect,
   229  		},
   230  	},
   231  }
   232  mgr, err := ctrl.NewManagerFromComponentConfig(ctrl.GetConfigOrDie(), scheme, configname, config)
   233  if err != nil {
   234  	// ...
   235  }
   236  ```
   237  
   238  #### Kubebuilder Scaffolding Example
   239  
   240  Within expanded in a separate design _(link once created)_ this will allow controller authors to generate a type that implements the `ManagerConfiguration` interface. The following is a sample of what this looks like:
   241  
   242  ```golang
   243  package config
   244  
   245  import (
   246    "time"
   247  
   248  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
   249  	configv1alpha1 "sigs.k8s.io/controller-runtime/pkg/apis/config/v1alpha1"
   250  )
   251  
   252  type ControllerNameConfigurationSpec struct {
   253  	configv1alpha1.ControllerManagerConfiguration `json:",inline"`
   254  }
   255  
   256  type ControllerNameConfiguration struct {
   257    metav1.TypeMeta
   258  
   259    Spec ControllerNameConfigurationSpec `json:"spec"`
   260  }
   261  ```
   262  
   263  Usage of this custom `ComponentConfig` type would require then changing the `ctrl.NewFromComponentConfig()` to use the new struct vs the `DefaultControllerManagerConfiguration`.
   264  
   265  ## User Stories
   266  
   267  ### Controller Author with `controller-runtime` and default type
   268  
   269  - Mount `ConfigMap`
   270  - Initialize `ctrl.Manager` with `NewFromComponentConfig` with config name and `DefaultControllerManagerConfiguration` type
   271  - Build custom controller as usual
   272  
   273  ### Controller Author with `controller-runtime` and custom type
   274  
   275  - Implement `ComponentConfig` type
   276  - Embed `ControllerManagerConfiguration` type
   277  - Mount `ConfigMap`
   278  - Initialize `ctrl.Manager` with `NewFromComponentConfig` with config name and `ComponentConfig` type
   279  - Build custom controller as usual
   280  
   281  ### Controller Author with `kubebuilder` (tbd proposal for `kubebuilder`)
   282  
   283  - Initialize `kubebuilder` project using `--component-config-name=XYZConfiguration`
   284  - Build custom controller as usual
   285  
   286  ### Controller User without modifications to config
   287  
   288  _Provided that the controller provides manifests_
   289  
   290  - Apply the controller to the cluster
   291  - Deploy custom resources
   292  
   293  ### Controller User with modifications to config
   294  
   295  - _Following from previous example without changes_
   296  - Create a new `ConfigMap` for changes
   297  - Modify the `controller-runtime` pod to use the new `ConfigMap`
   298  - Apply the controller to the cluster
   299  - Deploy custom resources
   300  
   301  
   302  ## Risks and Mitigations
   303  
   304  - Given that this isn't changing the core Manager initialization for `controller-runtime` it's fairly low risk
   305  
   306  ## Alternatives
   307  
   308  * `NewFromComponentConfig()` could load the object from disk based on the file name and hydrate the `ComponentConfig` type.
   309  
   310  ## Implementation History
   311  
   312  - [x] 02/19/2020: Proposed idea in an issue or [community meeting]
   313  - [x] 02/24/2020: Proposal submitted to `controller-runtime`
   314  - [x] 03/02/2020: Updated with default `DefaultControllerManagerConfiguration`
   315  - [x] 03/04/2020: Updated with embeddable `RuntimeConfig`
   316  - [x] 03/10/2020: Updated embeddable name to `ControllerManagerConfiguration`
   317  
   318  
   319  <!-- Links -->
   320  [community meeting]: https://docs.google.com/document/d/1Ih-2cgg1bUrLwLVTB9tADlPcVdgnuMNBGbUl4D-0TIk