Choose A Resource
- Use
ObjectPatchfor a namespaced target, in the same namespace. - Use
ClusterObjectPatchfor a cluster-scoped target, such as aNamespace.ClusterObjectPatchitself is cluster-scoped.
targetRef has apiVersion, kind, and name, but no namespace. Therefore, an
ObjectPatch cannot target another namespace, and a ClusterObjectPatch cannot
target a namespaced object.
Create A Patch
targetRef identifies an existing object. patch is a non-empty JSON Merge Patch.
managed-by=kubex.
managed-by=kubex to the existing Namespace.
Autoscaling and disruption budget examples
These targets must already exist and their APIs must be installed. The Helm chart supplies optionalget and patch grants by default. See
optional object patch permissions
for the independent opt-out flags.
Use namespaced ObjectPatch resources to update an HPA’s replica limits or allow
unhealthy pod eviction through a PDB. Each target is in patches-demo, the same
namespace as its patch.
autoscaling.openshift.io/v1 ClusterAutoscalers are cluster-scoped.
Use ClusterObjectPatch without a namespace, for example to disable scale-down
on the existing default ClusterAutoscaler:
Selector-aware operations
jsonPatch is optional and may be used alone or together with patch. When
both are present, the merge patch is applied first and selector operations are
applied afterward, in declaration order. Supported operations are add,
remove, and replace.
Each operation requires an RFC 6901 path containing exactly one @ segment.
That segment selects elements from the array at the path prefix. The selector
is a CEL expression evaluated with the current element exposed as item.
matchPolicy defaults to ExactlyOne; use All to modify every matching
element. add and replace require a non-null value; remove must omit it.
Each patch supports at most 100 operations. Paths and selectors are limited to
1024 characters, and selectors that exceed the runtime CEL cost limit fail.
replace supports the selected element itself or any existing descendant path.
add and remove are intentionally limited to one direct object member below
the selected element, such as /containers/@/workingDir. add creates or
replaces that member. remove ensures the member is absent; because value is
not used, omit it.
Array insertion, append, and selected-element removal are not supported because
they are not idempotent across reconciliation retries.
Unmatched array elements and their order are preserved. Selectors must remain
true after every operation and after later operations run. The complete plan
must replay without errors or further changes before the controller sends it.
Patch Semantics
JSON Merge Patch merges object members into the existing object. Nested object members are merged, so adding a label preserves existing labels.nullremoves that field or map key.- An array replaces the complete existing array; arrays are not merged item by item.
null under spec.patch, use
kubectl apply --server-side to create it. kubectl replace is an option only
for updating an existing patch resource; it does not create one. Client-side
kubectl apply consumes null while constructing its own merge patch and may
not store the literal null.
Lifecycle And Status
Creating a patch resource or editing itsspec starts an apply cycle. Arbitrary
metadata edits do not. An already-matching target still succeeds and reports
Applied; success sets Ready=True and condition reason Applied. Successful
cycles reset retry count to 0.
If the target is missing before first apply, status is Pending, reason
TargetNotFound, and it is checked again after requeueInterval. If an applied
target disappears, status is NeedsUpdate, reason TargetMissing; when it
reappears, the patch is applied again. Deletion and recreation between checks is
not detected.
requeueInterval defaults to 5m and must be positive. It controls ordinary
target observation, missing-target checks, duplicate-claim checks, and drift
stabilization observations. Reduce it for faster drift detection when the extra
API reads are acceptable.
After apply, the controller checks the target after each requeueInterval. Without
spec.drift, status becomes NeedsUpdate, reason Drifted, and Drifted=True,
but the controller does not repair the target until the reapply annotation is
set. This preserves report-only behavior for existing resources.
Set spec.drift to enable automatic repair. retryInterval defaults to 5m,
must be positive, and cannot exceed 24 hours. retryAmount defaults to 3;
0 allows unlimited automatic repairs. stabilizationWindow defaults to
15m and must be positive.
When drift is found, the controller increments status.driftRetryCount, stores
the repair deadline in status.nextDriftRetryTime, and waits before reading the
target again. The delay doubles for each repair in the same streak and is capped
at 24 hours. For retryInterval: 5m, repairs wait 5 minutes, 10 minutes, then
20 minutes. If the target still differs at the deadline, the controller repairs
it. If another actor restored it, no patch request is sent.
After repair or external recovery, nextDriftRetryTime is cleared while
driftRetryCount is retained. The controller observes the target after each
requeueInterval through stabilizationWindow. With both defaults, three clean
five-minute observations occur before the counter resets. Drift during this
window continues the same exponential streak. Exhausting a finite drift retry
amount sets terminal Error, reason DriftRetryLimitExceeded, and
Drifted=True; polling stops until the spec changes or the reapply annotation
is set.
The relevant status fields are:
The common status fields follow the patch state.
Pending leaves status.outcome empty and keeps the proposal IN_PROGRESS; Applied sets outcome: Success; NeedsUpdate and Error set outcome: Failure. status.reason contains the current detail and status.observedGeneration identifies the generation that produced it. A proposal becomes APPLIED or FAILED only when that generation matches metadata.generation.
Transient API errors during patch requests consume retry budget; target-read
errors do not. retryAmount defaults to 3 and means retries after the first
attempt: four total attempts by default. retryAmount: 0 retries indefinitely.
This budget is independent of drift.retryAmount. Retrying is Pending, reason
Retrying; finite exhaustion is Error, reason RetryLimitExceeded, and
polling stops.
Reapply A Patch
Set the reapply annotation to authorize one new apply cycle for drift, access recovery, orRetryLimitExceeded:
Permissions And Claims
The automation-controller service account needsget and patch on every
target API resource. Helm enables independent optional grants through
rbac.objectPatches.nodePools, rbac.objectPatches.horizontalPodAutoscalers,
rbac.objectPatches.podDisruptionBudgets, and
rbac.objectPatches.clusterAutoscalers, all defaulting to true. Each creates a
separate ClusterRole and binding granting only get and patch on its target
resource. Set a flag to false to omit that grant; rbac.create: false suppresses
all chart-created RBAC. HPA reads used by safety checks remain in the main role.
Flags do not revoke permissions from external roles. Non-Helm deployments must
supply target permissions separately. Add RBAC for other targets only when the
existing controller permissions do not cover them. See the
RBAC guide for configuration.
Forbidden or Unauthorized target access produces Error, Ready=False, and
reason TargetAccessDenied. After restoring get and patch, change the patch’s
spec or set the reapply annotation to start a new apply cycle.
Patch resources may share a target when their static claims do not conflict. Claims
are arbitrated oldest first by creation timestamp, then name. A claimant blocked by
an accepted claimant does not reserve its other claims. ObjectPatch claims are
compared within their namespace; ClusterObjectPatch claims cluster-wide. A loser
reports Error, reason TargetAlreadyClaimed, and rechecks ownership after
requeueInterval.
Merge-patch claims recurse to leaf paths. Scalars, null, arrays, and empty
objects claim their exact path; arrays are atomic. Parent and child paths conflict.
For selector operations, item.key == "literal" and its reversed form claim the
entire selected array item, so different keys can coexist and the same key conflicts
even when child fields differ. Any other selector claims the entire array at the
path before @.
For example, two ClusterObjectPatch resources targeting the same Karpenter
NodePool can select item.key == "node.kubernetes.io/instance-type" and
item.key == "karpenter.sh/capacity-type" to update different requirement
items without conflicting.

