How to create the bundle image for any Kubernetes Operator

Vincent Hou
8 min readJul 9, 2021

If you build your Kubernetes Operator with the Operator SDK, you do not need to worry about all the processes, from scaffolding your source code and manifests, to building and publishing your images and artifacts. However, if your operator is not written or generated by the Operator SDK at the beginning, but still plan to build and publish your operator in the Red Hat marketplace, including but not limited to openratorhub.io. it will certainly be a pain-in-the-ass. This article focuses on easing your pain, by walking toy through how to create the bundle image for the operator, if it is not generated by the Operator SDK.

I will take the Knative Operator as the example, since it is a typical non-controller-runtime based operator.

Prerequisites:

Build and install Operator SDK:

You have leverage partially the command lines of Operator SDK to generate the bundle image anyhow.

  1. Build and install operator-sdk with the latest commit

I built the operator-sdk binary based on the commit `46681e3` and did not have any issue with it. Make sure you build one based on a commit, no earlier than that.

Go to the path $GOPATH/src/github.com/operator-framework in your terminal:

cd $GOPATH/src/github.com/operator-framework

Download the source code of operator-sdk:

git clone git@github.com:operator-framework/operator-sdk.git

Go to the home directory of the operator-sdk:

cd operator-sdk

Build the binaries:

make install

The binary operator-sdk is automatically installed under $GOPATH/bin. Make sure your environment variable $PATH includes $GOPATH/bin. You can verify the installation with the command:

which operator-sdk

It will show you the path of the operator-sdk.

Create a branch for the source code:

I save my source code of Knative Operator under $GOPATH/src/knative.dev. You can run the following commands to download the source code:

cd $GOPATH/src/knative.dev
git clone git@github.com:knative/operator.git

Create a new branch to continue the work for building the bundle image. For example, we can name it bundle-image-creation.

git checkout -b bundle-image-creation

Go to the home directory of your project:

cd operator

Create the file structure to comply with the Operator SDK

First, make sure you have a directory called config. If not, create one.

mkdir config

Create a few sub-directories under config: crd, rbac, manager, manifests, and scorecard, if they are not available:

mkdir config/crd
mkdir config/rbac
mkdir config/manager
mkdir config/manifests
mkdir config/scorecard

Structure under config/scorecard

Let’s start with the most straightforward one. The files and directories under scorecard are static contents, applicable to any operator. Create directories called bases and patches under config/scrorecard.

mkdir config/scorecard/bases
mkdir config/scorecard/patches

Create a file called config.yaml under config/scorecard/bases, with the content:

apiVersion: scorecard.operatorframework.io/v1alpha3
kind: Configuration
metadata:
name: config
stages:
- parallel: true
tests: []

Create a file called basic.config.yaml under config/scorecard/patches, with the content:

- op: add
path: /stages/0/tests/-
value:
entrypoint:
- scorecard-test
- basic-check-spec
image: quay.io/operator-framework/scorecard-test:v1.9.0
labels:
suite: basic
test: basic-check-spec-test

Create a file called olm.config.yaml under config/scorecard/patches, with the content:

- op: add
path: /stages/0/tests/-
value:
entrypoint:
- scorecard-test
- olm-bundle-validation
image: quay.io/operator-framework/scorecard-test:v1.9.0
labels:
suite: olm
test: olm-bundle-validation-test
- op: add
path: /stages/0/tests/-
value:
entrypoint:
- scorecard-test
- olm-crds-have-validation
image: quay.io/operator-framework/scorecard-test:v1.9.0
labels:
suite: olm
test: olm-crds-have-validation-test
- op: add
path: /stages/0/tests/-
value:
entrypoint:
- scorecard-test
- olm-crds-have-resources
image: quay.io/operator-framework/scorecard-test:v1.9.0
labels:
suite: olm
test: olm-crds-have-resources-test
- op: add
path: /stages/0/tests/-
value:
entrypoint:
- scorecard-test
- olm-spec-descriptors
image: quay.io/operator-framework/scorecard-test:v1.9.0
labels:
suite: olm
test: olm-spec-descriptors-test
- op: add
path: /stages/0/tests/-
value:
entrypoint:
- scorecard-test
- olm-status-descriptors
image: quay.io/operator-framework/scorecard-test:v1.9.0
labels:
suite: olm
test: olm-status-descriptors-test

Create a file named kustomization.yaml under config/scorecard with the content:

resources:
- bases/config.yaml
patchesJson6902:
- path: patches/basic.config.yaml
target:
group: scorecard.operatorframework.io
version: v1alpha3
kind: Configuration
name: config
- path: patches/olm.config.yaml
target:
group: scorecard.operatorframework.io
version: v1alpha3
kind: Configuration
name: config

Unless you have other special needs for the scorecard, you do not need to edit any files under config/scorecard.

Structure under config/crd

