Skip to content

Commit cb45a3b

Browse files
Ish Shahcamilamacedo86Eric Stroczynski
authored
Update Advanced Topics Doc
Co-authored-by: Camila Macedo <[email protected]> Co-authored-by: Eric Stroczynski <[email protected]>
1 parent e48552e commit cb45a3b

File tree

1 file changed

+135
-147
lines changed

1 file changed

+135
-147
lines changed

website/content/en/docs/kubebuilder/advanced-topics.md

Lines changed: 135 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -28,48 +28,44 @@ Then, in your controller, you can use [`Conditions`][godoc-conditions] methods t
2828

2929
### Adding 3rd Party Resources To Your Operator
3030

31-
> **// TODO:** Update the `main.go` code in these sections to use the `init()` func to register the scheme instead of doing it in `main()`.
3231

3332
The operator's Manager supports the core Kubernetes resource types as found in the client-go [scheme][scheme_package] package and will also register the schemes of all custom resource types defined in your project.
3433

3534
```Go
3635
import (
37-
"github.com/example-inc/memcached-operator/pkg/apis"
38-
...
36+
cachev1alpha1 "github.com/example-inc/memcached-operator/api/v1alpha1
37+
...
3938
)
4039
41-
// Setup Scheme for all resources
42-
if err := apis.AddToScheme(mgr.GetScheme()); err != nil {
43-
log.Error(err, "")
44-
os.Exit(1)
40+
func init() {
41+
42+
// Setup Scheme for all resources
43+
utilruntime.Must(cachev1alpha1.AddToScheme(mgr.GetScheme()))
44+
// +kubebuilder:scaffold:scheme
4545
}
4646
```
4747
4848
To add a 3rd party resource to an operator, you must add it to the Manager's scheme. By creating an `AddToScheme()` method or reusing one you can easily add a resource to your scheme. An [example][deployments_register] shows that you define a function and then use the [runtime][runtime_package] package to create a `SchemeBuilder`.
4949
5050
#### Register with the Manager's scheme
5151
52-
Call the `AddToScheme()` function for your 3rd party resource and pass it the Manager's scheme via `mgr.GetScheme()`
53-
in `cmd/manager/main.go`.
54-
52+
Call the `AddToScheme()` function for your 3rd party resource and pass it the Manager's scheme via `mgr.GetScheme()` in `main.go`.
5553
Example:
5654
```go
5755
import (
58-
....
59-
60-
routev1 "github.com/openshift/api/route/v1"
56+
routev1 "github.com/openshift/api/route/v1"
6157
)
6258
63-
func main() {
64-
...
59+
func init() {
60+
...
6561
66-
// Adding the routev1
67-
if err := routev1.AddToScheme(mgr.GetScheme()); err != nil {
68-
log.Error(err, "")
69-
os.Exit(1)
70-
}
62+
// Adding the routev1
63+
utilruntime.Must(clientgoscheme.AddToScheme(mgr.GetScheme()))
7164
72-
...
65+
utilruntime.Must(routev1.AddToScheme(mgr.GetScheme()))
66+
// +kubebuilder:scaffold:scheme
67+
68+
...
7369
}
7470
```
7571
@@ -81,28 +77,28 @@ Example of registering `DNSEndpoints` 3rd party resource from `external-dns`:
8177
8278
```go
8379
import (
84-
...
80+
...
8581
"k8s.io/apimachinery/pkg/runtime/schema"
8682
"sigs.k8s.io/controller-runtime/pkg/scheme"
87-
...
88-
// DNSEndoints
89-
externaldns "github.com/kubernetes-incubator/external-dns/endpoint"
90-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
83+
...
84+
// DNSEndoints
85+
externaldns "github.com/kubernetes-incubator/external-dns/endpoint"
86+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9187
)
9288
93-
func main() {
94-
...
89+
func init() {
90+
...
9591
96-
log.Info("Registering Components.")
92+
log.Info("Registering Components.")
9793
98-
schemeBuilder := &scheme.Builder{GroupVersion: schema.GroupVersion{Group: "externaldns.k8s.io", Version: "v1alpha1"}}
99-
schemeBuilder.Register(&externaldns.DNSEndpoint{}, &externaldns.DNSEndpointList{})
100-
if err := schemeBuilder.AddToScheme(mgr.GetScheme()); err != nil {
101-
log.Error(err, "")
102-
os.Exit(1)
103-
}
94+
schemeBuilder := &scheme.Builder{GroupVersion: schema.GroupVersion{Group: "externaldns.k8s.io", Version: "v1alpha1"}}
95+
schemeBuilder.Register(&externaldns.DNSEndpoint{}, &externaldns.DNSEndpointList{})
96+
if err := schemeBuilder.AddToScheme(mgr.GetScheme()); err != nil {
97+
log.Error(err, "")
98+
os.Exit(1)
99+
}
104100
105-
...
101+
...
106102
}
107103
```
108104
@@ -115,18 +111,11 @@ func main() {
115111
116112
### Metrics
117113
118-
> **// TODO:** Update the [metrics doc](https://github.com/operator-framework/operator-sdk/blob/master/website/content/en/docs/golang/metrics/operator-sdk-monitoring.md) since it doesn't match the default scaffolded by kubebuilder anymore.
119-
120-
To learn about how metrics work in the Operator SDK read the [metrics section][metrics_doc] of the user documentation.
121-
122-
#### Default Metrics exported with 3rd party resource
114+
To learn about how metrics work in the Operator SDK read the [metrics section][metrics_doc] of the Kubebuilder documentation.
123115
124-
> **// TODO:** Remove this section since we're no longer scaffolding main.go to use the SDK's `GenerateAndServeCRMetrics()` util in `pkg/kube-metrics`.
125116
126117
### Handle Cleanup on Deletion
127118
128-
> **// TODO:** Update finalizer reconcile code for kubebuilder's default reconciler imports and variable names
129-
130119
To implement complex deletion logic, you can add a finalizer to your Custom Resource. This will prevent your Custom Resource from being
131120
deleted until you remove the finalizer (ie, after your cleanup logic has successfully run). For more information, see the
132121
[official Kubernetes documentation on finalizers](https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#finalizers).
@@ -137,105 +126,104 @@ The following is a snippet from the controller file under `pkg/controller/memcac
137126
138127
```Go
139128
import (
140-
...
141-
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
129+
...
130+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
142131
)
143132
144133
const memcachedFinalizer = "finalizer.cache.example.com"
145134
146-
func (r *ReconcileMemcached) Reconcile(request reconcile.Request) (reconcile.Result, error) {
147-
reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
148-
reqLogger.Info("Reconciling Memcached")
149-
150-
// Fetch the Memcached instance
151-
memcached := &cachev1alpha1.Memcached{}
152-
err := r.client.Get(context.TODO(), request.NamespacedName, memcached)
153-
if err != nil {
154-
if errors.IsNotFound(err) {
155-
// Request object not found, could have been deleted after reconcile request.
156-
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
157-
// Return and don't requeue
158-
reqLogger.Info("Memcached resource not found. Ignoring since object must be deleted.")
159-
return reconcile.Result{}, nil
160-
}
161-
// Error reading the object - requeue the request.
162-
reqLogger.Error(err, "Failed to get Memcached.")
163-
return reconcile.Result{}, err
164-
}
165-
166-
...
167-
168-
// Check if the Memcached instance is marked to be deleted, which is
169-
// indicated by the deletion timestamp being set.
170-
isMemcachedMarkedToBeDeleted := memcached.GetDeletionTimestamp() != nil
171-
if isMemcachedMarkedToBeDeleted {
172-
if contains(memcached.GetFinalizers(), memcachedFinalizer) {
173-
// Run finalization logic for memcachedFinalizer. If the
174-
// finalization logic fails, don't remove the finalizer so
175-
// that we can retry during the next reconciliation.
176-
if err := r.finalizeMemcached(reqLogger, memcached); err != nil {
177-
return reconcile.Result{}, err
178-
}
179-
180-
// Remove memcachedFinalizer. Once all finalizers have been
181-
// removed, the object will be deleted.
182-
controllerutil.RemoveFinalizer(memcached, memcachedFinalizer)
183-
err := r.client.Update(context.TODO(), memcached)
184-
if err != nil {
185-
return reconcile.Result{}, err
186-
}
187-
}
188-
return reconcile.Result{}, nil
189-
}
190-
191-
// Add finalizer for this CR
192-
if !contains(memcached.GetFinalizers(), memcachedFinalizer) {
193-
if err := r.addFinalizer(reqLogger, memcached); err != nil {
194-
return reconcile.Result{}, err
195-
}
196-
}
197-
198-
...
199-
200-
return reconcile.Result{}, nil
135+
func (r *MemcachedReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
136+
ctx := context.Background()
137+
reqLogger := r.log.WithValues("memcached", req.NamespacedName)
138+
reqLogger.Info("Reconciling Memcached")
139+
140+
// Fetch the Memcached instance
141+
memcached := &cachev1alpha1.Memcached{}
142+
err := r.Get(ctx, req.NamespacedName, memcached)
143+
if err != nil {
144+
if errors.IsNotFound(err) {
145+
// Request object not found, could have been deleted after reconcile request.
146+
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
147+
// Return and don't requeue
148+
reqLogger.Info("Memcached resource not found. Ignoring since object must be deleted.")
149+
return ctrl.Result{}, nil
150+
}
151+
// Error reading the object - requeue the request.
152+
reqLogger.Error(err, "Failed to get Memcached.")
153+
return ctrl.Result{}, err
154+
}
155+
156+
...
157+
158+
// Check if the Memcached instance is marked to be deleted, which is
159+
// indicated by the deletion timestamp being set.
160+
isMemcachedMarkedToBeDeleted := memcached.GetDeletionTimestamp() != nil
161+
if isMemcachedMarkedToBeDeleted {
162+
if contains(memcached.GetFinalizers(), memcachedFinalizer) {
163+
// Run finalization logic for memcachedFinalizer. If the
164+
// finalization logic fails, don't remove the finalizer so
165+
// that we can retry during the next reconciliation.
166+
if err := r.finalizeMemcached(reqLogger, memcached); err != nil {
167+
return ctrl.Result{}, err
168+
}
169+
170+
// Remove memcachedFinalizer. Once all finalizers have been
171+
// removed, the object will be deleted.
172+
controllerutil.RemoveFinalizer(memcached, memcachedFinalizer)
173+
err := r.Update(ctx, memcached)
174+
if err != nil {
175+
return ctrl.Result{}, err
176+
}
177+
}
178+
return ctrl.Result{}, nil
179+
}
180+
181+
// Add finalizer for this CR
182+
if !contains(memcached.GetFinalizers(), memcachedFinalizer) {
183+
if err := r.addFinalizer(reqLogger, memcached); err != nil {
184+
return ctrl.Result{}, err
185+
}
186+
}
187+
188+
...
189+
190+
return ctrl.Result{}, nil
201191
}
202192
203-
func (r *ReconcileMemcached) finalizeMemcached(reqLogger logr.Logger, m *cachev1alpha1.Memcached) error {
204-
// TODO(user): Add the cleanup steps that the operator
205-
// needs to do before the CR can be deleted. Examples
206-
// of finalizers include performing backups and deleting
207-
// resources that are not owned by this CR, like a PVC.
208-
reqLogger.Info("Successfully finalized memcached")
209-
return nil
193+
func (r *MemcachedReconciler) finalizeMemcached(reqLogger logr.Logger, m *cachev1alpha1.Memcached) error {
194+
// TODO(user): Add the cleanup steps that the operator
195+
// needs to do before the CR can be deleted. Examples
196+
// of finalizers include performing backups and deleting
197+
// resources that are not owned by this CR, like a PVC.
198+
reqLogger.Info("Successfully finalized memcached")
199+
return nil
210200
}
211201
212-
func (r *ReconcileMemcached) addFinalizer(reqLogger logr.Logger, m *cachev1alpha1.Memcached) error {
213-
reqLogger.Info("Adding Finalizer for the Memcached")
214-
controllerutil.AddFinalizer(m, memcachedFinalizer)
215-
216-
// Update CR
217-
err := r.client.Update(context.TODO(), m)
218-
if err != nil {
219-
reqLogger.Error(err, "Failed to update Memcached with finalizer")
220-
return err
221-
}
222-
return nil
202+
func (r *MemcachedReconciler) addFinalizer(reqLogger logr.Logger, m *cachev1alpha1.Memcached) error {
203+
reqLogger.Info("Adding Finalizer for the Memcached")
204+
controllerutil.AddFinalizer(m, memcachedFinalizer)
205+
206+
// Update CR
207+
err := r.Update(context.TODO(), m)
208+
if err != nil {
209+
reqLogger.Error(err, "Failed to update Memcached with finalizer")
210+
return err
211+
}
212+
return nil
223213
}
224214
225215
func contains(list []string, s string) bool {
226-
for _, v := range list {
227-
if v == s {
228-
return true
229-
}
230-
}
231-
return false
216+
for _, v := range list {
217+
if v == s {
218+
return true
219+
}
220+
}
221+
return false
232222
}
233223
```
234224
235225
### Leader election
236226
237-
> **// TODO:** Update this section to remove `leader-for-life` option? Since it's no longer the default.
238-
239227
During the lifecycle of an operator it's possible that there may be more than 1 instance running at any given time e.g when rolling out an upgrade for the operator.
240228
In such a scenario it is necessary to avoid contention between multiple operator instances via leader election so that only one leader instance handles the reconciliation while the other instances are inactive but ready to take over when the leader steps down.
241229
@@ -254,18 +242,18 @@ A call to `leader.Become()` will block the operator as it retries until it can b
254242
255243
```Go
256244
import (
257-
...
258-
"github.com/operator-framework/operator-sdk/pkg/leader"
245+
...
246+
"github.com/operator-framework/operator-sdk/pkg/leader"
259247
)
260248
261249
func main() {
262-
...
263-
err = leader.Become(context.TODO(), "memcached-operator-lock")
264-
if err != nil {
265-
log.Error(err, "Failed to retry for leader lock")
266-
os.Exit(1)
267-
}
268-
...
250+
...
251+
err = leader.Become(context.TODO(), "memcached-operator-lock")
252+
if err != nil {
253+
log.Error(err, "Failed to retry for leader lock")
254+
os.Exit(1)
255+
}
256+
...
269257
}
270258
```
271259
If the operator is not running inside a cluster `leader.Become()` will simply return without error to skip the leader election since it can't detect the operator's namespace.
@@ -276,19 +264,19 @@ The leader-with-lease approach can be enabled via the [Manager Options][manager_
276264
277265
```Go
278266
import (
279-
...
280-
"sigs.k8s.io/controller-runtime/pkg/manager"
267+
...
268+
"sigs.k8s.io/controller-runtime/pkg/manager"
281269
)
282270
283271
func main() {
284-
...
285-
opts := manager.Options{
286272
...
287-
LeaderElection: true,
288-
LeaderElectionID: "memcached-operator-lock"
289-
}
290-
mgr, err := manager.New(cfg, opts)
291-
...
273+
opts := manager.Options{
274+
...
275+
LeaderElection: true,
276+
LeaderElectionID: "memcached-operator-lock"
277+
}
278+
mgr, err := manager.New(cfg, opts)
279+
...
292280
}
293281
```
294282
@@ -300,9 +288,9 @@ When the operator is not running in a cluster, the Manager will return an error
300288
[deployments_register]: https://github.com/kubernetes/api/blob/master/apps/v1/register.go#L41
301289
[runtime_package]: https://godoc.org/k8s.io/apimachinery/pkg/runtime
302290
[scheme_builder]: https://godoc.org/sigs.k8s.io/controller-runtime/pkg/scheme#Builder
303-
[metrics_doc]: /docs/golang/metrics/operator-sdk-monitoring/
291+
[metrics_doc]: https://book.kubebuilder.io/reference/metrics.html
304292
[lease_split_brain]: https://github.com/kubernetes/client-go/blob/30b06a83d67458700a5378239df6b96948cb9160/tools/leaderelection/leaderelection.go#L21-L24
305293
[leader_for_life]: https://godoc.org/github.com/operator-framework/operator-sdk/pkg/leader
306294
[leader_with_lease]: https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg/leaderelection
307295
[pod_eviction_timeout]: https://kubernetes.io/docs/reference/command-line-tools-reference/kube-controller-manager/#options
308-
[manager_options]: https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg/manager#Options
296+
[manager_options]: https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg/manager#Options

0 commit comments

Comments
 (0)