Skip to content

Commit 7aae5bd

Browse files
Adding component config proposal
Signed-off-by: Chris Hein <[email protected]>
1 parent 95c0327 commit 7aae5bd

File tree

2 files changed

+322
-0
lines changed

2 files changed

+322
-0
lines changed

designs/component-config.md

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

0 commit comments

Comments
 (0)