Skip to content

Manage standalone ClusterObjectDeployments

A ClusterObjectDeployment (COD) manages mutable desired content by creating immutable ClusterObjectSet (COS) revisions. This tutorial installs the standalone experimental object-controller, deploys a ConfigMap, and follows an A → B → A template change through three forward revisions.

The COD owns its COS revisions through controller owner references. Each COS owns the Kubernetes objects it manages:

flowchart LR
    COD[ClusterObjectDeployment] --> COS[ClusterObjectSet revision]
    COS --> CM[ConfigMap]

Demo: revisions and ownership

This recording follows A → B → A, showing the new COS revision and ConfigMap owner at each update, the unchanged ConfigMap UID, archived history, and cascading cleanup when the COD is deleted.

Terminal
 

The demo uses direct COD resources in the experimental E2E environment, which also runs operator-controller and catalogd. It does not create a ClusterExtension or use a catalog. The installation instructions below show how to run only object-controller.

Prerequisites

  • A Kubernetes test cluster where you can install CRDs and cluster-scoped RBAC.
  • A schedulable Linux node labeled node-role.kubernetes.io/control-plane, matching the chart's default node selector. If your cluster lacks this label, customize deployments.templateSpec.nodeSelector when rendering the chart.
  • The cod-example namespace must not already exist. The sample creates it; cleanup deletes it and all resources in it.
  • kubectl and Helm, with kubectl configured for the test cluster.
  • A checkout of this repository. Run the commands below from its root directory.
  • An object-controller image built from the checkout and available to the cluster. See Controller deployment for build and image configuration details. For KIND, load the image into the cluster before installing the controller.

The example uses the new COD name and COS group cod-example. Use a fresh group for the walkthrough: group membership identifies the revision family.

Install the standalone controller

Set the image reference and install only object-controller and its experimental APIs:

OBJECT_CONTROLLER_IMAGE='<your-built-image>'
helm template object-controller helm/olmv1 \
  --set options.featureSet=experimental \
  --set options.objectController.enabled=true \
  --set options.operatorController.enabled=false \
  --set options.catalogd.enabled=false \
  --set-string options.objectController.deployment.image="$OBJECT_CONTROLLER_IMAGE" \
  > /tmp/standalone-object-controller.yaml
kubectl apply -f /tmp/standalone-object-controller.yaml
kubectl -n olmv1-system rollout status deployment/object-controller-controller-manager --timeout=180s

This installation uses the chart's existing cluster-admin service account binding for managing arbitrary Kubernetes resources. ClusterExtension and catalog controllers are unnecessary for standalone COD use.

Create the first revision

The sample in config/samples/olm_v1_clusterobjectdeployment.yaml creates a namespace and a COD whose template contains one inline ConfigMap:

apiVersion: v1
kind: Namespace
metadata:
  name: cod-example
---
apiVersion: olm.operatorframework.io/v1
kind: ClusterObjectDeployment
metadata:
  name: cod-example
spec:
  revisionHistoryLimit: 2
  progressDeadlineMinutes: 10
  template:
    metadata:
      labels:
        app.kubernetes.io/name: cod-example
      annotations:
        example.com/description: Configuration managed by a standalone COD
    spec:
      collisionProtection: Prevent
      phases:
      - name: configuration
        objects:
        - type: InlineManifest
          inlineManifest:
            apiVersion: v1
            kind: ConfigMap
            metadata:
              name: cod-example-settings
              namespace: cod-example
            data:
              message: Hello from revision A
kubectl apply -f config/samples/olm_v1_clusterobjectdeployment.yaml
kubectl wait --for=condition=Available clusterobjectdeployment/cod-example --timeout=180s
kubectl get clusterobjectdeployment cod-example
kubectl get clusterobjectsets --field-selector spec.group=cod-example
kubectl -n cod-example get configmap cod-example-settings -o jsonpath='{.data.message}{"\n"}'

The message is Hello from revision A. The COD creates cod-example-1 with spec.group: cod-example, spec.revision: 1, and spec.lifecycleState: Active. Its Progressing condition becomes False with reason NewClusterObjectSetProgressed after rollout completes, and Available becomes True.

The namespace in the sample is created separately from the COD's managed content. The ConfigMap is owned by the COS. Inspect that ownership and record the ConfigMap's UID for comparison after the update:

kubectl -n cod-example get configmap cod-example-settings \
  -o jsonpath='{.metadata.ownerReferences[?(@.controller==true)].name}{"\n"}{.metadata.uid}{"\n"}'

Change the template

Change the ConfigMap message to B:

kubectl patch clusterobjectdeployment cod-example --type=json \
  -p='[{"op":"replace","path":"/spec/template/spec/phases/0/objects/0/inlineManifest/data/message","value":"Hello from revision B"}]'
kubectl wait --for=create clusterobjectset/cod-example-2 --timeout=180s
kubectl wait --for=condition=Ready clusterobjectset/cod-example-2 --timeout=180s
kubectl -n cod-example get configmap cod-example-settings \
  -o jsonpath='{.data.message}{"\n"}{.metadata.ownerReferences[?(@.controller==true)].name}{"\n"}{.metadata.uid}{"\n"}'

The message becomes Hello from revision B, ownership moves to cod-example-2, and the ConfigMap keeps its UID. During the handoff, more than one COS can be Active. An older Ready revision can keep the COD Available=True while its successor is still progressing. Waiting for the new COS to become Ready observes that specific rollout.

Reapply the original sample to restore A:

kubectl apply -f config/samples/olm_v1_clusterobjectdeployment.yaml
kubectl wait --for=create clusterobjectset/cod-example-3 --timeout=180s
kubectl wait --for=condition=Ready clusterobjectset/cod-example-3 --timeout=180s
kubectl -n cod-example get configmap cod-example-settings -o jsonpath='{.data.message}{"\n"}'
kubectl get clusterobjectsets --field-selector spec.group=cod-example

The message returns to Hello from revision A through revision 3. Restoring an earlier template creates a new, higher revision; an archived revision cannot become Active again. Reapplying an unchanged template keeps the current revision.

Changes to spec.template, including its labels and annotations, create a new revision. Changes to rollout policy, such as spec.progressDeadlineMinutes or spec.revisionHistoryLimit, do not. See Which edits create revisions? for the complete rules.

Read status and diagnose failures

The COD exposes Available and Progressing. A child COS exposes Ready with the details of its resource reconciliation.

For revision-specific failures, the COD's condition message names the COS to inspect. See COD status conditions for the available reasons and Revision ownership for conflicts that prevent a new rollout.

Inspect the parent, the current child, and the controller logs:

kubectl describe clusterobjectdeployment cod-example
kubectl get clusterobjectset cod-example-3 -o yaml
kubectl -n olmv1-system logs deployment/object-controller-controller-manager

A COS can report Ready=False with PreconditionsNotMet for a mutable referenced Secret; before the deadline, the COD reports Progressing=True/RevisionPreconditionsNotMet. A COS reports ObjectsNotReady for objects still waiting on probes. A missing referenced Secret causes a retryable read error in controller logs and leaves the COS's existing Ready condition unchanged. A newly created COS can remain Ready=Unknown with reason Pending until the Secret is available.

Supply the expected immutable Secret content or resolve the readiness failure and let reconciliation retry. Change the COD template when the desired object content needs to change. See COS status conditions for the child reasons.

Adjust the rollout deadline

The sample configures a ten-minute rollout budget with spec.progressDeadlineMinutes. To change that budget without creating another revision, patch the COD directly:

kubectl patch clusterobjectdeployment cod-example --type=merge \
  -p='{"spec":{"progressDeadlineMinutes":20}}'

The new budget is measured from the current COS's original creation timestamp, not from the time of the edit. See Progress deadlines and recovery for timing, timeout precedence, and recovery behavior.

Inspect history and referenced content

The COS controller archives superseded revisions after handoff and cleans up objects that were removed from the desired content. COD prunes excess archived history after teardown completes. Active revisions are excluded from the limit.

The sample retains two archived revisions with spec.revisionHistoryLimit: 2. See Revision history and deletion for retention rules. After A → B → A, revisions 1 and 2 can remain as archived history while revision 3 is Active:

kubectl get clusterobjectsets --field-selector spec.group=cod-example \
  -o custom-columns=NAME:.metadata.name,REVISION:.spec.revision,STATE:.spec.lifecycleState

For larger content, each template object can use the COS secretRef mechanism to read a JSON manifest from an immutable Secret. Create the referenced Secret before applying the COD; it must remain available for every retained revision that references it. Changing the intended content requires a new Secret reference in the template. See Object storage. Standalone callers manage the lifetime of these content Secrets.

With BoxcutterRuntime enabled, operator-controller manages CODs for ClusterExtensions. This standalone workflow uses object-controller without bundle resolution or catalog components. See Relationship with ClusterExtension for the integration.

For all fields, see the COD API reference.

Clean up

Deleting the COD cascades to its owned COSs and managed objects. Wait for the ConfigMap to disappear before removing the separately created namespace. Only remove cod-example if it was created for this walkthrough and contains no unrelated resources:

kubectl delete clusterobjectdeployment cod-example
kubectl -n cod-example wait --for=delete configmap/cod-example-settings --timeout=180s
kubectl delete namespace cod-example

After cleaning up the example resources, remove the standalone installation only if every resource in the rendered manifest is dedicated to this walkthrough. The manifest includes the olmv1-system namespace and the COD/COS CRDs; deleting them also deletes the namespace's contents and all COD/COS instances. If any resources are shared, leave the installation in place:

kubectl delete -f /tmp/standalone-object-controller.yaml