The operator-sdk

Let’s now explore the operator-sdk suite and create the same CRD and operator. Remember that we want to write an operator that will deploy a daemon on nodes of our cluster. It will use the DaemonSet object to deploy this daemon and we would like to be able to specify a label, to deploy the daemon only on nodes tagged with this label. We also want to be able to specify the Docker image to deploy.

Installation

Follow these steps to install the operator-sdk:

go get github.com/operator-framework/operator-sdk

cd $GOPATH/src/github.com/operator-framework/operator-sdk

make dep

make install

Start a project

Lets’s create the operator. We need to create our project under GOPATH:

mkdir -p $GOPATH/src/mydomain.com/mygroup2 && cd $_

then initiate the project:

operator-sdk new app-operator --api-version=mygroup2.mydomain.com/v1beta1 --kind=GenericDaemon

Write some code

We need to modify the structure of our GenericDaemon to add the necessary fields for our object.

// pkg/apis/mygroup2/v1beta1/types.go

[...]

type GenericDaemonSpec struct {

// Label is the value of the 'daemon=' label to set on a node that should run the daemon

Label string `json:"label"`

// Image is the Docker image to run for the daemon

Image string `json:"image"`

}

type GenericDaemonStatus struct {

// Count is the number of nodes the daemon is deployed to

Count int32 `json:"count"`

}

then create the reconcile handler:

/// pkg/stub/handler.go

func (h *Handler) Handle(ctx context.Context, event sdk.Event) error {

switch o := event.Object.(type) {

case *v1beta1.GenericDaemon:

genericdaemon := o

logrus.Info("handle GenericDaemon")

ds := newDaemonset(o)

err := sdk.Create(ds)

if err != nil && !errors.IsAlreadyExists(err) {

logrus.Errorf("Failed to create daemonset : %v", err)

return err

}

err = sdk.Get(ds)

if err != nil {

logrus.Errorf("failed to get daemonset: %v", err)

return err

}

if genericdaemon.Status.Count != ds.Status.NumberReady {

genericdaemon.Status.Count = ds.Status.NumberReady

err = sdk.Update(genericdaemon)

if err != nil {

logrus.Errorf("failed to update genericdaemon status: %v", err)

return err

}

}

}

return nil

} func newDaemonset(cr *v1beta1.GenericDaemon) *appsv1.DaemonSet {

return &appsv1.DaemonSet{

TypeMeta: metav1.TypeMeta{

Kind: "DaemonSet",

APIVersion: "apps/v1",

},

ObjectMeta: metav1.ObjectMeta{

Name: cr.Name + "-daemonset",

Namespace: cr.Namespace,

OwnerReferences: []metav1.OwnerReference{

*metav1.NewControllerRef(cr, schema.GroupVersionKind{

Group: v1beta1.SchemeGroupVersion.Group,

Version: v1beta1.SchemeGroupVersion.Version,

Kind: "GenericDaemon",

}),

},

},

Spec: appsv1.DaemonSetSpec{

Selector: &metav1.LabelSelector{

MatchLabels: map[string]string{"daemonset": cr.Name + "-daemonset"},

},

Template: corev1.PodTemplateSpec{

ObjectMeta: metav1.ObjectMeta{

Labels: map[string]string{"daemonset": cr.Name + "-daemonset"},

},

Spec: corev1.PodSpec{

NodeSelector: map[string]string{"daemon": cr.Spec.Label},

Containers: []corev1.Container{

{

Name: "genericdaemon",

Image: cr.Spec.Image,

},

},

},

},

},

}

}

Deploy and play

That’s all! We can now rebuild the operator and image and push the image and finally deploy our project:

operator-sdk build mydockerid/genericdaemon2

docker push mydockerid/genericdaemon2

kubectl create -f deploy/rbac.yaml

kubectl create -f deploy/crd.yaml

kubectl create -f deploy/operator.yaml

At this point, the operator should be running:

$ kubectl get pods

NAME READY STATUS RESTARTS AGE

app-operator-65bf4777f7-8lx9m 1/1 Running 0 4s



We can now personalize a generated GenericDaemon sample:

// deploy/cr.yaml

apiVersion: mygroup2.mydomain.com/v1beta1

kind: GenericDaemon

metadata:

name: example

spec:

image: httpd

label: http

and create it:

$ kubectl apply -f deploy/cr.yaml $ kubectl get genericdaemon

NAME AGE

example 5s $ kubectl get daemonset

NAME READY [...] NODE SELECTOR

example-daemonset 0 [...] daemon=http $ kubectl describe genericdaemons example

[...]

Spec:

Image: httpd

Label: http

Status:

Count: 0 $ kubectl label nodes mynode1 daemon=http $ kubectl get daemonset

NAME READY [...] NODE SELECTOR

example-daemonset 1 [...] daemon=http $ kubectl describe genericdaemons example

[...]

Spec:

Image: httpd

Label: http

Status:

Count: 1

Conclusion

The operator-sdk usage is comparable to kubebuilder. It introduces a new API on top of the native Kubernetes API, which can be confusing if you are used to this one, but simplifies the code and the concepts.

This is a pre-alpha release, which lacks some generated tests for our operator code.