Publish Kubernetes Operator to OperatorHub

In this post I will show you how you can publish your operator to OperatorHub.

I assume you have a pre built and publised docker image with the operator in it.

Install requirement

brew install opm
brew install operator-sdk

Stucture

Your Operator submission can be formatted following the bundle or packagemanifest format. he packagemanifest format is a legacy format which is kept for backwards compatibility only so I will use bundle format.

tree OLM

OLM
└── 0.0.1
    ├── manifests
    │   ├── trivy-operator.v0.0.1.clusterserviceversion.yaml
    │   └── crds.yaml
    └── metadata
        └── annotations.yaml

Create clusterserviceversion

To add your operator to any of the supported platforms, you will need to submit metadata for your Operator to be used by the Operator Lifecycle Manager (OLM). This is YAML file called ClusterServiceVersion which contains references to all of the CRDs, RBAC rules, Deployment and container image needed to install and securely run your Operator.

nano OLM/0.0.1/manifests/trivy-operator.v0.0.1.clusterserviceversion.yaml

First start with a CSV that only contains some descriptive metadata:

apiVersion: operators.coreos.com/v1alpha1
kind: ClusterServiceVersion
metadata:
  annotations:
    capabilities: Basic Install
    categories: "Security"
    description: Trivy Operator for scheduled imagescans and an Admission Control.
    certified: "false"
    containerImage: devopstales/trivy-operator:0.0.1
  name: trivy-operator.v0.0.1
spec:
  description: Trivy Operator for scheduled imagescans and an Admission Control.
  displayName: Trivy Operator
  keywords:
  - trivy
  - security
  maintainers:
  - email: devopstales@mydomain.intra
    name: devopstales
  maturity: alpha
  provider:
    name: Blog
    url: devopstales.hithub.io
  version: 0.10.0

The next section to add to the CSV is the Install Strategy - this tells OLM about the runtime components of your operator and their requirements.

apiVersion: operators.coreos.com/v1alpha1
kind: ClusterServiceVersion
metadata:
  annotations:
  # ... 
  name: trivy-operator.v0.0.1
spec:
  install:
  # ... 
    # strategy indicates what type of deployment artifacts are used
    strategy: deployment
    # spec for the deployment strategy is a list of deployment specs and required permissions - similar to a pod template used in a deployment
    spec:
      permissions:
      - serviceAccountName: trivy-operator 
        rules:
        - apiGroups:
          - ""
          resources:
          - pods
          verbs:
          - '*'
          # the rest of the rules
      # permissions required at the cluster scope
      clusterPermissions:
      - serviceAccountName: trivy-operator 
        rules:
        - apiGroups:
          - ""
          resources:
          - serviceaccounts
          verbs:
          - '*'
          # the rest of the rules
      deployments:
      - name: trivy-operator
        spec:
          replicas: 1
          # the rest of a deployment spec

For Openshift you can append SCC to the serviceaccount in the rules section

It’s also important to tell OLM the ways in which your operator can be deployed:

apiVersion: operators.coreos.com/v1alpha1
kind: ClusterServiceVersion
metadata:
  annotations:
  # ... 
  name: trivy-operator.v0.0.1
spec:
  # ... 
  installModes:
  - supported: true
    type: OwnNamespace
  - supported: true
    type: SingleNamespace
  - supported: false
    type: MultiNamespace
  - supported: true
    type: AllNamespaces

By definition, operators are programs that can talk to the Kubernetes API and they are also extend the Kubernetes API, by CustomResourceDefinitions. Which APIs are watched or owned is important metadata for OLM so we need to define thease too.

apiVersion: operators.coreos.com/v1alpha1
kind: ClusterServiceVersion
metadata:
  annotations:
  # ... 
    alm-examples: |-
      [
        {
          "apiVersion": "trivy-operator.devopstales.io/v1",
          "kind": "NamespaceScanner",
          "metadata": {
            "name": "trivy-operator-main-config"
          },
          "spec": {
            "crontab": "*/5 * * * *",
            "namespace_selector": "trivy-scan",
            "registry": [
              {
                "name": "docker.io",
                "user": "",
                "password": ""
              }
            ]
          }
        }
      ]      
  name: trivy-operator.v0.0.1
