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-sdk
CLI 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-admin
permissions. - 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-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
- 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
Sample
custom 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
Sample
resource:
$ operator-sdk add controller --api-version=shailendra14k.com/v1alpha1 --kind=Sample
- Replace the default
pkg/controller/sample/sample_controller.go
with 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.yaml
with 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
Sample
custom 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 (
Sample
image):
$ 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