Skip to content

Commit 23c65a4

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

File tree

2 files changed

+320
-0
lines changed

2 files changed

+320
-0
lines changed

designs/component-config.md

Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
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 `ControllerConfiguration` 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+
// ControllerConfiguration defines the embedded RuntimeConfiguration for controller-runtime clients.
141+
type ControllerConfiguration 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 ControllerConfigurationHealth `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+
// ControllerConfigurationHealth defines the health configs
159+
type ControllerConfigurationHealth 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 it's 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+
// DefaultControllerConfiguration is the Schema for the DefaultControllerConfigurations API
185+
type DefaultControllerConfiguration struct {
186+
metav1.TypeMeta `json:",inline"`
187+
188+
Spec configv1alpha1.ControllerConfiguration `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: FoobarControllerConfiguration
198+
spec:
199+
port: 9443
200+
metricsBindAddress: ":8080"
201+
leaderElection:
202+
leaderElect: false
203+
```
204+
205+
Given the following config and `DefaultControllerConfiguration` 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.DefaultControllerConfiguration{})
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://godoc.org/github.com/spf13/pflag), [`flagnum`](https://godoc.org/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.DefaultControllerConfiguration{
226+
Spec: configv1alpha1.ControllerConfiguration{
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.ControllerConfiguration `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 `DefaultControllerConfiguration`.
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 `DefaultControllerConfiguration` type
271+
- Build custom controller as usual
272+
273+
### Controller Author with `controller-runtime` and custom type
274+
275+
- Implement `ComponentConfig` type
276+
- Embed `ControllerConfiguration` 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 `DefaultControllerConfiguration`
315+
- [x] 03/04/2020: Updated with embeddable `RuntimeConfig`
316+
- [x] 03/10/2020: Updated embeddable name to `ControllerConfiguration`
317+
318+
319+
<!-- Links -->
320+
[community meeting]: https://docs.google.com/document/d/1Ih-2cgg1bUrLwLVTB9tADlPcVdgnuMNBGbUl4D-0TIk
27.8 KB
Loading

0 commit comments

Comments
 (0)