This directory is where you put your CRDs. As in Knative Operator, there are two CRDs: KnativeServing and KnativeEventing. I would like to create a file called operator.knative.dev_knativeservings.yaml to host KnativeServing, and a file called operator.knative.dev_knativeeventings.yaml to host KnativeEventing. Here is what I did:

Create a sub-directory called bases under config/crd:

mkdir config/crd/bases

Create a file named operator.knative.dev_knativeservings.yaml under config/crd/bases, with the definition of KnativeServing. To check the CRD of KnativeServing, please refer to this file.

Create a file named operator.knative.dev_knativeeventings.yaml under config/crd/bases, with the definition of KnativeEventing. To check the CRD of KnativeEventing, please refer to this file.

Create a file named kustomization.yaml under config/crd with the content as below:

resources:
- bases/operator.knative.dev_knativeeventings.yaml
- bases/operator.knative.dev_knativeservings.yaml

Structure under config/rbac

This directory is where you put your ServiceAccount, Role, RoleBinding, ClusterRole and ClusterRoleBinding.

I create a file named role.yaml under config/rbac, to host all the roles and ClusterRoles. For Knative Operator, it is like this file.

I create a file named role_binding.yaml under config/rbac, to host all the RoleBinding and ClusterRoleBinding. For Knative Operator, it is like this file.

I create a file named service_account.yaml under config/rbac, to host all the ServiceAccount. For Knative Operator, it is like this file.

Create a file named kustomization.yaml under config/rbac with the content as below:

resources:
- service_account.yaml
- role.yaml
- role_binding.yaml

You can orchestrate your rbac files based on your project.

Structure under config/manager

This directory is where you save your deployment resource for the operator.

I create a file named manager.yaml under config/manager with the deployment resource for Knative Operator:

apiVersion: apps/v1
kind: Deployment
metadata:
name: knative-operator
namespace: default
labels:
operator.knative.dev/release: devel
spec:
replicas: 1
selector:
matchLabels:
name: knative-operator
template:
metadata:
annotations:
sidecar.istio.io/inject: "false"
labels:
name: knative-operator
spec:
serviceAccountName: knative-operator
containers:
- name: knative-operator
image: gcr.io/knative-releases/knative.dev/operator/cmd/operator:v0.24.0
imagePullPolicy: IfNotPresent
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: SYSTEM_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: METRICS_DOMAIN
value: knative.dev/operator
- name: CONFIG_LOGGING_NAME
value: config-logging
- name: CONFIG_OBSERVABILITY_NAME
value: config-observability
ports:
- name: metrics
containerPort: 9090

This file contains only the deployment resource. Give a correct image link for your deployment. As you notice, I use the valid image gcr.io/knative-releases/knative.dev/operator/cmd/operator:v0.24.0, for Knative Operator. All the other fields are the same as in this file.

Create a file named kustomization.yaml under config/manager with the content as below:

resources:
- manager.yaml

Structure under config/manifests

This directory is where you save your CSV file and consolidate all the other directories.

Create a file named kustomization.yaml under config/manifests with the content as below:

resources:
- bases/knative-operator.clusterserviceversion.yaml
- ../crd
- ../rbac
- ../manager
- ../scorecard

OK. What is knative-operator.clusterserviceversion.yaml? Create this file under config/manifests/bases. This file is usually generated by the Operator SDK by the command:

operator-sdk generate kustomize manifests -q

However, since this operator is not generated by the Operator SDK, you probably need to prepare this file manually. As in Knative Operator, the content is as below:

