This procedure walks through an example of building a simple Memcached Operator using tools and libraries provided by the SDK.
-
Operator SDK CLI installed on the development workstation
-
Operator Lifecycle Manager (OLM) installed on a Kubernetes-based cluster (v1.8 or above to support the
apps/v1beta2API group), for example {product-title} {product-version} -
Access to the cluster using an account with
cluster-adminpermissions -
OpenShift CLI (
oc) v{product-version}+ installed
-
Create a new project.
Use the CLI to create a new
memcached-operatorproject:$ mkdir -p $GOPATH/src/github.com/example-inc/$ cd $GOPATH/src/github.com/example-inc/$ operator-sdk new memcached-operator$ cd memcached-operator -
Add a new custom resource definition (CRD).
-
Use the CLI to add a new CRD API called
Memcached, withAPIVersionset tocache.example.com/v1apha1andKindset toMemcached:$ operator-sdk add api \ --api-version=cache.example.com/v1alpha1 \ --kind=MemcachedThis scaffolds the Memcached resource API under
pkg/apis/cache/v1alpha1/. -
Modify the spec and status of the
Memcachedcustom resource (CR) at thepkg/apis/cache/v1alpha1/memcached_types.gofile:type MemcachedSpec struct { // Size is the size of the memcached deployment Size int32 `json:"size"` } type MemcachedStatus struct { // Nodes are the names of the memcached pods Nodes []string `json:"nodes"` }
-
After modifying the
*_types.gofile, always run the following command to update the generated code for that resource type:$ operator-sdk generate k8s
-
-
Optional: Add custom validation to your CRD.
OpenAPI v3.0 schemas are added to CRD manifests in the
spec.validationblock when the manifests are generated. This validation block allows Kubernetes to validate the properties in a Memcached CR when it is created or updated.Additionally, a
pkg/apis/<group>/<version>/zz_generated.openapi.gofile is generated. This file contains the Go representation of this validation block if the+k8s:openapi-gen=true annotationis present above theKindtype declaration, which is present by default. This auto-generated code is the OpenAPI model of your GoKindtype, from which you can create a full OpenAPI Specification and generate a client.As an Operator author, you can use Kubebuilder markers (annotations) to configure custom validations for your API. These markers must always have a
+kubebuilder:validationprefix. For example, adding an enum-type specification can be done by adding the following marker:// +kubebuilder:validation:Enum=Lion;Wolf;Dragon type Alias string
Usage of markers in API code is discussed in the Kubebuilder Generating CRDs and Markers for Config/Code Generation documentation. A full list of OpenAPIv3 validation markers is also available in the Kubebuilder CRD Validation documentation.
If you add any custom validations, run the following command to update the OpenAPI validation section in the
deploy/crds/cache.example.com_memcacheds_crd.yamlfile for the CRD:$ operator-sdk generate crdsExample generated YAMLspec: validation: openAPIV3Schema: properties: spec: properties: size: format: int32 type: integer
-
Add a new controller.
-
Add a new controller to the project to watch and reconcile the
Memcachedresource:$ operator-sdk add controller \ --api-version=cache.example.com/v1alpha1 \ --kind=MemcachedThis scaffolds a new controller implementation under
pkg/controller/memcached/. -
For this example, replace the generated controller file
pkg/controller/memcached/memcached_controller.gowith the example implementation.The example controller executes the following reconciliation logic for each
Memcachedresource:-
Create a Memcached deployment if it does not exist.
-
Ensure that the Deployment size is the same as specified by the
MemcachedCR spec. -
Update the
Memcachedresource status with the names of the Memcached pods.
The next two sub-steps inspect how the controller watches resources and how the reconcile loop is triggered. You can skip these steps to go directly to building and running the Operator.
-
-
Inspect the controller implementation at the
pkg/controller/memcached/memcached_controller.gofile to see how the controller watches resources.The first watch is for the
Memcachedtype as the primary resource. For each add, update, or delete event, the reconcile loop is sent a reconcileRequest(a<namespace>:<name>key) for thatMemcachedobject:err := c.Watch( &source.Kind{Type: &cachev1alpha1.Memcached{}}, &handler.EnqueueRequestForObject{})
The next watch is for
Deploymentobjects, but the event handler maps each event to a reconcileRequestfor the owner of the deployment. In this case, this is theMemcachedobject for which the deployment was created. This allows the controller to watch deployments as a secondary resource:err := c.Watch(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForOwner{ IsController: true, OwnerType: &cachev1alpha1.Memcached{}, })
-
Every controller has a
Reconcilerobject with aReconcile()method that implements the reconcile loop. The reconcile loop is passed theRequestargument which is a<namespace>:<name>key used to lookup the primary resource object,Memcached, from the cache:func (r *ReconcileMemcached) Reconcile(request reconcile.Request) (reconcile.Result, error) { // Lookup the Memcached instance for this reconcile request memcached := &cachev1alpha1.Memcached{} err := r.client.Get(context.TODO(), request.NamespacedName, memcached) ... }
Based on the return value of the
Reconcile()function, the reconcileRequestmight be requeued, and the loop might be 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
-
-
Build and run the Operator.
-
Before running the Operator, the CRD must be registered with the Kubernetes API server:
$ oc create \ -f deploy/crds/cache_v1alpha1_memcached_crd.yaml -
After registering the CRD, there are two options for running the Operator:
-
As a Deployment inside a Kubernetes cluster
-
As Go program outside a cluster
Choose one of the following methods.
-
Option A: Running as a deployment inside the cluster.
-
Build the
memcached-operatorimage and push it to a registry:$ operator-sdk build quay.io/example/memcached-operator:v0.0.1 -
The deployment manifest is generated at
deploy/operator.yaml. Update the deployment image as follows since the default is just a placeholder:$ sed -i 's|REPLACE_IMAGE|quay.io/example/memcached-operator:v0.0.1|g' deploy/operator.yaml -
Ensure you have an account on Quay.io for the next step, or substitute your preferred container registry. On the registry, create a new public image repository named
memcached-operator. -
Push the image to the registry:
$ podman push quay.io/example/memcached-operator:v0.0.1 -
Set up RBAC and create the
memcached-operatormanifests:$ oc create -f deploy/role.yaml$ oc create -f deploy/role_binding.yaml$ oc create -f deploy/service_account.yaml$ oc create -f deploy/operator.yaml -
Verify that the
memcached-operatordeploy is up and running:$ oc get deploymentExample outputNAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE memcached-operator 1 1 1 1 1m
-
-
Option B: Running locally outside the cluster.
This method is preferred during development cycle to deploy and test faster.
Run the Operator locally with the default Kubernetes configuration file present at
$HOME/.kube/config:$ operator-sdk run --local --namespace=defaultYou can use a specific
kubeconfigusing the flag--kubeconfig=<path/to/kubeconfig>.
-
-
-
Verify that the Operator can deploy a Memcached application by creating a
MemcachedCR.-
Create the example
MemcachedCR that was generated atdeploy/crds/cache_v1alpha1_memcached_cr.yaml. -
View the file:
$ cat deploy/crds/cache_v1alpha1_memcached_cr.yamlExample outputapiVersion: "cache.example.com/v1alpha1" kind: "Memcached" metadata: name: "example-memcached" spec: size: 3 -
Create the object:
$ oc apply -f deploy/crds/cache_v1alpha1_memcached_cr.yaml -
Ensure that
memcached-operatorcreates the deployment for the CR:$ oc get deploymentExample outputNAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE memcached-operator 1 1 1 1 2m example-memcached 3 3 3 3 1m -
Check the pods and CR to confirm the CR status is updated with the pod names:
$ oc get podsExample outputNAME READY STATUS RESTARTS AGE example-memcached-6fd7c98d8-7dqdr 1/1 Running 0 1m example-memcached-6fd7c98d8-g5k7v 1/1 Running 0 1m example-memcached-6fd7c98d8-m7vn7 1/1 Running 0 1m memcached-operator-7cc7cfdf86-vvjqk 1/1 Running 0 2m$ oc get memcached/example-memcached -o yamlExample outputapiVersion: cache.example.com/v1alpha1 kind: Memcached metadata: clusterName: "" creationTimestamp: 2018-03-31T22:51:08Z generation: 0 name: example-memcached namespace: default resourceVersion: "245453" selfLink: /apis/cache.example.com/v1alpha1/namespaces/default/memcacheds/example-memcached uid: 0026cc97-3536-11e8-bd83-0800274106a1 spec: size: 3 status: nodes: - example-memcached-6fd7c98d8-7dqdr - example-memcached-6fd7c98d8-g5k7v - example-memcached-6fd7c98d8-m7vn7
-
-
Verify that the Operator can manage a deployed Memcached application by updating the size of the deployment.
-
Change the
spec.sizefield in thememcachedCR from3to4:$ cat deploy/crds/cache_v1alpha1_memcached_cr.yamlExample outputapiVersion: "cache.example.com/v1alpha1" kind: "Memcached" metadata: name: "example-memcached" spec: size: 4 -
Apply the change:
$ oc apply -f deploy/crds/cache_v1alpha1_memcached_cr.yaml -
Confirm that the Operator changes the deployment size:
$ oc get deploymentExample outputNAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE example-memcached 4 4 4 4 5m
-
-
Clean up the resources:
$ oc delete -f deploy/crds/cache_v1alpha1_memcached_cr.yaml$ oc delete -f deploy/crds/cache_v1alpha1_memcached_crd.yaml$ oc delete -f deploy/operator.yaml$ oc delete -f deploy/role.yaml$ oc delete -f deploy/role_binding.yaml$ oc delete -f deploy/service_account.yaml
-
For more information about OpenAPI v3.0 validation schemas in CRDs, refer to the Kubernetes documentation.