Slide 1

Slide 1 text

Kubernetes Operator with Go Campinas, SP, MAR, 2020 Matheus Moraes Sensedia Software Engineer Claudio Oliveira Sensedia Lead Solutions Architect

Slide 2

Slide 2 text

Agenda Kubernetes Kubernetes Controller Operator pattern Custom Resources Definition (CRD’s) The Operator Framework Best Practices

Slide 3

Slide 3 text

Kubernetes

Slide 4

Slide 4 text

Everything in kubernetes is event, it means the things happens asynchronously

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

and controllers react to these events and do something important

Slide 7

Slide 7 text

k8s Controllers

Slide 8

Slide 8 text

kubernetes controllers is one way to extend k8s features

Slide 9

Slide 9 text

Control loop Kubernetes-native application

Slide 10

Slide 10 text

Introducing APIrator Mock for developers Easy deployments on k8s Useful for tests Creates and prepares instances for you, quickly and effective

Slide 11

Slide 11 text

github.com/apirator/apirator

Slide 12

Slide 12 text

Introducing CRD...

Slide 13

Slide 13 text

What is a CRD? CRD is an acronym for Custom Resource Definition

Slide 14

Slide 14 text

but wait…..What is Custom Resources???

Slide 15

Slide 15 text

What is a CR? A resource is an endpoint in the Kubernetes API that stores a collection of API objects of a certain kind POD is built-in Kind Deployment is built-in Kind Service is built-in Kind APIMock is not a built-in kind is a Custom Resource

Slide 16

Slide 16 text

CRD is the way to tell instructions to kubernetes some Custom Resources properties like “template”

Slide 17

Slide 17 text

CRD example https://github.com/apirator/apirator/blob/develop/deploy/crds/apirator.io_apimocks_crd.yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: apimocks.apirator.io spec: version: v1alpha1 group: apirator.io names: kind: APIMock listKind: APIMockList plural: apimocks singular: apimock scope: Namespaced validation: openAPIV3Schema: type: object properties: spec: type: object properties: definition: type: string description: 'OpenAPI Specification' apiVersion: apirator.io/v1alpha1 kind: APIMock metadata: name: example-apimock spec: definition: | openapi: "3.0.0" info: title: Simple API overview version: 2.0.0 1 1 2 2 3 3 4 4 CR example

Slide 18

Slide 18 text

