1
0
mirror of https://github.com/openshift/openshift-docs.git synced 2026-02-07 09:46:53 +01:00
Files
openshift-docs/modules/building-memcached-operator-using-osdk.adoc

501 lines
13 KiB
Plaintext

// Module included in the following assemblies:
//
// * operators/operator_sdk/osdk-getting-started.adoc
[id="building-memcached-operator-using-osdk_{context}"]
= Building a Go-based Operator using the Operator SDK
The Operator SDK makes it easier to build Kubernetes native applications, a
process that can require deep, application-specific operational knowledge. The
SDK not only lowers that barrier, but it also helps reduce the amount of
boilerplate code needed for many common management capabilities, such as
metering or monitoring.
This procedure walks through an example of building a simple Memcached Operator
using tools and libraries provided by the SDK.
.Prerequisites
- 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/v1beta2` API group), for example {product-title} {product-version}
- Access to the cluster using an account with `cluster-admin` permissions
- OpenShift CLI (`oc`) v{product-version}+ installed
.Procedure
. *Create a new project.*
+
Use the CLI to create a new `memcached-operator` project:
+
[source,terminal]
----
$ mkdir -p $GOPATH/src/github.com/example-inc/
----
+
[source,terminal]
----
$ cd $GOPATH/src/github.com/example-inc/
----
+
[source,terminal]
----
$ operator-sdk new memcached-operator
----
+
[source,terminal]
----
$ cd memcached-operator
----
. *Add a new Custom Resource Definition (CRD).*
.. Use the CLI to add a new CRD API called `Memcached`, with `APIVersion` set to
`cache.example.com/v1apha1` and `Kind` set to `Memcached`:
+
[source,terminal]
----
$ operator-sdk add api \
--api-version=cache.example.com/v1alpha1 \
--kind=Memcached
----
+
This scaffolds the Memcached resource API under `pkg/apis/cache/v1alpha1/`.
.. Modify the spec and status of the `Memcached` Custom Resource (CR) at the
`pkg/apis/cache/v1alpha1/memcached_types.go` file:
+
[source,go]
----
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.go` file, always run the following command to
update the generated code for that resource type:
+
[source,terminal]
----
$ operator-sdk generate k8s
----
. *Optional: Add custom validation to your CRD.*
+
OpenAPI v3.0 schemas are added to CRD manifests in the `spec.validation` block 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.go` file is
generated. This file contains the Go representation of this validation block if
the `+k8s:openapi-gen=true annotation` is present above the `Kind` type
declaration, which is present by default. This auto-generated code is your Go
`Kind` type's OpenAPI model, 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:validation` prefix. For example, adding an enum-type specification
can be done by adding the following marker:
+
[source,go]
----
// +kubebuilder:validation:Enum=Lion;Wolf;Dragon
type Alias string
----
+
Usage of markers in API code is discussed in the Kubebuilder
link:https://book.kubebuilder.io/reference/generating-crd.html[Generating CRDs]
and link:https://book.kubebuilder.io/reference/markers.html[Markers for Config/Code Generation]
documentation. A full list of OpenAPIv3 validation markers is also available in
the Kubebuilder
link:https://book.kubebuilder.io/reference/markers/crd-validation.html[CRD Validation]
documentation.
+
If you add any custom validations, run the following command to update the
OpenAPI validation section in the CRD's
`deploy/crds/cache.example.com_memcacheds_crd.yaml` file:
+
[source,terminal]
----
$ operator-sdk generate crds
----
+
.Example generated YAML
[source,yaml]
----
spec:
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 Memcached
resource:
+
[source,terminal]
----
$ operator-sdk add controller \
--api-version=cache.example.com/v1alpha1 \
--kind=Memcached
----
+
This scaffolds a new Controller implementation under
`pkg/controller/memcached/`.
.. For this example, replace the generated controller file
`pkg/controller/memcached/memcached_controller.go` with the
link:https://github.com/operator-framework/operator-sdk/blob/master/example/memcached-operator/memcached_controller.go.tmpl[example implementation].
+
The example controller executes the following reconciliation logic for each
`Memcached` CR:
+
--
* Create a Memcached Deployment if it does not exist.
* Ensure that the Deployment size is the same as specified by the `Memcached` CR spec.
* Update the `Memcached` CR 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.go` file to see how the
Controller watches resources.
+
The first watch is for the Memcached type as the primary resource. For each Add,
Update, or Delete event, the reconcile loop is sent a reconcile `Request` (a
`<namespace>:<name>` key) for that Memcached object:
+
[source,go]
----
err := c.Watch(
&source.Kind{Type: &cachev1alpha1.Memcached{}}, &handler.EnqueueRequestForObject{})
----
+
The next watch is for Deployments, but the event handler maps each event to a
reconcile `Request` for the owner of the Deployment. In this case, this is the
Memcached object for which the Deployment was created. This allows the
controller to watch Deployments as a secondary resource:
+
[source,go]
----
err := c.Watch(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &cachev1alpha1.Memcached{},
})
----
.. Every Controller has a Reconciler object with a `Reconcile()` method that
implements the reconcile loop. The reconcile loop is passed the `Request`
argument which is a `<namespace>:<name>` key used to lookup the primary resource
object, Memcached, from the cache:
+
[source,go]
----
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 `Reconcile()` the reconcile `Request` may be
requeued and the loop may be triggered again:
+
[source,go]
----
// 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
----
[id="building-memcached-operator-using-osdk-build-and-run_{context}"]
. *Build and run the Operator.*
.. Before running the Operator, the CRD must be registered with the Kubernetes API
server:
+
[source,terminal]
----
$ 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-operator` image and push it to a registry:
+
[source,terminal]
----
$ 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:
+
[source,terminal]
----
$ sed -i 's|REPLACE_IMAGE|quay.io/example/memcached-operator:v0.0.1|g' deploy/operator.yaml
----
.... Ensure you have an account on link:https://quay.io[Quay.io] for the next step,
or substitute your preferred container registry. On the registry,
link:https://quay.io/new/[create a new public image] repository named
`memcached-operator`.
.... Push the image to the registry:
+
[source,terminal]
----
$ podman push quay.io/example/memcached-operator:v0.0.1
----
.... Setup RBAC and deploy `memcached-operator`:
+
[source,terminal]
----
$ oc create -f deploy/role.yaml
----
+
[source,terminal]
----
$ oc create -f deploy/role_binding.yaml
----
+
[source,terminal]
----
$ oc create -f deploy/service_account.yaml
----
+
[source,terminal]
----
$ oc create -f deploy/operator.yaml
----
.... Verify that `memcached-operator` is up and running:
+
[source,terminal]
----
$ oc get deployment
----
+
.Example output
[source,terminal]
----
NAME 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`:
+
[source,terminal]
----
$ operator-sdk run --local --namespace=default
----
+
You can use a specific `kubeconfig` using the flag
`--kubeconfig=<path/to/kubeconfig>`.
. *Verify that the Operator can deploy a Memcached application* by creating a
Memcached CR.
.. Create the example `Memcached` CR that was generated at
`deploy/crds/cache_v1alpha1_memcached_cr.yaml`.
.. View the file:
+
[source,terminal]
----
$ cat deploy/crds/cache_v1alpha1_memcached_cr.yaml
----
+
.Example output
[source,terminal]
----
apiVersion: "cache.example.com/v1alpha1"
kind: "Memcached"
metadata:
name: "example-memcached"
spec:
size: 3
----
.. Create the object:
+
[source,terminal]
----
$ oc apply -f deploy/crds/cache_v1alpha1_memcached_cr.yaml
----
.. Ensure that `memcached-operator` creates the Deployment for the CR:
+
[source,terminal]
----
$ oc get deployment
----
+
.Example output
[source,terminal]
----
NAME 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 status to confirm the status is updated with the
`memcached` pod names:
+
[source,terminal]
----
$ oc get pods
----
+
.Example output
[source,terminal]
----
NAME 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
----
+
[source,terminal]
----
$ oc get memcached/example-memcached -o yaml
----
+
.Example output
[source,terminal]
----
apiVersion: 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.size` field in the `memcached` CR from `3` to `4`:
+
[source,terminal]
----
$ cat deploy/crds/cache_v1alpha1_memcached_cr.yaml
----
+
.Example output
[source,terminal]
----
apiVersion: "cache.example.com/v1alpha1"
kind: "Memcached"
metadata:
name: "example-memcached"
spec:
size: 4
----
.. Apply the change:
+
[source,terminal]
----
$ oc apply -f deploy/crds/cache_v1alpha1_memcached_cr.yaml
----
.. Confirm that the Operator changes the Deployment size:
+
[source,terminal]
----
$ oc get deployment
----
+
.Example output
[source,terminal]
----
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
example-memcached 4 4 4 4 5m
----
. *Clean up the resources:*
+
[source,terminal]
----
$ oc delete -f deploy/crds/cache_v1alpha1_memcached_cr.yaml
----
+
[source,terminal]
----
$ oc delete -f deploy/crds/cache_v1alpha1_memcached_crd.yaml
----
+
[source,terminal]
----
$ oc delete -f deploy/operator.yaml
----
+
[source,terminal]
----
$ oc delete -f deploy/role.yaml
----
+
[source,terminal]
----
$ oc delete -f deploy/role_binding.yaml
----
+
[source,terminal]
----
$ oc delete -f deploy/service_account.yaml
----
.Additional resources
* For more information about OpenAPI v3.0 validation schemas in CRDs, refer to the
link:https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#specifying-a-structural-schema[Kubernetes documentation].