PodMutation API Reference
PodMutation is a cluster-scoped custom resource that selects workloads and
rewrites scheduling-related Pod fields during admission.
apiVersion: agent.cloudpilot.ai/v1alpha1
kind: PodMutationThe short name is pm:
kubectl get pmProcessing model
PodMutation follows this order for each newly created Pod:
- Evaluate every enabled PodMutation against the original incoming Pod.
- Sort matching resources by ascending
spec.priority, then by resource name. - For each matching resource, apply
mutationRemovebeforemutationAdd. - Append CloudPilot-managed Spot, NonSpot, and Diversity affinity.
All resources are matched before mutation begins. A field added by one PodMutation therefore cannot cause another PodMutation to match.
PodMutation does not update running Pods in place. Recreate or roll out a workload after creating or changing a rule. The webhook refreshes its PodMutation cache every 30 seconds.
Spec fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
enable | boolean | No | true | Enables or disables the resource. |
priority | integer | No | 0 | Controls execution order. Lower values run first. Rules with the same priority are ordered by name. |
workloadSelector | object | Yes | — | Selects workloads and Pods. At least one selector field must be non-empty. |
mutationRemove | object | No | — | Removes fields before mutationAdd is applied. |
mutationAdd | object | No | — | Adds or replaces supported Pod fields. |
workloadSelector
Selector dimensions are ANDed together. Within one list field, values are ORed unless stated otherwise.
| Field | Type | Matching behavior |
|---|---|---|
namespaces | string array | The workload namespace must equal one listed value. |
names | string array | The workload name must equal one listed value. |
kinds | string array | The workload kind must be Deployment, StatefulSet, Job, or CronJob and equal one listed value. |
nodeSelectorMatch | map of string to string | The Pod nodeSelector must contain every configured key-value pair. |
nodeAffinityMatch | node affinity object | The original Pod node affinity must contain a matching required and/or preferred term. |
tolerationMatch | toleration array | At least one Pod toleration must exactly match one configured key, operator, value, and effect tuple. |
DaemonSet-owned Pods are not mutated.
nodeAffinityMatch
nodeAffinityMatch uses the Kubernetes node affinity YAML shape, restricted to
matchExpressions.
Required matching preserves Kubernetes term semantics:
nodeSelectorTermsare ORed.- Requirements inside one term are ANDed.
- The selector term may be a subset of the Pod term.
- A requirement must have the same key and operator.
- Values are compared as a set.
Preferred matching requires the same weight and a matching preference term. If both required and preferred selectors are configured, both must match.
matchFields is not supported and is rejected by the API server.
mutationRemove
| Field | Type | Behavior |
|---|---|---|
nodeSelectorKeys | string array | Removes the listed keys from spec.nodeSelector. |
tolerationKeys | string array | Removes tolerations whose key is listed. |
nodeAffinity | object | Removes node affinity requirements by key or removes the complete node affinity. |
mutationRemove.nodeAffinity
Exactly one of these fields must be configured:
| Field | Type | Behavior |
|---|---|---|
keys | string array | Removes matching keys from required and preferred matchExpressions and from incoming Pod matchFields. Maximum 64 keys. |
all | boolean | When true, removes the complete incoming nodeAffinity. |
If key removal empties a preferred term, that term is removed. If it empties a required OR term, the complete required selector is cleared so the webhook does not emit an empty term that Kubernetes treats as matching no nodes.
all: true affects only nodeAffinity; it preserves podAffinity and
podAntiAffinity.
mutationAdd
| Field | Type | Behavior |
|---|---|---|
annotations | map of string to string | Adds annotations and overwrites duplicate keys. |
labels | map of string to string | Adds labels and overwrites duplicate keys. |
nodeSelector | map of string to string | Adds node selector entries and overwrites duplicate keys. |
tolerations | toleration array | Adds tolerations and deduplicates them by key, operator, value, and effect. |
nodeAffinity | node affinity object | Logically combines required affinity and appends preferred affinity. |
Adding required node affinity performs logical AND with the current required selector. Because each side may contain OR terms, the webhook creates the Cartesian combination of both term sets, then canonicalizes duplicate and redundant terms. Preferred terms are appended and deduplicated.
Adding a requirement does not implicitly replace another requirement that uses the same key. Use explicit remove-then-add when replacement is required.
Node affinity schema
Both nodeAffinityMatch and mutationAdd.nodeAffinity support these fields:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: example.com/node-pool
operator: In
values:
- pool-a
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 50
preference:
matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- us-east-1aRequirement operators
| Operator | values requirement |
|---|---|
In | One or more values. |
NotIn | One or more values. |
Exists | No values. |
DoesNotExist | No values. |
Gt | Exactly one integer value. |
Lt | Exactly one integer value. |
Keys and values must use valid Kubernetes label syntax. Preferred weights must be between 1 and 100.
Reserved CloudPilot-managed affinity
The following keys are managed by CloudPilot AI and cannot be used in
nodeAffinityMatch or mutationAdd.nodeAffinity, or removed through
mutationRemove.nodeAffinity.keys:
karpenter.sh/capacity-typeeks.amazonaws.com/capacityTypecloud.google.com/gke-provisioningnode.kubernetes.io/instance-type
CloudPilot-managed affinity is added after customer PodMutation rules. It is
therefore still present after mutationRemove.nodeAffinity.all: true.
Safety limits
The CRD and webhook bound affinity expansion during admission:
- maximum 64 configured required terms;
- maximum 64 configured preferred terms;
- maximum 64 configured expressions in one term;
- maximum 64 values in one expression;
- maximum 64 required or preferred terms after multiple mutations are combined;
- maximum 64 requirements in one combined required term;
- maximum 4,096 intermediate required terms before canonicalization;
- maximum 256 raw required terms, or 256 requirements in one raw term, on the incoming Pod.
If matching rules exceed a runtime combination limit, Pod admission is rejected with an error instead of emitting an oversized or semantically broadened affinity expression.
Complete example
apiVersion: agent.cloudpilot.ai/v1alpha1
kind: PodMutation
metadata:
name: migrate-legacy-pool
spec:
enable: true
priority: 100
workloadSelector:
namespaces:
- production
kinds:
- Deployment
- StatefulSet
nodeAffinityMatch:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: example.com/node-pool
operator: In
values:
- legacy
mutationRemove:
nodeAffinity:
keys:
- example.com/node-pool
mutationAdd:
annotations:
example.com/migration: merged-pool
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: example.com/node-pool
operator: In
values:
- mergedWait for the webhook cache to refresh, then roll out the selected workloads so their replacement Pods pass through admission.
For a migration walkthrough, see Use PodMutation During a NodePool Merge.