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"
Retriever
+ Handler
is a controller
operator
is also a controller
.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:
operator
and controller
, now only controller
(this is at library level, you can continue creating controllers/operators).Delete
event removed because wasn't reliable (Check Garbage-collection
section).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)
Kooper at this moment uses as base v1.17
. But check the integration test in CI to know the supported versions.
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
Kooper embraces simplicity over optimization, it favors small APIs, simplicity and easy to use/test methods. Some examples:
On the examples folder you have different examples, like regular controllers, operators, metrics based, leader election, multiresource type controllers...
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:
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:
Retriever
: The core retriever it needs to implement list (list objects), and watch, subscribe to object changes.RetrieverFromListerWatcher
: Converts a Kubernetes ListerWatcher into a kooper Retriever.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).
Kooper handles all the events on the same handler:
Handler
: The interface that knows how to handle kubernetes objects.HandlerFunc
: A helper that gets a Handler
from a function so you don't need to create a new type to define your Handler
.The Handler
is an interface so you can use the middleware/wrapper/decorator pattern to extend (e.g add custom metrics).
The controller is the component that uses the Handler
and Retriever
to start a feedback loop controller process:
controller.Retriever.List
to get all the resources and pass them to the controller.Handler
.controller.Handler
for every change done in the resources using the controller.Retriever.Watcher
.controller.Handler
with all resources in case we have missed a Watch
event.Check Leader election.
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:
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:
{namespace}/{name}
scheme (ignoring types).Handler
in multiple controllers.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:
DisableResync
), sometimes this can be useful on secondary resources (only act on changes).