Operator SDK: Build Kubernetes Operators and deploy them on Red Hat OpenShift

The Operator SDK makes it simple to build Kubernetes-native applications, providing the tools to build, test, and package Operators. The SDK also helps the developer to build Operators without requiring knowledge of Kubernetes API complexities.

In this article, we will create a sample Operator for deploying a sample application based on Spring Boot and Camel. This application is a simple Camel route that uses the undertow component. After building the Operator, we will deploy it on an OpenShift cluster.

Get started

Before creating our sample Operator, we need to set up the sample application (or, you can create any similar application yourself). If you don't have Podman and Maven installed, do so now. Then, do the following:

  1. Build the sample app's image and push it to the Quay repo.
  2. Install the operator-sdk CLI from Homebrew (macOS), GitHub, or by compiling and installing from the master.
  3. Install the right Golang for your OS.
  4. Get access to an OpenShift cluster with cluster-admin permissions.
  5. Access the OpenShift CLI (oc)

You will find explanations for how to do each of these tasks by following the links.

Create the sample Operator

Now, to create our sample Operator:

  1. Create the sample-operator project:
$ mkdir -p $GOPATH/src/github.com/shailendra14k/  // rename to your account.
$ cd $GOPATH/src/github.com/shailendra14k/
$ export GO111MODULE=on
$ operator-sdk new sample-operator
$ cd sample-operator
$ go mod tidy // Install dependencies
  1. Verify that the directory structure was created:
├── build
│   ├── bin
│   │   ├── entrypoint
│   │   └── user_setup
│   └── Dockerfile
├── cmd
│   └── manager
│       └── main.go
├── deploy
│   ├── operator.yaml
│   ├── role_binding.yaml
│   ├── role.yaml
│   └── service_account.yaml
├── go.mod
├── go.sum
├── pkg
│   ├── apis
│   │   └── apis.go
│   └── controller
│       └── controller.go
├── tools.go
└── version
    └── version.go

Note: Here is more information about the SDK project's layout.

  1. Create a custom resource definition (CRD):
$ operator-sdk add api --api-version=shailendra14k.com/v1alpha1 --kind=Sample
  1. Open pkg/apis/shailendra14k/v1alpha1/sample_types.go.
  2. Update the Sample custom resource spec and Status:
