Operator SDK tutorial for Helm-based Operators

    • Create a Nginx deployment

    • Ensure that the deployment size is the same as specified by the custom resource (CR) spec

    • Update the Nginx CR status using the status writer with the names of the nginx pods

    This process is accomplished using two centerpieces of the Operator Framework:

    Operator SDK

    The operator-sdk CLI tool and controller-runtime library API

    Operator Lifecycle Manager (OLM)

    Installation, upgrade, and role-based access control (RBAC) of Operators on a cluster

    • Operator SDK CLI installed

    • Logged into an OKD 4.8 cluster with oc with an account that has cluster-admin permissions

    • To allow the cluster pull the image, the repository where you push your image must be set as public, or you must configure an image pull secret

    Creating a project

    Use the Operator SDK CLI to create a project called nginx-operator.

    Procedure

    1. Create a directory for the project:

    2. Change to the directory:

      1. $ cd $HOME/projects/nginx-operator
    3. Run the operator-sdk init command with the helm plug-in to initialize the project:

      1. $ operator-sdk init \
      2. --plugins=helm \
      3. --domain=example.com \
      4. --group=demo \
      5. --version=v1 \
      6. --kind=Nginx

      By default, the helm plug-in initializes a project using a boilerplate Helm chart. You can use additional flags, such as the —helm-chart flag, to initialize a project using an existing Helm chart.

      The init command creates the nginx-operator project specifically for watching a resource with API version example.com/v1 and kind Nginx.

    4. For Helm-based projects, the init command generates the RBAC rules in the config/rbac/role.yaml file based on the resources that would be deployed by the default manifest for the chart. Verify that the rules generated in this file meet the permission requirements of the Operator.

    Instead of creating your project with a boilerplate Helm chart, you can alternatively use an existing chart, either from your local file system or a remote chart repository, by using the following flags:

    • --helm-chart

    • --helm-chart-repo

    • --helm-chart-version

    If the --helm-chart flag is specified, the --group, --version, and --kind flags become optional. If left unset, the following default values are used:

    If the --helm-chart flag specifies a local chart archive, for example example-chart-1.2.0.tgz, or directory, the chart is validated and unpacked or copied into the project. Otherwise, the Operator SDK attempts to fetch the chart from a remote repository.

    If a custom repository URL is not specified by the --helm-chart-repo flag, the following chart reference formats are supported:

    FormatDescription

    <repo_name>/<chart_name>

    Fetch the Helm chart named <chart_name> from the helm chart repository named <repo_name>, as specified in the $HELM_HOME/repositories/repositories.yaml file. Use the helm repo add command to configure this file.

    <url>

    Fetch the Helm chart archive at the specified URL.

    If a custom repository URL is specified by --helm-chart-repo, the following chart reference format is supported:

    If the --helm-chart-version flag is unset, the Operator SDK fetches the latest available version of the Helm chart. Otherwise, it fetches the specified version. The optional --helm-chart-version flag is not used when the chart specified with the --helm-chart flag refers to a specific version, for example when it is a local path or a URL.

    1. $ operator-sdk init --plugins helm --help

    PROJECT file

    Among the files generated by the operator-sdk init command is a Kubebuilder PROJECT file. Subsequent operator-sdk commands, as well as help output, that are run from the project root read this file and are aware that the project type is Helm. For example:

    1. domain: example.com
    2. layout: helm.sdk.operatorframework.io/v1
    3. projectName: helm-operator
    4. resources:
    5. - group: demo
    6. kind: Nginx
    7. version: v1
    8. version: 3-alpha

    For this example, the nginx-operator project executes the following reconciliation logic for each Nginx custom resource (CR):

    • Create an Nginx deployment if it does not exist.

    • Create an Nginx service if it does not exist.

    • Create an Nginx ingress if it is enabled and does not exist.

    • Ensure that the deployment, service, and optional ingress match the desired configuration as specified by the CR, for example the replica count, image, and service type.

    By default, the nginx-operator project watches Nginx resource events as shown in the watches.yaml file and executes Helm releases using the specified chart:

    1. # Use the 'create api' subcommand to add watches to this file.
    2. - group: demo
    3. version: v1
    4. kind: Nginx
    5. chart: helm-charts/nginx
    6. # +kubebuilder:scaffold:watch

    When a Helm Operator project is created, the Operator SDK creates a sample Helm chart that contains a set of templates for a simple Nginx release.

    For this example, templates are available for deployment, service, and ingress resources, along with a NOTES.txt template, which Helm chart developers use to convey helpful information about a release.

    If you are not already familiar with Helm charts, review the Helm developer documentation.

    Modifying the custom resource spec

    Helm uses a concept called values to provide customizations to the defaults of a Helm chart, which are defined in the values.yaml file.

    You can override these defaults by setting the desired values in the custom resource (CR) spec. You can use the number of replicas as an example.

    Procedure

    1. The helm-charts/nginx/values.yaml file has a value called replicaCount set to 1 by default. To have two Nginx instances in your deployment, your CR spec must contain replicaCount: 2.

      Edit the config/samples/demo_v1_nginx.yaml file to set replicaCount: 2:

      1. apiVersion: demo.example.com/v1
      2. kind: Nginx
      3. metadata:
      4. name: nginx-sample
      5. spec:
      6. ...
      7. replicaCount: 2
    2. Similarly, the default service port is set to 80. To use 8080, edit the config/samples/demo_v1_nginx.yaml file to set spec.port: 8080,which adds the service port override:

      1. apiVersion: demo.example.com/v1
      2. kind: Nginx
      3. metadata:
      4. name: nginx-sample
      5. spec:
      6. replicaCount: 2
      7. service:
      8. port: 8080

    The Helm Operator applies the entire spec as if it was the contents of a values file, just like the helm install -f ./overrides.yaml command.

    Running the Operator

    There are three ways you can use the Operator SDK CLI to build and run your Operator:

    • Run locally outside the cluster as a Go program.

    • Run as a deployment on the cluster.

    • Bundle your Operator and use Operator Lifecycle Manager (OLM) to deploy on the cluster.

    You can run your Operator project as a Go program outside of the cluster. This is useful for development purposes to speed up deployment and testing.

    Procedure

    • Run the following command to install the custom resource definitions (CRDs) in the cluster configured in your ~/.kube/config file and run the Operator locally:

      1. $ make install run

      Example output

      1. ...
      2. {"level":"info","ts":1612652419.9289865,"logger":"controller-runtime.metrics","msg":"metrics server is starting to listen","addr":":8080"}
      3. {"level":"info","ts":1612652419.9296563,"logger":"helm.controller","msg":"Watching resource","apiVersion":"demo.example.com/v1","kind":"Nginx","namespace":"","reconcilePeriod":"1m0s"}
      4. {"level":"info","ts":1612652419.929983,"logger":"controller-runtime.manager","msg":"starting metrics server","path":"/metrics"}
      5. {"level":"info","ts":1612652419.930015,"logger":"controller-runtime.manager.controller.nginx-controller","msg":"Starting EventSource","source":"kind source: demo.example.com/v1, Kind=Nginx"}
      6. {"level":"info","ts":1612652420.2307851,"logger":"controller-runtime.manager.controller.nginx-controller","msg":"Starting Controller"}
      7. {"level":"info","ts":1612652420.2309358,"logger":"controller-runtime.manager.controller.nginx-controller","msg":"Starting workers","worker count":8}

    Running as a deployment on the cluster

    You can run your Operator project as a deployment on your cluster.

    Procedure

    1. Run the following make commands to build and push the Operator image. Modify the IMG argument in the following steps to reference a repository that you have access to. You can obtain an account for storing containers at repository sites such as Quay.io.

      1. Build the image:

        1. $ make docker-build IMG=<registry>/<user>/<image_name>:<tag>
      2. Push the image to a repository:

        1. $ make docker-push IMG=<registry>/<user>/<image_name>:<tag>

        The name and tag of the image, for example IMG=<registry>/<user>/<image_name>:<tag>, in both the commands can also be set in your Makefile. Modify the IMG ?= controller:latest value to set your default image name.

    2. Run the following command to deploy the Operator:

      By default, this command creates a namespace with the name of your Operator project in the form <project_name>-system and is used for the deployment. This command also installs the RBAC manifests from config/rbac.

    3. Verify that the Operator is running:

      1. $ oc get deployment -n <project_name>-system

      Example output

      1. NAME READY UP-TO-DATE AVAILABLE AGE
      2. <project_name>-controller-manager 1/1 1 1 8m

    Bundling an Operator

    The Operator bundle format is the default packaging method for Operator SDK and Operator Lifecycle Manager (OLM). You can get your Operator ready for use on OLM by using the Operator SDK to build and push your Operator project as a bundle image.

    Prerequisites

    • Operator SDK CLI installed on a development workstation

    • OpenShift CLI (oc) v4.8+ installed

    • Operator project initialized by using the Operator SDK

    Procedure

    1. Run the following make commands in your Operator project directory to build and push your Operator image. Modify the IMG argument in the following steps to reference a repository that you have access to. You can obtain an account for storing containers at repository sites such as Quay.io.

      1. Build the image:

        1. $ make docker-build IMG=<registry>/<user>/<operator_image_name>:<tag>
      2. Push the image to a repository:

        1. $ make docker-push IMG=<registry>/<user>/<operator_image_name>:<tag>
      1. $ # Image URL to use all building/pushing image targets
      2. IMG ?= <registry>/<user>/<operator_image_name>:<tag>

      This value is used for subsequent operations.

    2. Create your Operator bundle manifest by running the make bundle command, which invokes several commands, including the Operator SDK generate bundle and bundle validate subcommands:

      Bundle manifests for an Operator describe how to display, create, and manage an application. The make bundle command creates the following files and directories in your Operator project:

      • A bundle manifests directory named bundle/manifests that contains a ClusterServiceVersion object

      • A bundle metadata directory named bundle/metadata

      • All custom resource definitions (CRDs) in a config/crd directory

      • A Dockerfile bundle.Dockerfile

      These files are then automatically validated by using operator-sdk bundle validate to ensure the on-disk bundle representation is correct.

    3. Build and push your bundle image by running the following commands. OLM consumes Operator bundles using an index image, which reference one or more bundle images.

      1. Build the bundle image. Set BUNDLE_IMG with the details for the registry, user namespace, and image tag where you intend to push the image:

        1. $ make bundle-build BUNDLE_IMG=<registry>/<user>/<bundle_image_name>:<tag>
      2. Push the bundle image:

        1. $ docker push <registry>/<user>/<bundle_image_name>:<tag>

    Deploying an Operator with Operator Lifecycle Manager

    Operator Lifecycle Manager (OLM) helps you to install, update, and manage the lifecycle of Operators and their associated services on a Kubernetes cluster. OLM is installed by default on OKD and runs as a Kubernetes extension so that you can use the web console and the OpenShift CLI (oc) for all Operator lifecycle management functions without any additional tools.

    The Operator bundle format is the default packaging method for Operator SDK and OLM. You can use the Operator SDK to quickly run a bundle image on OLM to ensure that it runs properly.

    Prerequisites

    • Operator SDK CLI installed on a development workstation

    • Operator bundle image built and pushed to a registry

    • OLM installed on a Kubernetes-based cluster (v1.16.0 or later if you use apiextensions.k8s.io/v1 CRDs, for example OKD 4.8)

    • Logged in to the cluster with oc using an account with cluster-admin permissions

    Procedure

    1. Run the Operator on your cluster by using the OLM integration in Operator SDK:

      1. $ operator-sdk run bundle \
      2. [-n <namespace>] \(1)
      3. <registry>/<user>/<bundle_image_name>:<tag>

      This command performs the following actions:

      • Create an index image referencing your bundle image. The index image is opaque and ephemeral, but accurately reflects how a bundle would be added to a catalog in production.

      • Create a catalog source that points to your new index image, which enables OperatorHub to discover your Operator.

      • Deploy your Operator to your cluster by creating an OperatorGroup, Subscription, InstallPlan, and all other required objects, including RBAC.

    After your Operator is installed, you can test it by creating a custom resource (CR) that is now provided on the cluster by the Operator.

    Prerequisites

    • Example Nginx Operator, which provides the Nginx CR, installed on a cluster

    Procedure

    1. Change to the namespace where your Operator is installed. For example, if you deployed the Operator using the make deploy command:

      1. $ oc project nginx-operator-system
    2. Edit the sample Nginx CR manifest at config/samples/demo_v1_nginx.yaml to contain the following specification:

    3. The Nginx service account requires privileged access to run in OKD. Add the following security context constraint (SCC) to the service account for the nginx-sample pod:

      1. $ oc adm policy add-scc-to-user \
      2. anyuid system:serviceaccount:nginx-operator-system:nginx-sample
    4. Create the CR:

      1. $ oc apply -f config/samples/demo_v1_nginx.yaml
    5. Ensure that the Nginx Operator creates the deployment for the sample CR with the correct size:

      1. $ oc get deployments

      Example output

      1. NAME READY UP-TO-DATE AVAILABLE AGE
      2. nginx-operator-controller-manager 1/1 1 1 8m
      3. nginx-sample 3/3 3 3 1m
    6. Check the pods and CR status to confirm the status is updated with the Nginx pod names.

      1. Check the pods:

        1. $ oc get pods

        Example output

        1. NAME READY STATUS RESTARTS AGE
        2. nginx-sample-6fd7c98d8-7dqdr 1/1 Running 0 1m
        3. nginx-sample-6fd7c98d8-g5k7v 1/1 Running 0 1m
        4. nginx-sample-6fd7c98d8-m7vn7 1/1 Running 0 1m
      2. Check the CR status:

        1. $ oc get nginx/nginx-sample -o yaml

        Example output

        1. apiVersion: demo.example.com/v1
        2. kind: Nginx
        3. metadata:
        4. ...
        5. name: nginx-sample
        6. ...
        7. spec:
        8. replicaCount: 3
        9. status:
        10. nodes:
        11. - nginx-sample-6fd7c98d8-7dqdr
        12. - nginx-sample-6fd7c98d8-g5k7v
        13. - nginx-sample-6fd7c98d8-m7vn7
    7. Update the deployment size.

      1. Update config/samples/demo_v1_nginx.yaml file to change the spec.size field in the Nginx CR from 3 to 5:

        1. $ oc patch nginx nginx-sample \
        2. -p '{"spec":{"replicaCount": 5}}' \
        3. --type=merge
      2. Confirm that the Operator changes the deployment size:

        1. $ oc get deployments

        Example output

        1. NAME READY UP-TO-DATE AVAILABLE AGE
        2. nginx-operator-controller-manager 1/1 1 1 10m
        3. nginx-sample 5/5 5 5 3m
    8. Clean up the resources that have been created as part of this tutorial.

      • If you used the command to test the Operator, run the following command:

        1. $ operator-sdk cleanup <project_name>

    Additional resources