Why installing Kafka is not helm install here
The team wants Kafka. On AKS you would helm install a chart (25.19) and own every upgrade by hand. On OpenShift you install an operator from a catalog, and a small framework (OLM) decides when it may upgrade. When the install silently never happens, you need to know which of four objects is waiting.
What you need to know already: the reconciliation loop (15.9); CRDs and operators in one sentence (30.1); Deployments and ServiceAccounts (15.16, 17.33); RBAC (17.30); oc patch --type merge (15.40); jsonpath range (15.38); Helm charts and releases (25.19).
The words you need first
- Operand - the thing the operator runs for you (the Kafka cluster); the operator is the manager, the operand is what it manages.
- CR (custom resource) - one object of a CRD's kind, e.g. your
Kafkanamedevents. - OLM (Operator Lifecycle Manager) - the part of OpenShift that installs and upgrades operators. OperatorHub is its catalog page in the console.
- Channel - an update stream of one operator (
stable,stable-6.3): which versions you follow. - Approval - Automatic (OLM upgrades as soon as a new version appears) or Manual (a person approves each install and upgrade).
Software that runs software
An Operator is a controller that knows how to run one product - a Kafka cluster, a PostgreSQL database, Argo CD - plus the CRDs you use to ask for it. You create a Kafka object; the Strimzi operator creates the StatefulSets, Services, Secrets and certificates, and keeps fixing them. It is the reconcile loop from 15.9, written by the vendor. (Strimzi is the open-source Kafka operator; Red Hat sells it as "Streams for Apache Kafka", package amq-streams.)
OpenShift itself is built from operators (the ClusterOperators of lesson 30.1, owned by the Cluster Version Operator). Add-on operators - the ones you choose - are installed and upgraded by OLM, the Operator Lifecycle Manager, from catalogs shown in the console as OperatorHub.
The catalog
# as kubeadmin, in the operator mission
oc get catalogsources -n openshift-marketplace
NAME DISPLAY TYPE PUBLISHER AGE
certified-operators Certified Operators grpc Red Hat 13d
community-operators Community Operators grpc Red Hat 13d
redhat-marketplace Red Hat Marketplace grpc Red Hat 13d
redhat-operators Red Hat Operators grpc Red Hat 13d
oc get packagemanifests -n openshift-marketplace
NAME CATALOG AGE
amq-streams Red Hat Operators 13d
cluster-logging Red Hat Operators 13d
crunchy-postgres-operator Certified Operators 13d
openshift-gitops-operator Red Hat Operators 13d
openshift-pipelines-operator-rh Red Hat Operators 13d
strimzi-kafka-operator Community Operators 13d
A CatalogSource is an index image (an image that contains a list of operators and their versions) served over gRPC (a binary request/response protocol over HTTP/2) by a pod in openshift-marketplace. A PackageManifest is one product in a catalog, with its channels (update streams, like stable or stable-6.3) and the head version of each. (simulator) The lab catalog holds six packages; their versions are lab data - look up the real channel names on your cluster.
The jsonpath below prints each channel's name and its newest version (currentCSV); describe | grep -A10 InstallModes shows which install modes (next section) the operator supports:
oc get packagemanifest amq-streams -n openshift-marketplace -o jsonpath='{range .status.channels[*]}{.name}{" "}{.currentCSV}{"\n"}{end}'
stable amqstreams.v3.0.1
amq-streams-2.9.x amqstreams.v2.9.3
oc describe packagemanifest amq-streams -n openshift-marketplace | grep -A10 InstallModes
InstallModes:
- supported: true
Type: OwnNamespace
- supported: true
Type: SingleNamespace
- supported: true
Type: MultiNamespace
- supported: true
Type: AllNamespaces
The four objects of an install
| object | where | what it says |
|---|---|---|
OperatorGroup (og) | the operator's namespace | which namespaces operators here watch (targetNamespaces); exactly one per namespace |
Subscription (sub) | same namespace | "install package P from catalog C, follow channel X, approval Automatic/Manual" |
InstallPlan (ip) | same namespace | OLM's plan to install a specific version: the CSV + CRDs + RBAC. Needs approval when the Subscription says Manual |
ClusterServiceVersion (csv) | same namespace (copied to watched ones) | the installed operator version: its Deployment, CRDs it owns, permissions, phase |
The console's Install button creates the first two for you. As YAML:
apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata:
name: kafka
namespace: kafka
spec:
targetNamespaces:
- kafka
---
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
name: amq-streams
namespace: kafka
spec:
channel: stable
name: amq-streams
source: redhat-operators
sourceNamespace: openshift-marketplace
installPlanApproval: Manual
Install modes are what the OperatorGroup must match:
- AllNamespaces - the operator watches every namespace. Installed into
openshift-operators, which already has theglobal-operatorsOperatorGroup (notargetNamespaces= all). Most Red Hat platform operators (GitOps, Pipelines) are AllNamespaces only. - OwnNamespace - watches only the namespace it is installed in:
targetNamespaces: [<its own namespace>]. Typical for "one team, one Kafka". - SingleNamespace / MultiNamespace - watch one / several other namespaces.
Following an install
oc apply -f amq-streams.yaml
operatorgroup.operators.coreos.com/kafka created
subscription.operators.coreos.com/amq-streams created
oc get sub,ip,csv -n kafka
NAME PACKAGE SOURCE CHANNEL
subscription.operators.coreos.com/amq-streams amq-streams redhat-operators stable
NAME CSV APPROVAL APPROVED
installplan.operators.coreos.com/install-2bzcl amqstreams.v3.0.1 Manual false
oc get sub,ip,csv lists three kinds at once. APPROVED false, no CSV: with installPlanApproval: Manual nothing happens until someone approves the plan. The Subscription says so too, in its conditions (the Reason / Status / Type lines):
oc describe sub amq-streams -n kafka | tail -12
Reason: RequiresApproval
Status: True
Type: InstallPlanPending
Current CSV: amqstreams.v3.0.1
Install Plan Ref:
API Version: operators.coreos.com/v1alpha1
Kind: InstallPlan
Name: install-2bzcl
Namespace: kafka
State: UpgradePending
Approve the plan the Subscription points at (status.installPlanRef):
oc patch installplan install-2bzcl -n kafka --type merge -p '{"spec":{"approved":true}}'
installplan.operators.coreos.com/install-2bzcl patched
oc get csv -n kafka
NAME DISPLAY VERSION REPLACES PHASE
amqstreams.v3.0.1 Streams for Apache Kafka 3.0.1 Succeeded
The CSV walks Pending -> InstallReady -> Installing -> Succeeded: requirements checked (OperatorGroup, CRDs, permissions), the operator Deployment created, the Deployment available. Then the CRDs exist and you can create the product:
oc apply -f kafka.yaml
kafka.kafka.strimzi.io/events created
Before the CSV succeeded, the same command fails - the API does not know the kind yet:
oc apply -f kafka.yaml
error: resource mapping not found for name: "events" namespace: "kafka" from "kafka.yaml": no matches for kind "Kafka" in version "kafka.strimzi.io/v1beta2"
ensure CRDs are installed first
Why Manual?
Automatic approval means OLM upgrades the operator as soon as the catalog has a new version in the channel - which in a bank means an operator changing behaviour in production on a random Tuesday. Most regulated shops run Manual and approve upgrade InstallPlans in a change window, testing them on a non-prod cluster first. The catch: an unapproved plan blocks the channel. Operators stuck at an old version, "the new CRD field does not exist", or a fresh install that never happened are almost always an InstallPlan waiting in RequiresApproval.
Only one InstallPlan matters: the one in the Subscription's status.installPlanRef. If the catalog moved on while a plan waited, OLM creates a new plan for the newer version; older unapproved plans are just left behind. Approving an old one does nothing useful.
When the CSV fails
oc get csv -n kafka
NAME DISPLAY VERSION REPLACES PHASE
amqstreams.v3.0.1 Streams for Apache Kafka 3.0.1 Failed
oc describe csv amqstreams.v3.0.1 -n kafka | tail -5
Reason: NoOperatorGroup
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning NoOperatorGroup 4s operator-lifecycle-manager csv in namespace with no operatorgroups
The OperatorGroup reasons, the ones you will actually see:
| reason | message | fix |
|---|---|---|
NoOperatorGroup | csv in namespace with no operatorgroups | create one OperatorGroup |
TooManyOperatorGroups | csv created in namespace with multiple operatorgroups, can't pick one automatically | delete the extra one |
UnsupportedOperatorGroup | OwnNamespace InstallModeType not supported, cannot configure to watch own namespace | install where the mode is supported (often: openshift-operators) |
OLM retries by itself once the OperatorGroup is right.
Upgrades
With a Subscription on channel stable and the operator at 3.0.0, a catalog update that adds 3.0.1 to stable produces a new InstallPlan (REPLACES amqstreams.v3.0.0). Automatic: it runs. Manual: it waits for you. Changing minor streams means changing the Subscription's channel (amq-streams-2.9.x -> stable), after reading the operator's upgrade notes. Uninstalling = delete the Subscription and the CSV (oc delete sub ... alone leaves the operator running); the CRDs and your CRs stay until you delete them.
OLM v1
OpenShift also ships OLM v1 (GA since 4.18): a ClusterExtension object (olm.operatorframework.io) that installs an operator bundle from a ClusterCatalog, with a ServiceAccount you provide for its permissions, and no OperatorGroups or InstallPlans. It is the direction of travel; the OperatorHub console and most existing clusters still use the v0 objects above, which is what you will be asked about.
What you can now do
- Name the four OLM objects (OperatorGroup, Subscription, InstallPlan, CSV) and what each one says.
- Install an operator: pick the channel and install mode, write the OperatorGroup and Subscription, approve a Manual InstallPlan.
- Diagnose "the operator never arrived": a pending InstallPlan, NoOperatorGroup, "no matches for kind".