type SampleSpec struct {
  Size        int32             `json:"size"`
  BodyValue   string            `json:"bodyvalue"`
  Image        string           `json:"image"`

// SampleStatus defines the observed state of Sample
type SampleStatus struct {
    Nodes []string `json:"nodes"`
  1. Run the following to update the generated code after modifying *_types.go:
$ operator-sdk generate k8s

//Generate the updated CRDs
$ operator-sdk generate crds
  1. Create a new controller to watch and reconcile the Sample resource:
$ operator-sdk add controller --api-version=shailendra14k.com/v1alpha1 --kind=Sample
  1. Replace the default pkg/controller/sample/sample_controller.go with the sample_controller.go.
  2. Build and push the Operator image to a registry:
$ operator-sdk build quay.io/shailendra14k/sample-operator:v0.1 --image-builder podman

//Verify the operator image cretaed locally
$ podman images
REPOSITORY                                                TAG      IMAGE ID       CREATED          SIZE
quay.io/shailendra14k/sample-operator                     v0.1     50fceb91c078   27 seconds ago   150 MB

// Push the image

$ podman push quay.io/shailendra14k/sample-operator:v0.1
  1. Update the default deploy/operator.yaml with the correct image details:
serviceAccountName: sample-operator
  - name: sample-operator
    # Replace this with the built image name
    image: quay.io/shailendra14k/sample-operator:v0.1
    - sample-operator
    imagePullPolicy: Always

Deploy the sample Operator on OpenShift

Now that you have an Operator (sample-operator), deploy it by doing the following:

  1. Create a new project:
$ oc new-project sample-operator
  1. Create the sample CRD:
$ oc create -f deploy/crds/shailendra14k.com_samples_crd.yaml
  1. Deploy the Operator and set up the role-based access control (RBAC):
$ oc create -f deploy/service_account.yaml
$ oc create -f deploy/role.yaml
$ oc create -f deploy/role_binding.yaml
$ oc create -f deploy/operator.yaml
  1. Verify the deployment and Operator logs:
$ oc get all
NAME                                   READY     STATUS    RESTARTS   AGE
pod/sample-operator-867cf7c68f-jpjxk   1/1       Running   0          95s

NAME                              TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)             AGE
service/sample-operator-metrics   ClusterIP   <none>        8383/TCP,8686/TCP   76s

NAME                              READY     UP-TO-DATE   AVAILABLE   AGE
deployment.apps/sample-operator   1/1       1            1           97s

NAME                                         DESIRED   CURRENT   READY     AGE
replicaset.apps/sample-operator-867cf7c68f   1         1         1         97s

//Operator logs
{"level":"info","ts":1585146465.5662885,"logger":"metrics","msg":"Metrics Service object created","Service.Name":"sample-operator-metrics","Service.Namespace":"sample-operator"}
{"level":"info","ts":1585146468.5416582,"logger":"cmd","msg":"Starting the Cmd."}
{"level":"info","ts":1585146468.5419788,"logger":"controller-runtime.manager","msg":"starting metrics server","path":"/metrics"}
{"level":"info","ts":1585146468.5421832,"logger":"controller-runtime.controller","msg":"Starting EventSource","controller":"sample-controller","source":"kind source: /, Kind="}
{"level":"info","ts":1585146468.6432557,"logger":"controller-runtime.controller","msg":"Starting EventSource","controller":"sample-controller","source":"kind source: /, Kind="}
{"level":"info","ts":1585146468.743742,"logger":"controller-runtime.controller","msg":"Starting Controller","controller":"sample-controller"}
{"level":"info","ts":1585146468.7437823,"logger":"controller-runtime.controller","msg":"Starting workers","controller":"sample-controller","worker count":1}
  1. Create the Sample custom resource (CR):
$ cat deploy/crds/shailendra14k.com_v1alpha1_sample_cr.yaml 

apiVersion: shailendra14k.com/v1alpha1
kind: Sample
  name: example-sample
  # Add fields here
  size: 2
  bodyvalue: "Response received from POD : {{env:HOSTNAME}}"
  image: "quay.io/shailendra14k/sample:v0.1"

$ oc create -f deploy/crds/shailendra14k.com_v1alpha1_sample_cr.yaml 
sample.shailendra14k.com/example-sample created
  1. Verify that the application deployed and the pod was created:
$ oc get deployment
example-sample    2/2       2            2           4m8s
sample-operator   1/1       1            1           10m

$ oc get pods
NAME                               READY     STATUS    RESTARTS   AGE
example-sample-7d856cb9fc-7pbg2    1/1       Running   0          3m29s
example-sample-7d856cb9fc-bjg47    1/1       Running   0          3m29s
sample-operator-867cf7c68f-jpjxk   1/1       Running   0          10m

$ oc get samples
NAME             AGE
example-sample   5m23s
  1. Create a service and route to test the Camel route application (Sample image):
$ oc create service clusterip sample --tcp=8080:8080
$ oc expose svc/sample
  1. Test the application:
$ curl http://sample-sampleoperator.apps.lab.com/test
Response received from POD : example-sample-7d856cb9fc-7pbg2

$ curl http://sample-sampleoperator.apps.lab.com/test
Response received from POD : example-sample-7d856cb9fc-bjg47
  1. To update the application size from two to one:
apiVersion: shailendra14k.com/v1alpha1
kind: Sample
  name: example-sample
  # Add fields here
  size: 1
  bodyvalue: "Response received from POD : {{env:HOSTNAME}}"
  image: "quay.io/shailendra14k/sample:v0.1"

$ oc apply -f deploy/crds/shailendra14k.com_v1alpha1_sample_cr.yaml

$ oc get deployment
example-sample    1/1       1            1           3m1s

Finally, you can delete the namespace using the command:

$ oc delete project sample-operator


Thanks for reading! I hope this article helps you get started with the Operator SDK and deploying Operators on OpenShift.

Last updated: February 5, 2024