spec:
spec:
  # ... 
  customresourcedefinitions:
    owned:
    # a list of CRDs that this operator owns 
      # name is the metadata.name of the CRD
    - name: namespace-scanners.trivy-operator.devopstales.io
      # version is the version of the CRD (one per entry)
      version: v1
      # spec.names.kind from the CRD
      kind: NamespaceScanner

You must place the CustomResourceDefinitions to the manifests folder andy any other object like service what you want to deploy by the Operator Framework. If you created your `` you can test with OperatorHub’s Operator Preview page.

Test operator

You can test your files with operator-sdk locally:

operator-sdk bundle validate ./OLM/0.0.1 --select-optional suite=operatorframework
INFO[0000] All validation tests have completed successfully

Generate bundle

To test the functions all working correctly you need first to install the operator with Operato framework. To do so you need a build the bundle image the the catalog index:

cd OML
opm alpha bundle build -c stable -d 0.0.1/manifests -t devopstales/trivy-operator-bundle:0.0.1 -p trivy-operator
docker push docker.io/devopstales/trivy-operator-bundle:0.0.1

opm index add -b docker.io/devopstales/trivy-operator-bundle:0.0.1 -t docker.io/devopstales/trivy-operator-index:0.0.1 -c docker
docker push docker.io/devopstales/trivy-operator-index:0.0.1

Install OML

To install the operator from your catalog index first you need to install the operator-lifecycle-manager.

curl -sL https://github.com/operator-framework/operator-lifecycle-manager/releases/download/v0.19.1/install.sh | bash -s v0.19.1

Add your index as CatalogSource

nano cs-trivy-operator.yaml

apiVersion: operators.coreos.com/v1alpha1
kind: CatalogSource
metadata:
  name: devopstales-catalog
  namespace: olm
spec:
  displayName: devopstales
  publisher: devopstales
  sourceType: grpc
  image: docker.io/devopstales/trivy-operator-index:0.0.1
  updateStrategy:
    registryPoll:
      interval: 1m
kubectl apply -f cs-trivy-operator.yaml

kubectl get catalogsource --all-namespaces
NAMESPACE   NAME                    DISPLAY               TYPE      PUBLISHER        AGE
olm         devopstales-catalog     devopstales           grpc      devopstales      13m
olm         operatorhubio-catalog   Community Operators   grpc      OperatorHub.io   7h20m

kubectl get packagemanifest | grep trivy
trivy-operator                             devopstales           6h54m

Subscribe to this operator:

$ cat og.yaml 
apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata:
  name: test-og
  namespace: default
spec:
  targetNamespaces:
  - default

$ cat sub_devopstales-catalog.yaml 
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
  name: trivy-operator
  namespace: default
spec:
  channel: alpha
  installPlanApproval: Automatic
  name: trivy-operator
  source: devopstales-catalog
  sourceNamespace: olm
  startingCSV: trivy-operator.v0.0.1

After create them, you will get the below objects:

$ oc get sub -n default
NAME             PACKAGE          SOURCE                CHANNEL
trivy-operator   trivy-operator   devopstales-catalog   alpha

$ oc get ip -n default
NAME            CSV                     APPROVAL    APPROVED
install-tvpq4   trivy-operator.v0.0.1   Automatic   true

$ oc get csv -n default
NAME                    DISPLAY          VERSION   REPLACES   PHASE
trivy-operator.v0.0.1   Trivy Operator   0.0.1                Succeeded

The you can create the Custo Resource to test the functionality.

Publicate operato on OperatoHub

To publish your bundled operator you need to create a pull requuest to the operatorhub community-operators repo or RedHat Opeshift community-operators repo.