spotahome / kooper

Kooper is a simple Go library to create Kubernetes operators and controllers.
https://product.spotahome.com/kooper-extending-kubernetes-made-easy-4e1edd884687
Apache License 2.0
499 stars 50 forks source link
controller framework go golang infrastructure k8s kubernetes library operator toolkit

Kooper

CI Go Report Card Apache 2 licensed GitHub release (latest SemVer) Kubernetes release

Kooper is a Go library to create simple and flexible Kubernetes controllers/operators, in a fast, decoupled and easy way.

In other words, is a small alternative to big frameworks like Kubebuilder or operator-framework.

Library refactored (v2), for v2 use import "github.com/spotahome/kooper/v2"

Features

V0 vs V2

First of all, we used v2 instead of v[01], because it changes the library as a whole, theres no backwards compatibility, v0 is stable and used in production, although you eventually will want to update to v2 becasuse v0 will not be updated.

Import with:

import "github.com/spotahome/kooper/v2"

Regarding the changes... To know all of them check the changelog but mainly we simplified everything. The most relevant changes you will need to be aware and could impact are:

Getting started

The simplest example that prints pods would be this:

    // Create our retriever so the controller knows how to get/listen for pod events.
    retr := controller.MustRetrieverFromListerWatcher(&cache.ListWatch{
        ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
            return k8scli.CoreV1().Pods("").List(options)
        },
        WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
            return k8scli.CoreV1().Pods("").Watch(options)
        },
    })

    // Our domain logic that will print all pod events.
    hand := controller.HandlerFunc(func(_ context.Context, obj runtime.Object) error {
        pod := obj.(*corev1.Pod)
        logger.Infof("Pod event: %s/%s", pod.Namespace, pod.Name)
        return nil
    })

    // Create the controller with custom configuration.
    cfg := &controller.Config{
        Name:      "example-controller",
        Handler:   hand,
        Retriever: retr,
        Logger:    logger,
    }
    ctrl, err := controller.New(cfg)
    if err != nil {
        return fmt.Errorf("could not create controller: %w", err)
    }

    // Start our controller.
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    ctrl.Run(ctx)

Kubernetes version compatibility

Kooper at this moment uses as base v1.17. But check the integration test in CI to know the supported versions.

When should I use Kooper?

Alternatives

What is the difference between kooper and alternatives like Kubebuilder or operator-framework?

Kooper embraces the Go philosophy of having small simple components/libs and use them as you wish in combination of others, instead of trying to solve every use case and imposing everything by sacriying flexbility and adding complexity.

As an example using the web applications world as reference: We could say that Kooper is more like Go HTTP router/libs, on the other side, Kubebuilder and operator-framework are like Ruby on rails/Django style frameworks.

For example Kubebuilder comes with:

Kooper instead solves most of the core controller/operator problems but as a simple, small and flexible library, and let the other problems (like admission webhooks) be solved by other libraries specialiced on that. e.g

Simplicty VS optimization

Kooper embraces simplicity over optimization, it favors small APIs, simplicity and easy to use/test methods. Some examples:

More examples

On the examples folder you have different examples, like regular controllers, operators, metrics based, leader election, multiresource type controllers...

Core concepts

Concept doesn't do a distinction between Operators and controllers, all are controllers, the difference of both is on what resources are retrieved.

A controller is based on 3 simple concepts:

Retriever

The component that lists and watch the resources the controller will handle when there is a change. Kooper comes with some helpers to create fast retrievers:

The Retriever can be based on Kubernetes base resources (Pod, Deployment, Service...) or based on CRDs, theres no distinction.

The Retriever is an interface so you can use the middleware/wrapper/decorator pattern to extend (e.g add custom metrics).

Handler

Kooper handles all the events on the same handler:

The Handler is an interface so you can use the middleware/wrapper/decorator pattern to extend (e.g add custom metrics).

Controller

The controller is the component that uses the Handler and Retriever to start a feedback loop controller process:

Other concepts

Leader election

Check Leader election.

Garbage collection

Kooper only handles the events of resources that exist, these are triggered when the resources being watched are updated or created. There is no delete event, so in order to clean the resources you have 2 ways of doing these:

Multiresource or secondary resources

Sometimes we have controllers that work on a main or primary resource and we also want to handle the events of a secondary resource that is based on the first one. For example, a deployment controller that watches the pods (secondary) that belong to the deployment (primary) handled.

After using multiresource controllers/retrievers, we though that we don't need a multiresource controller, this is not necesary becase:

The solution to these problems, is to embrace simplicity once again, and mainly is creating multiple controllers using the same Handler, each controller with a different ListerWatcher. The Handler API is easy enough to reuse it across multiple controllers, check an example. Also, this comes with extra benefits: