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:
- Build the sample app's image and push it to the Quay repo.
 - Install the 
operator-sdkCLI from Homebrew (macOS), GitHub, or by compiling and installing from the master. - Install the right Golang for your OS.
 - Get access to an OpenShift cluster with 
cluster-adminpermissions. - 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:
- Create the 
sample-operatorproject: 
$ 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
- 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.
- Create a custom resource definition (CRD):
 
$ operator-sdk add api --api-version=shailendra14k.com/v1alpha1 --kind=Sample
- Open 
pkg/apis/shailendra14k/v1alpha1/sample_types.go. - Update the 
Samplecustom resource spec andStatus: 
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"`
}
- Run the following to update the generated code after modifying 
*_types.go: 
$ operator-sdk generate k8s //Generate the updated CRDs $ operator-sdk generate crds
- Create a new controller to watch and reconcile the 
Sampleresource: 
$ operator-sdk add controller --api-version=shailendra14k.com/v1alpha1 --kind=Sample
- Replace the default 
pkg/controller/sample/sample_controller.gowith thesample_controller.go. - 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
- Update the default 
deploy/operator.yamlwith the correct image details: 
serviceAccountName: sample-operator
containers:
  - name: sample-operator
    # Replace this with the built image name
    image: quay.io/shailendra14k/sample-operator:v0.1
    command:
    - sample-operator
    imagePullPolicy: Always
Deploy the sample Operator on OpenShift
Now that you have an Operator (sample-operator), deploy it by doing the following:
- Create a new project:
 
$ oc new-project sample-operator
- Create the sample CRD:
 
$ oc create -f deploy/crds/shailendra14k.com_samples_crd.yaml
- 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
- 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   172.30.125.171   <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}
- Create the 
Samplecustom resource (CR): 
$ cat deploy/crds/shailendra14k.com_v1alpha1_sample_cr.yaml 
apiVersion: shailendra14k.com/v1alpha1
kind: Sample
metadata:
  name: example-sample
spec:
  # 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
- Verify that the application deployed and the pod was created:
 
$ oc get deployment NAME READY UP-TO-DATE AVAILABLE AGE 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
- Create a service and route to test the Camel route application (
Sampleimage): 
$ oc create service clusterip sample --tcp=8080:8080 $ oc expose svc/sample
- 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
- To update the application size from two to one:
 
apiVersion: shailendra14k.com/v1alpha1
kind: Sample
metadata:
  name: example-sample
spec:
  # 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
NAME              READY     UP-TO-DATE   AVAILABLE   AGE
example-sample    1/1       1            1           3m1s
Finally, you can delete the namespace using the command:
$ oc delete project sample-operator
Conclusion
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