{ Deploy CRD Deploy CRD to the cluster kubectl apply -f apimock.yaml kubectl command line

Slide 19

Slide 19 text

almost there, controllers and CRDs… and about operator pattern

Slide 20

Slide 20 text

Operator Pattern “An Operator is a Controller that uses a CRD to encapsulate operational knowledge for a specific application in an algorithmic and automated form. The Operator pattern allows us to extend the Controller pattern from the preceding chapter for more flexibility and greater expressiveness.” from Kubernetes Patterns Book https://learning.oreilly.com/library/view/kubernetes-patterns/9781492050278/ch23.html#Operator

Slide 21

Slide 21 text

in other words, the operator pattern encapsulates our sysadmin in a software… because sysadmin maybe get some tired and our code in golang is tireless

Slide 22

Slide 22 text

Options to build Operators!!! Metacontroller from GCP kubebuilder from SIG API Machinery Operator Framework from CoreOS/RedHat

Slide 23

Slide 23 text

Why Operator Framework??? golang based scaffolding projects in golang operator-cli that helps to create image, deploy and test automatically creation of boilerplate code to establish connect on kubernetes apply best practices to run custom controllers reasonable documentation

Slide 24

Slide 24 text

CLI operator-sdk Scaffold a new project operator-sdk new apirator-operator --repo=github.com/apirator/apirator Create a new Custom Resource operator-sdk add api --api-version=apirator.io/v1alpha1 --kind=APIMock Create a new Custom Controller operator-sdk add controller --api-version=apirator.io/v1alpha1 --kind=APIMock Generates our Custom Resource Definition operator-sdk generate crds

Slide 25

Slide 25 text

Let’s GO!!!

Slide 26

Slide 26 text

Our CRD is a golang struct type APIMock struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` Spec APIMockSpec `json:"spec,omitempty"` Status APIMockStatus `json:"status,omitempty"` } type APIMockSpec struct { // OpenAPI Specification Definition string `json:"definition,omitempty"` } apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: apimocks.apirator.io spec: additionalPrinterColumns: # omitted version: v1alpha1 group: apirator.io names: kind: APIMock listKind: APIMockList plural: apimocks singular: apimock scope: Namespaced validation: openAPIV3Schema: type: object properties: spec: type: object properties: definition: type: string description: 'OpenAPI Specification' 1 2 4 2 3 5 5 4 3 pkg/apis/apirator/v1alpha1/apimock_types.go 1

Slide 27

Slide 27 text

Remember controller is an infinite loop...The reconcile function is called every time when things get changed

Slide 28

Slide 28 text

Reconcile function func (r *ReconcileAPIMock) Reconcile(request reconcile.Request) (reconcile.Result, error) { instance := &apiratorv1alpha1.APIMock{} err := r.client.Get(context.TODO(), request.NamespacedName, instance) if err != nil { if errors.IsNotFound(err) { return reconcile.Result{}, nil } return reconcile.Result{}, err } if errOas := oas.Validate(instance.Spec.Definition); errOas != nil { if err := r.markAsInvalidOAS(instance); err != nil { return reconcile.Result{}, err } return reconcile.Result{}, errOas } }

Slide 29

Slide 29 text

Requeue only if necessary

Slide 30

Slide 30 text

The request may be requeued and the reconcile loop triggered again: // Reconcile successful - don't requeue return reconcile.Result{}, nil // Reconcile failed due to error - requeue return reconcile.Result{}, err // Requeue for any reason other than error return reconcile.Result{Requeue: true}, nil // Reconcile for any reason than error after 5 seconds return reconcile.Result{RequeueAfter: time.Second*5}, nil Example: container image from Pod

Slide 31

Slide 31 text

every time check through the kubernetes object to ensure consistency

Slide 32

Slide 32 text

Resource Status depErr := r.EnsureDeployment(instance) if depErr != nil { instance.Status.Phase = apirator.ERROR err = r.client.Status().Update(context.TODO(), instance) if err != nil { /* omitted */ } } /status

Slide 33

Slide 33 text

always define Owner References for Kubernetes Garbage Collector

Slide 34

Slide 34 text

Owner References d := &v1.Deployment{ TypeMeta: metav1.TypeMeta{ Kind: "Deployment", APIVersion: "apps/v1", }, ObjectMeta: metav1.ObjectMeta{ Name: mock.GetName(), Namespace: mock.GetNamespace(), Labels: labels.LabelForAPIMock(mock), OwnerReferences: []metav1.OwnerReference{ *metav1.NewControllerRef(mock, mock.GroupVersionKind()), }, }, Spec: v1.DeploymentSpec{} apiVersion: extensions/v1beta1 kind: Deployment metadata: labels: api: example-apimock managed-by: apirator name: example-apimock namespace: oas ownerReferences: - apiVersion: apirator.io/v1alpha1 controller: true kind: APIMock name: example-apimock uid: 5ad30a02-6481-11ea-9916-42010a8000c7 spec: replicas: 1 revisionHistoryLimit: 10 selector: matchLabels: api: example-apimock managed-by: apirator

Slide 35

Slide 35 text

handle idempotency carefully, avoid duplicated objects

Slide 36

Slide 36 text

pkg/controller/apimock/apimock_controller.go var _ reconcile.Reconciler = &ReconcileAPIMock{} type ReconcileAPIMock struct { client client.Client scheme *runtime.Scheme } pkg/controller/apimock/deployment.go func (r *ReconcileAPIMock) EnsureDeployment(mock *v1alpha1.APIMock) error { svcK8s := &v1.Deployment{} err := r.client.Get(context.TODO(), types.NamespacedName{ Name: mock.GetName(), Namespace: mock.Namespace, }, svcK8s) if err != nil && errors.IsNotFound(err) { // omitted code return nil } else if err != nil { log.Error(err, "Failed to get Deployment") return err } return nil }

Slide 37

Slide 37 text

and about update objects?

Slide 38

Slide 38 text

Diff between desired and existing objects https://github.com/google/go-cmp import "github.com/google/go-cmp/cmp" if r.isOwnedBy(existing, owner) { desired.SetResourceVersion(existing.GetResourceVersion()) diff := cmp.Diff(existing, desired, options) if diff != "" { err = r.client.Update(ctx, desired) if err != nil { return errors.Wrap(err, "failed to update "+kind+" "+namespace+"/"+name) } logger.Debug(kind + " " + namespace + "/" + name + " updated") } else { logger.Debug(existing.GetSelfLink() + " unchanged") } return nil } ignoredFields = [...]string{ "ObjectMeta.SelfLink", "ObjectMeta.UID", "ObjectMeta.ResourceVersion", "ObjectMeta.Generation", "ObjectMeta.CreationTimestamp", "ObjectMeta.Finalizers", "ObjectMeta.ManagedFields", "TypeMeta.APIVersion", }

Slide 39

Slide 39 text

the Reconcile struct has a pre-built k8s config client with desired security configuration (RBAC), take this advantage. USE it!!!

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

keep the legibility and simplicity every time, it should be designed for sysadmin guys, not for the business team

Slide 42

Slide 42 text

github.com/apirator/apirator

Slide 43

Slide 43 text

No content

Slide 44

Slide 44 text

Demo time

Slide 45

Slide 45 text

Thanks! Any questions ? mfariam matheusfm matheusfm claudioed claudioed claudioed You can find us at: