Skip to content

Commit 210f1ad

Browse files
author
Per G. da Silva
committed
Add ClusterObjectSet documentation
Signed-off-by: Per G. da Silva <pegoncal@redhat.com>
1 parent 435e9c5 commit 210f1ad

1 file changed

Lines changed: 388 additions & 0 deletions

File tree

Lines changed: 388 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,388 @@
1+
# ClusterObjectSets
2+
3+
This document explains what ClusterObjectSets are, how they enable safe phased rollouts of Kubernetes resources, and how operator-controller uses them to manage ClusterExtensions.
4+
5+
## What is a ClusterObjectSet?
6+
7+
A `ClusterObjectSet` is a cluster-scoped Kubernetes API that represents an immutable, versioned set of Kubernetes resources organized into ordered phases. It provides a declarative way to roll out a group of related resources sequentially, with built-in readiness checks between phases.
8+
9+
Each ClusterObjectSet has:
10+
11+
- A **revision number** — an immutable, sequential integer that identifies the version
12+
- A **lifecycle state** — either `Active` (being reconciled) or `Archived` (inactive)
13+
- A list of **phases** — ordered groups of Kubernetes objects that are applied sequentially
14+
- A **collision protection** strategy — controlling how pre-existing objects are handled
15+
- **Status conditions** — reporting rollout progress, availability, and success
16+
17+
ClusterObjectSets can be used by any controller or system that needs to manage the rollout of a set of Kubernetes resources in a controlled, phased manner. Within OLM, the operator-controller uses ClusterObjectSets as the mechanism to deploy and upgrade ClusterExtensions.
18+
19+
## Why ClusterObjectSets?
20+
21+
ClusterObjectSets solve several problems that arise when managing sets of related Kubernetes resources:
22+
23+
- **Immutable revisions** — Because a ClusterObjectSet is immutable once created, it provides a clear, auditable record of exactly what was deployed at each revision. There is no ambiguity about what resources belong to a given version.
24+
- **Phased rollout** — Resources are grouped into phases (e.g. CRDs before Deployments) and applied sequentially. A phase only progresses after all its objects pass readiness probes, preventing partially-applied states.
25+
- **Safe transitions** — During a revision change, both the old and new revisions remain active until the new revision fully rolls out. Object ownership transitions from one revision to the next, ensuring no resource is left unmanaged.
26+
- **Single ownership** — Each Kubernetes resource can only be managed by one ClusterObjectSet at a time. This prevents conflicts between revisions fighting over the same object.
27+
- **Large resource support** — Object manifests can be stored inline or externalized into Secrets via references, allowing ClusterObjectSets to manage bundles that would otherwise exceed etcd's 1.5 MiB object size limit.
28+
29+
## Lifecycle
30+
31+
### Active state
32+
33+
When a ClusterObjectSet is `Active`, the controller actively reconciles it:
34+
35+
- Phases are applied sequentially, each waiting for readiness before proceeding
36+
- Objects are managed via server-side apply
37+
- Status conditions are updated to reflect rollout progress
38+
39+
### Revision transitions
40+
41+
When transitioning from one revision to the next:
42+
43+
1. A new ClusterObjectSet is created with the next sequential revision number
44+
2. Both old and new revisions remain `Active` during the transition
45+
3. Objects transition ownership from the old revision to the new one
46+
4. Once the new revision reports `Succeeded`, the old revision can be archived
47+
48+
### Archival
49+
50+
When a revision's lifecycle state is set to `Archived`:
51+
52+
- It is removed from the owner list of all previously managed objects
53+
- Objects that did not transition to a succeeding revision are deleted
54+
- The revision cannot be un-archived
55+
- It remains in the cluster for historical reference until garbage collected
56+
57+
## Phases
58+
59+
Objects within a ClusterObjectSet are organized into phases. Each phase groups related resources, and phases are applied sequentially. Within a phase, all objects are applied simultaneously in no particular order.
60+
61+
A phase only progresses to the next after all of its objects pass their readiness probes. This ensures dependencies are satisfied before dependents are created — for example, CRDs are established before Deployments that use custom resources.
62+
63+
Phase names follow the DNS label standard ([RFC 1123](https://tools.ietf.org/html/rfc1123)) and must be unique within a ClusterObjectSet. A ClusterObjectSet supports up to 20 phases, with up to 50 objects per phase.
64+
65+
### Phase ordering in operator-controller
66+
67+
When operator-controller creates a ClusterObjectSet for a ClusterExtension, it automatically assigns objects to phases based on their GroupKind:
68+
69+
| Order | Phase | Resource Kinds |
70+
| --- | --- | --- |
71+
| 1 | `namespaces` | Namespace |
72+
| 2 | `policies` | NetworkPolicy, PodDisruptionBudget, PriorityClass |
73+
| 3 | `identity` | ServiceAccount |
74+
| 4 | `configuration` | Secret, ConfigMap |
75+
| 5 | `storage` | PersistentVolume, PersistentVolumeClaim, StorageClass |
76+
| 6 | `crds` | CustomResourceDefinition |
77+
| 7 | `roles` | ClusterRole, Role |
78+
| 8 | `bindings` | ClusterRoleBinding, RoleBinding |
79+
| 9 | `infrastructure` | Service, Issuer (cert-manager) |
80+
| 10 | `deploy` | Certificate (cert-manager), Deployment |
81+
| 11 | `scaling` | VerticalPodAutoscaler |
82+
| 12 | `publish` | PrometheusRule, ServiceMonitor, PodMonitor, Ingress, Route |
83+
| 13 | `admission` | ValidatingWebhookConfiguration, MutatingWebhookConfiguration |
84+
85+
!!! note
86+
This phase ordering is specific to how operator-controller creates ClusterObjectSets. The API itself does not enforce any particular phase ordering — phases are applied in the order they appear in the `spec.phases` list.
87+
88+
## Readiness probes
89+
90+
A phase only progresses to the next after all of its objects pass readiness probes. Several resource kinds have built-in probes:
91+
92+
| Resource Kind | Readiness Criteria |
93+
| --- | --- |
94+
| CustomResourceDefinition | Condition `Established` = True |
95+
| Namespace | `status.phase` = "Active" |
96+
| PersistentVolumeClaim | `status.phase` = "Bound" |
97+
| Deployment | `status.updatedReplicas` == `spec.replicas` and condition `Available` = True |
98+
| StatefulSet | `status.updatedReplicas` == `spec.replicas` and condition `Available` = True |
99+
| Certificate (cert-manager) | Condition `Ready` = True |
100+
| Issuer (cert-manager) | Condition `Ready` = True |
101+
102+
For custom resources or other objects that need tailored checks, you can define custom progression probes in the `spec.progressionProbes` field. These are experimental and support three assertion types:
103+
104+
`ConditionEqual`
105+
: Checks that an object has a condition of the specified type and status (e.g. `Ready` = `True`).
106+
107+
`FieldsEqual`
108+
: Checks that the values at two field paths match (e.g. `spec.replicas` == `status.readyReplicas`).
109+
110+
`FieldValue`
111+
: Checks that a field has a specific value (e.g. `status.phase` = `"Bound"`).
112+
113+
Probes use selectors to target objects by GroupKind or by label. A probe only runs against objects matching its selector — if no objects in a phase match, the probe is considered to have passed.
114+
115+
## Collision protection
116+
117+
Collision protection controls whether a ClusterObjectSet can adopt pre-existing objects on the cluster. This is configured at three levels, with the most specific taking precedence:
118+
119+
**object > phase > spec**
120+
121+
The available strategies are:
122+
123+
`Prevent`
124+
: Only manages objects the revision created itself. This is the safest option and prevents ownership collisions entirely.
125+
126+
`IfNoController`
127+
: Can adopt pre-existing objects that are not owned by another controller. Useful when taking over management of manually-created resources.
128+
129+
`None`
130+
: Can adopt any pre-existing object, even if owned by another controller. Use with extreme caution — this can cause multiple controllers to fight over the same resource.
131+
132+
## Object storage: inline vs. references
133+
134+
Each object in a phase can be stored in one of two ways (exactly one must be set):
135+
136+
`object`
137+
: The full Kubernetes manifest is embedded inline in the ClusterObjectSet. Simple and self-contained, but contributes to the overall size of the ClusterObjectSet resource in etcd.
138+
139+
`ref`
140+
: A reference to a Secret that holds the serialized object manifest. The `ref` specifies the Secret name, namespace, and data key containing the JSON-encoded manifest. This allows ClusterObjectSets to manage bundles that would otherwise exceed etcd's 1.5 MiB object size limit.
141+
142+
When operator-controller creates ClusterObjectSets for ClusterExtensions, it automatically externalizes objects into immutable Secrets:
143+
144+
- Secrets are packed up to 900 KiB each, leaving headroom below the etcd limit
145+
- Objects larger than 900 KiB are gzip-compressed before storage
146+
- Secrets use content-addressable naming for deduplication
147+
148+
For a detailed design discussion, see [Large Bundle Support](../../concepts/large-bundle-support.md).
149+
150+
## Status conditions
151+
152+
ClusterObjectSets report three conditions that describe their current state:
153+
154+
### Progressing
155+
156+
Indicates whether the revision is actively rolling out.
157+
158+
| Status | Reason | Meaning |
159+
| --- | --- | --- |
160+
| True | `RollingOut` | Actively making progress |
161+
| True | `Retrying` | Encountered a retryable error |
162+
| True | `Succeeded` | Reached the desired state |
163+
| False | `Blocked` | Error requiring manual intervention |
164+
| False | `Archived` | No longer actively reconciled |
165+
166+
### Available
167+
168+
Indicates whether all objects have been successfully rolled out and pass readiness probes.
169+
170+
| Status | Reason | Meaning |
171+
| --- | --- | --- |
172+
| True | `ProbesSucceeded` | All objects pass readiness probes |
173+
| False | `ProbeFailure` | One or more probes failing |
174+
| Unknown | `Reconciling` | Error prevented probe observation |
175+
| Unknown | `Archived` | Objects torn down after archival |
176+
| Unknown | `Migrated` | Migrated from existing release; probes not yet observed |
177+
178+
### Succeeded
179+
180+
A terminal condition set once the rollout completes. It persists even if the revision later becomes unavailable, marking that this version was successfully deployed at least once.
181+
182+
## How operator-controller uses ClusterObjectSets
183+
184+
Within OLM, ClusterObjectSets serve as the deployment mechanism for `ClusterExtensions`. The operator-controller is one consumer of the ClusterObjectSet API.
185+
186+
### Installation
187+
188+
When a ClusterExtension is installed:
189+
190+
1. The operator-controller resolves a bundle from the catalog
191+
2. The bundle's Kubernetes manifests are sorted into phases by resource kind
192+
3. Objects are packed into immutable Secrets
193+
4. A ClusterObjectSet is created with `revision: 1` in `Active` lifecycle state
194+
5. Phases roll out sequentially — each phase waits for all its objects to become ready before the next phase begins
195+
196+
### Upgrades
197+
198+
When a ClusterExtension is upgraded to a new version:
199+
200+
1. A new ClusterObjectSet is created with `revision: N+1`
201+
2. Both old and new revisions are `Active` during the transition
202+
3. Objects transition ownership from the old revision to the new one
203+
4. Once the new revision reports `Succeeded`, the old revision is archived
204+
5. Older archived revisions are garbage collected, keeping the last 5 for auditing
205+
206+
### Relationship with ClusterExtension
207+
208+
A `ClusterExtension` owns one or more `ClusterObjectSet` resources. The ClusterExtension's `.status.activeRevisions` field lists all currently active (non-archived) revisions along with their status conditions.
209+
210+
When created by operator-controller, ClusterObjectSets carry labels that identify the parent:
211+
212+
```yaml
213+
metadata:
214+
labels:
215+
olm.operatorframework.io/owner-kind: ClusterExtension
216+
olm.operatorframework.io/owner-name: my-extension
217+
olm.operatorframework.io/package-name: my-operator
218+
olm.operatorframework.io/bundle-version: 1.2.3
219+
```
220+
221+
## Examples
222+
223+
### Inline objects
224+
225+
This example shows a ClusterObjectSet with objects embedded directly in the spec. This is the simplest approach and works well when the total size of all objects stays within etcd's 1.5 MiB limit.
226+
227+
```yaml
228+
apiVersion: olm.operatorframework.io/v1
229+
kind: ClusterObjectSet
230+
metadata:
231+
name: my-app-rev1
232+
spec:
233+
revision: 1
234+
lifecycleState: Active
235+
collisionProtection: Prevent
236+
phases:
237+
- name: crds
238+
objects:
239+
- object:
240+
apiVersion: apiextensions.k8s.io/v1
241+
kind: CustomResourceDefinition
242+
metadata:
243+
name: widgets.example.com
244+
spec:
245+
group: example.com
246+
names:
247+
kind: Widget
248+
plural: widgets
249+
scope: Namespaced
250+
versions:
251+
- name: v1
252+
served: true
253+
storage: true
254+
schema:
255+
openAPIV3Schema:
256+
type: object
257+
properties:
258+
spec:
259+
type: object
260+
- name: rbac
261+
objects:
262+
- object:
263+
apiVersion: v1
264+
kind: ServiceAccount
265+
metadata:
266+
name: my-app-controller
267+
namespace: my-app-system
268+
- object:
269+
apiVersion: rbac.authorization.k8s.io/v1
270+
kind: ClusterRole
271+
metadata:
272+
name: my-app-manager-role
273+
rules:
274+
- apiGroups: ["example.com"]
275+
resources: ["widgets"]
276+
verbs: ["get", "list", "watch", "create", "update", "delete"]
277+
- name: deploy
278+
objects:
279+
- object:
280+
apiVersion: apps/v1
281+
kind: Deployment
282+
metadata:
283+
name: my-app-controller
284+
namespace: my-app-system
285+
spec:
286+
replicas: 1
287+
selector:
288+
matchLabels:
289+
app: my-app-controller
290+
template:
291+
metadata:
292+
labels:
293+
app: my-app-controller
294+
spec:
295+
serviceAccountName: my-app-controller
296+
containers:
297+
- name: manager
298+
image: example.com/my-app-controller:v1.0.0
299+
```
300+
301+
### Secret references
302+
303+
This example shows a ClusterObjectSet that references objects stored in Secrets. This is how operator-controller creates ClusterObjectSets, and is necessary when managing bundles with large resources like CRDs with extensive schemas.
304+
305+
First, create the Secrets containing the object manifests:
306+
307+
```yaml
308+
apiVersion: v1
309+
kind: Secret
310+
metadata:
311+
name: my-app-rev1-a1b2c3d4
312+
namespace: olmv1-system
313+
immutable: true
314+
data:
315+
# Each key is a content-addressable hash of the object JSON
316+
# Value is the JSON-serialized Kubernetes manifest
317+
Yldpa2V0cy5leGFtcGxlLmNvbQ: <base64-encoded CRD JSON>
318+
---
319+
apiVersion: v1
320+
kind: Secret
321+
metadata:
322+
name: my-app-rev1-e5f6g7h8
323+
namespace: olmv1-system
324+
immutable: true
325+
data:
326+
c2VydmljZWFjY291bnQ: <base64-encoded ServiceAccount JSON>
327+
Y2x1c3RlcnJvbGU: <base64-encoded ClusterRole JSON>
328+
ZGVwbG95bWVudA: <base64-encoded Deployment JSON>
329+
```
330+
331+
Then reference them from the ClusterObjectSet:
332+
333+
```yaml
334+
apiVersion: olm.operatorframework.io/v1
335+
kind: ClusterObjectSet
336+
metadata:
337+
name: my-app-rev1
338+
spec:
339+
revision: 1
340+
lifecycleState: Active
341+
collisionProtection: Prevent
342+
phases:
343+
- name: crds
344+
objects:
345+
- ref:
346+
name: my-app-rev1-a1b2c3d4
347+
namespace: olmv1-system
348+
key: "Yldpa2V0cy5leGFtcGxlLmNvbQ"
349+
- name: rbac
350+
objects:
351+
- ref:
352+
name: my-app-rev1-e5f6g7h8
353+
namespace: olmv1-system
354+
key: "c2VydmljZWFjY291bnQ"
355+
- ref:
356+
name: my-app-rev1-e5f6g7h8
357+
namespace: olmv1-system
358+
key: "Y2x1c3RlcnJvbGU"
359+
- name: deploy
360+
objects:
361+
- ref:
362+
name: my-app-rev1-e5f6g7h8
363+
namespace: olmv1-system
364+
key: "ZGVwbG95bWVudA"
365+
```
366+
367+
Multiple objects can reference different keys within the same Secret, allowing efficient packing of smaller objects.
368+
369+
## Inspecting ClusterObjectSets
370+
371+
```bash
372+
# List all ClusterObjectSets
373+
kubectl get clusterobjectsets
374+
375+
# List revisions for a specific extension
376+
kubectl get clusterobjectsets -l olm.operatorframework.io/owner-name=my-extension
377+
378+
# View full details for a specific revision
379+
kubectl get clusterobjectset <name> -o yaml
380+
```
381+
382+
Example output:
383+
384+
```
385+
NAME AVAILABLE PROGRESSING AGE
386+
my-extension-abc12 Unknown False 2d
387+
my-extension-def34 True True 1h
388+
```

0 commit comments

Comments
 (0)