apiVersion: operators.coreos.com/v1alpha1
kind: ClusterServiceVersion
metadata:
annotations:
alm-examples: '[{"apiVersion":"operator.knative.dev/v1alpha1","kind":"KnativeServing","metadata":{"name":"knative-serving"},"spec":{}},
{"apiVersion":"operator.knative.dev/v1alpha1","kind":"KnativeEventing","metadata":{"name":"knative-eventing"},"spec":{}}]'
capabilities: Basic Install
categories: Networking,Integration & Delivery,Cloud Provider,Developer Tools
certified: "false"
description: |-
Knative components build on top of Kubernetes, abstracting away the complex details and
enabling developers to focus on what matters. Built by codifying the best practices
shared by successful real-world implementations, Knative solves the "boring but difficult"
parts of deploying and managing cloud native services so you don't have to.
repository: https://github.com/knative/operator
support: The Knative Authors
name: knative-operator.v0.24.0
namespace: placeholder
spec:
apiservicedefinitions: {}
customresourcedefinitions:
owned:
- description: Represents an installation of a particular version of Knative Serving
displayName: Knative Serving
kind: KnativeServing
name: knativeservings.operator.knative.dev
statusDescriptors:
- description: The version of Knative Serving installed
displayName: Version
path: version
x-descriptors:
- 'urn:alm:descriptor:text'
version: v1alpha1
specDescriptors: []
resources:
- version: v1
kind: Deployment
- version: v1
kind: Service
- version: v1
kind: ReplicaSet
- version: v1
kind: Pod
- version: v1
kind: Secret
- version: v1
kind: ConfigMap
- description: Represents an installation of a particular version of Knative Eventing
displayName: Knative Eventing
kind: KnativeEventing
name: knativeeventings.operator.knative.dev
statusDescriptors:
- description: The version of Knative Eventing installed
displayName: Version
path: version
x-descriptors:
- 'urn:alm:descriptor:text'
version: v1alpha1
specDescriptors: []
resources:
- version: v1
kind: Deployment
- version: v1
kind: Service
- version: v1
kind: ReplicaSet
- version: v1
kind: Pod
- version: v1
kind: Secret
- version: v1
kind: ConfigMap
required: []
description: |+
## Knative Serving
Knative Serving builds on Kubernetes to support deploying and serving of
applications and functions as serverless containers. Serving is easy to get
started with and scales to support advanced scenarios. Other features
includes:
- Rapid deployment of serverless containers
- Automatic scaling up and down to zero
- Routing and network programming
- Point-in-time snapshots of deployed code and configurations
## Knative Eventing
Knative Eventing is a system that is designed to address a common need for cloud native
development and provides composable primitives to enable late-binding event sources and
event consumers.
Knative Eventing is designed to address a common need for cloud native development:
- Services are loosely coupled during development and deployed independently
- A producer can generate events before a consumer is listening, and a consumer
can express an interest in an event or class of events that is not yet being
produced.
- Services can be connected to create new applications
* without modifying producer or consumer, and
* with the ability to select a specific subset of events from a particular
producer.
displayName: Knative Operator
icon:
- base64data: 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
mediatype: image/png
install:
spec:
deployments: null
strategy: ""
installModes:
- supported: false
type: OwnNamespace
- supported: false
type: SingleNamespace
- supported: false
type: MultiNamespace
- supported: true
type: AllNamespaces
keywords:
- serverless
- FaaS
- microservices
- scale to zero
- knative
- serving
- eventing
links:
- name: Documentation
url: https://knative.dev/docs/install/knative-with-operators/
- name: Source Repository
url: https://github.com/knative
maintainers:
- email: knative-dev@googlegroups.com
name: The Knative Authors
maturity: alpha
minKubeVersion: 1.16.0
provider:
name: The Knative Authors
replaces: knative-operator.v0.23.1
selector: {}
version: 0.24.0

It is not difficult to prepare this file. You can refer to this guide for detailed explanation on each field of this CSV file.

Generate the bundle:

Create a file named PROJECT under the home directory of your project, with the following content:

domain: knative.dev
layout:
- go.kubebuilder.io/v3
plugins:
manifests.sdk.operatorframework.io/v2: {}
scorecard.sdk.operatorframework.io/v2: {}
projectName: knative-operator
repo: github.com/knative/operator
version: "3"

This above content is for Knative Operator. You need to change the domain, projectName and repo to reflect the your project.

Then, generate the bundle with the following command:

kustomize build config/manifests | operator-sdk generate bundle -q --overwrite --version 0.24.0

As you see, Knativ Operator is at 0.24.0, so I specify the version as 0.24.0.

You will see a directory called bundle is generated. For Knative Operator, it is like:

You can probably guess what is in each file based on the name.

You can validate your bundle with the command:

operator-sdk bundle validate ./bundle

Build and push the bundle image:

Create the file named bundle.Dockerfile under the home directory of your project as below:

FROM scratch

# Core bundle labels.
LABEL operators.operatorframework.io.bundle.mediatype.v1=registry+v1
LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/
LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/
LABEL operators.operatorframework.io.bundle.package.v1=knative-operator
LABEL operators.operatorframework.io.bundle.channels.v1=alpha
LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.9.0+git
LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1
LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v3

# Labels for testing.
LABEL operators.operatorframework.io.test.mediatype.v1=scorecard+v1
LABEL operators.operatorframework.io.test.config.v1=tests/scorecard/

# Copy files to locations specified by labels.
COPY bundle/manifests /manifests/
COPY bundle/metadata /metadata/
COPY bundle/tests/scorecard /tests/scorecard/

Knative Operator is at 0.24.0. I use the tag v0.24.0 for the bundle image. I name the bundle image after knative-operator-bundle.

Build the bundle image with the following command:

docker build -f bundle.Dockerfile -t docker.io/$USER/knative-operator-bundle:v0.24.0 .

Push the image with the following command:

docker push docker.io/$USER/knative-operator-bundle:v0.24.0

Replace $USER with the valid name for your image repository. You can use registry other than docker.

Here is the example for Knative Operator to build and publish the bundle image for RH marketplace ecosystem. It could be a reference for you to build the bundle image for non-operator-sdk generated operators.

Don’t want to derail? Follow Vincent!

--

--

Vincent Hou

A Chinese software engineer, used to study in Belgium and currently working in US, as Knative & Tekton Operator Lead and Istio Operator Contributor.