API Reference
AttunePolicy¶
Group: attune.io
Version: v1alpha1
Scope: Namespaced
Short name: ap
Defaulting Behavior¶
Fields are defaulted in three layers. Only weight
appears in the stored spec when omitted by the user (it is a CRD schema
default). All other defaultable fields (type, controlledValues,
cooldown, historyWindow, minimumDataPoints, queryStep, rateWindow, podAggregation, autoRevert,
resizeMethod, cpu.maxChangePercent, memory.maxChangePercent,
safetyObservationPeriod, excludeKnownSidecars, maxConcurrentResizes,
metricsSource) are applied
by the controller at reconcile time so that cluster-wide AttuneDefaults
and namespace-scoped AttuneNamespaceDefaults can override them. These
fields will appear empty in kubectl get attunepolicy -o yaml but still control runtime
behavior through the controller's built-in and inherited defaults unless you
override them. Use kubectl attune explain -n <namespace> <policy> to see
the effective values for the key controller-applied defaults and whether each
came from the policy, a namespace default, a cluster default, or the built-in
default.
Spec¶
apiVersion: attune.io/v1alpha1
kind: AttunePolicy
metadata:
name: example
namespace: default
spec:
# Target workload(s) to right-size.
targetRef:
kind: Deployment # Deployment | StatefulSet | DaemonSet | CronJob | Job | ReplicaSet | Rollout
name: my-app # optional: target a specific workload
selector: # optional: target by label selector
matchLabels:
tier: api
# Prometheus metrics configuration.
metricsSource:
prometheus:
address: "http://prometheus:9090" # Prometheus-compatible URL
headers: # optional: non-secret tenant or routing headers
X-Scope-OrgID: "my-tenant" # e.g., Mimir tenant ID
queryParameters: # optional: URL params for Thanos/VictoriaMetrics
dedup: "true" # e.g., Thanos deduplication
partial_response: "true" # reserved query keys like query/start/end/step/time/timeout are rejected
bearerTokenSecret: # optional: Secret in the policy namespace (deprecated on AttuneDefaults)
name: prometheus-token
key: token
# sigv4 signs Amazon Managed Prometheus (service aps). Off until set.
# Do not combine with bearerTokenSecret, an Authorization header, or
# an X-Amz-* header. Region has no default. Empty roleArn uses the
# pod identity chain (IRSA or Pod Identity).
# sigv4:
# region: us-east-1
# roleArn: arn:aws:iam::123456789012:role/amp-query
tls: # optional: TLS settings
insecureSkipVerify: false # default: false
# At most one of prometheus, datadog, cloudwatch, or vpa may be set.
# datadog is off until this block is set. site defaults to datadoghq.com.
# datadog:
# site: datadoghq.com
# apiKeySecretRef: # required when datadog is set
# name: datadog-api # policy namespace; on AttuneDefaults the name is copied onto each policy
# key: api-key
# cloudwatch is off until this block is set. region and clusterName are required.
# Empty cpuUnit means Millicores (divide by 1000), including a policy that
# sets cloudwatch and omits cpuUnit. Cores leaves the value unchanged.
# Nanocores divides by 1e9 and is the older scale; set it explicitly.
# A defaults cpuUnit is copied only with a whole cloudwatch block, and
# only onto a policy that sets no provider.
# cloudwatch:
# region: us-east-1
# clusterName: prod
# roleArn: arn:aws:iam::123456789012:role/cloudwatch-read # optional
# cpuUnit: Millicores
# Alternative: consume VPA recommendations instead of querying Prometheus.
# vpa:
# name: my-vpa # VPA object name
# namespace: default # defaults to policy namespace
historyWindow: 168h # lookback window (default: 168h)
minimumDataPoints: 48 # min samples before recommending (default: 48)
queryStep: 5m # Prometheus range query step interval (default: 5m)
rateWindow: 5m # PromQL rate() window for CPU queries (default: queryStep)
podAggregation: Max # Max (default) | Avg | None; max by (container) for scale
# CPU recommendation parameters.
cpu:
percentile: 95 # target percentile: 50, 90, 95, or 99
overhead: "20" # percentage headroom above percentile
burstSensitivity: "0.1" # burst boost multiplier (0 = disabled, max 1.0)
startupBoost: # optional: temporary CPU boost for cold starts. Off until set. Policy-wide.
multiplier: "3.0" # scale factor for startup CPU (1.1-10.0)
duration: 2m # boost window after pod creation (10s-1h)
excludeFromHistory: false # omitted is false. true drops CPU samples during duration plus rateWindow.
# surge is off until set. {} turns the short window on and fills
# triggerRatio 1.5, percentile 99, and window 30m. Policy-wide.
# A policy that omits surge inherits an AttuneDefaults surge.
# surge: {}
minAllowed: "1m" # omitted floor is 1m; an explicit value replaces it
maxAllowed: "4000m" # example explicit cap, not the omitted default. Omitted means no maximum. Admission rejects above 256 cores. "0" is a real cap.
controlledValues: RequestsAndLimits # default is RequestsOnly. A set limitMultiplier on this policy requires RequestsAndLimits here at admission.
# limitMultiplier: "2" # off until set. Omitted keeps the live request-to-limit ratio. "1" forces the limit equal to the request and still needs RequestsAndLimits. Omitted mode is rejected. Defaults may omit the mode. A stored policy is not rewritten. A container with no current limit keeps that limit omitted. Maximum 100. maxAllowed caps the request, not the limit.
maxChangePercent: 50 # max CPU change per cycle (default: 50)
maxIncreasePercent: 50 # max increase per cycle (default: 50)
maxDecreasePercent: 30 # max decrease per cycle (default: 30)
# Memory recommendation parameters.
memory:
percentile: 99
overhead: "30"
burstSensitivity: "0.1"
minAllowed: "4Mi" # omitted floor is 4Mi; an explicit value replaces it
maxAllowed: "8Gi" # example explicit cap, not the omitted default. Omitted means no maximum. Admission rejects above 16Ti. "0" is a real cap.
controlledValues: RequestsAndLimits # default is RequestsOnly
# limitMultiplier: "2" # off until set. Same rules as cpu.limitMultiplier. Policy-wide.
allowDecrease: false # prevent memory decreases (default: false)
decreaseUsageMarginPercent: 10 # default 10. Headroom above usage when decreasing a memory limit. 0 requires the limit strictly above usage. Ignored for CPU. Policy-wide.
maxChangePercent: 30 # max memory change per cycle (default: 30)
maxIncreasePercent: 50 # max increase per cycle (default: 50)
maxDecreasePercent: 30 # max decrease per cycle (default: 30)
memoryFromCpuRatio: "2.0" # off until set. GiB of memory per CPU core. Policy-wide. Does not fall back to the memory signal.
# oomBump is off until set. {} turns it on and fills ratio 1.2, minBump
# 100Mi, maxBumps 3, and hold 24h. When maxAllowed is omitted, maxBumps
# is the only cap. CPU rejects this block. Policy-wide.
# oomBump:
# ratio: "1.2"
# minBump: 100Mi
# maxBumps: 3
# hold: 24h
# surge: {} # off until set. Same fill rules as cpu.surge. Policy-wide.
# AttunePolicy only. Not inherited from AttuneDefaults. Omitted keeps
# one shared cpu and memory engine. There is no implicit "*".
# Write containerName "*" once for a field-wise fallback. A literal name
# wins over "*", and "*" wins over the merged policy block.
# An entry may set percentile, overhead, minAllowed, maxAllowed,
# burstSensitivity, maxChangePercent, maxIncreasePercent,
# maxDecreasePercent, allowDecrease, and controlledValues.
# The webhook rejects startupBoost, memoryFromCpuRatio,
# decreaseUsageMarginPercent, limitMultiplier, oomBump, and surge here.
# Omitted maxAllowed inherits "*" and then the policy max. It is uncapped
# only when that effective value is still unset.
containerPolicies:
- containerName: app
cpu:
maxAllowed: "500m" # example explicit cap for this container, not a default
# Pause reconciliation (no metrics, no recommendations, no resizes).
paused: false # default: false
# How and when to apply changes.
updateStrategy:
type: Recommend # Observe | Recommend | OneShot | Canary | Auto
initialSizing: false # optional: set pod resources at creation time via webhook
canary: # required when type is Canary
percentage: 10 # % of pods to resize first
observationPeriod: 30m # example. Omit or 0s for 5m. Shortest accepted value is 1m.
autoPromote: false # default. true promotes the rest of the fleet after a clean observation.
cooldown: 1h # min time between resizes of the same workload (default: 1h)
autoRevert: true # revert on safety violation (default: true)
safetyObservationPeriod: 5m # post-resize safety watch period (default: 5m, minimum: 1m)
resizeMethod: InPlaceOnly # InPlaceOnly | InPlaceOrRecreate (default: InPlaceOnly)
# maxConcurrentResizes omitted so AttuneDefaults can apply (built-in: 1, max: 50)
maxCpuIncreasePerMinute: "2000m" # optional. Unlimited until set. Aggregate CPU increase per wall-clock minute.
maxMemoryIncreasePerMinute: "4Gi" # optional. Unlimited until set. Aggregate memory increase per wall-clock minute.
# Older per-cycle caps. Optional, and unlimited until set. Prefer the
# per-minute fields. When both kinds are set, both apply. The per-cycle
# rate depends on reconcile interval.
# maxTotalCpuIncrease: "2000m"
# maxTotalMemoryIncrease: "4Gi"
maxStatusRecommendations: 100 # default 100. Caps status.recommendations. Resizes still use the full set.
includeExplanationsInStatus: true # default true. false strips explanation chains from status.
export: # optional: export recommendations
configMap: true # creates <policy>-<workload>-recommendations ConfigMap
# pullRequest is off until enabled is true. Requires repository and
# tokenSecretRef when enabled. provider defaults to github, baseBranch
# to main, cooldown to 24h, minChangePercent to 10, dryRun to false.
# pullRequest:
# enabled: false
# provider: github
# repository: owner/repo
# tokenSecretRef:
# name: gitops-token
# key: token
# baseBranch: main
# apiUrl: https://api.github.com # optional enterprise or self-hosted base
# allowPrivateEndpoints: false # default false. Loopback and link-local stay blocked.
# cooldown: 24h
# minChangePercent: 10
# dryRun: false
# labels: []
templatePersistence: # optional: write recs into Deploy/STS template
enabled: false # default off; triggers rolling update when true
when: AfterSuccessfulResize # or OnRecommendation
sloGuardrails: # optional: revert if application SLOs breach after resize
- name: p99-latency
query: 'histogram_quantile(0.99, rate(http_request_duration_seconds_bucket{namespace="{{ .Namespace }}"}[5m]))'
threshold: "0.5"
comparison: above
evaluationWindow: 5m # default 5m. Shortest accepted value is 1m. Zero is invalid.
schedule: # optional: restrict when resizes can occur
windows:
- start: "02:00" # HH:MM (24-hour)
end: "06:00"
daysOfWeek: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
timezone: "America/New_York" # IANA timezone (default: UTC)
# hpaTargetBounds does not turn auto-tune on. The HPA still needs
# attune.io/auto-tune: "true". A successful in-place memory resize
# retunes memory utilization. Canary waits until that workload is
# promoted. This band only clamps the published percent. CPU bounds
# do not apply to memory, and memory bounds do not apply to CPU.
# Unset means no band. 50 and 90 are an example, not a default.
# hpaTargetBounds:
# cpu:
# min: 50
# max: 90
# memory:
# min: 50
# max: 90
# Auto-exclude well-known mesh/sidecar names (default: true).
# Set false to restore pre-feature behavior (only excludedContainers).
excludeKnownSidecars: true
# Extra container names to skip (unioned with the known list when
# excludeKnownSidecars is true).
excludedContainers:
- my-company-agent
# Policy priority (1-1000, higher wins). Default: 100.
weight: 100
Status¶
| Field | Type | Description |
|---|---|---|
conditions |
[]Condition |
Standard Kubernetes conditions (Ready, Resizing, Degraded, ScheduleBlocked, ResizeBlocked) |
cooldown.effectiveCooldown |
Duration |
Current cooldown including exponential backoff |
cooldown.backoffMultiplier |
int32 |
Current backoff multiplier (1, 2, 4, 8, or 16) |
cooldown.consecutiveReverts |
int32 |
Number of consecutive reverts driving the backoff |
workloads.discovered |
int32 |
Number of workloads matching the target |
workloads.withRecommendations |
int32 |
Workloads with active recommendations |
workloads.resized |
int32 |
Workloads with a successful in-place resize in the latest reconcile. That is this cycle's apply, plus a successful in-place row a concurrent reconcile wrote during this reconcile. Rows already in the snapshot do not count, and neither does a success from an earlier hour. Retained resizeHistory is not the source of this count. An idle reconcile stores 0. |
workloads.pending |
int32 |
Workloads with a recommendation this cycle that were not resized this cycle (withRecommendations - resized, floored at 0). An idle cycle stores the recommendation count. |
workloads.deferred |
int32 |
Pods with kubelet Deferred in-place resize (retry when cleared) |
workloads.infeasible |
int32 |
Pods with Infeasible in-place resize on their node |
workloads.dataPointsCollected |
int32 |
Max data points collected across all containers |
workloads.dataPointsRequired |
int32 |
Minimum data points needed before recommendations |
recommendations[].workload |
string |
Workload name |
recommendations[].kind |
string |
Workload kind |
recommendations[].containers[].name |
string |
Container name |
recommendations[].containers[].current |
ResourceValues |
Current CPU/memory requests and limits |
recommendations[].containers[].recommended |
ResourceValues |
Proposed CPU/memory requests and limits |
recommendations[].containers[].explanation |
ContainerRecommendationExplanation |
Persisted reasoning used by kubectl attune explain |
recommendations[].containers[].explanation.cpu |
ResourceRecommendationExplanation |
CPU estimator-chain details |
recommendations[].containers[].explanation.memory |
ResourceRecommendationExplanation |
Memory estimator-chain details |
recommendations[].containers[].confidence |
float64 |
Confidence score (0-1) |
recommendations[].containers[].dataPoints |
int32 |
Prometheus samples used |
recommendations[].containers[].lastUpdated |
Time |
Last recommendation timestamp |
recommendations[].stale |
bool |
true when the newest finite sample is older than 3 * queryStep (default 15m), or when the last rec was reused after an empty query. Resize, boost, initial sizing webhook, template persistence, ConfigMap rewrite, kubectl preview/diff/wizard, and GitOps drift are skipped until fresh data arrives. Stale recs do not count toward Ready or savings. Reuse expires after the same freshness bound |
recommendations[].lastDataTime |
Time |
Timestamp of the most recent Prometheus data point |
| savings.cpuRequestReduction | string | Total CPU request reduction (e.g. "1200m") |
| savings.cpuRequestTotal | string | Total current CPU requests across all workloads (e.g. "2000m") |
| savings.memoryRequestReduction | string | Total memory request reduction (e.g. "2Gi") |
| savings.memoryRequestTotal | string | Total current memory requests across all workloads (e.g. "2Gi") |
| savings.estimatedMonthlySavings | string | Estimated monthly cost savings |
| savings.cpuRequestIncrease | string | Total CPU increase for under-provisioned workloads (e.g. "500m") |
| savings.memoryRequestIncrease | string | Total memory increase for under-provisioned workloads (e.g. "512Mi") |
| savings.estimatedMonthlyCostIncrease | string | Estimated monthly cost increase for under-provisioned workloads |
| savings.reclaimedCpuRequest | string | Freeable CPU request if recommended decreases apply (alias of cpuRequestReduction) |
| savings.reclaimedMemoryRequest | string | Freeable memory request if recommended decreases apply (alias of memoryRequestReduction) |
| resizeHistory | []ResizeHistoryEntry | Retained resize rows, capped at 50. Not a time window, and not the source of workloads.resized or Resizing=InProgress. |
| resizeHistory[].timestamp | Time | When the resize occurred |
| resizeHistory[].workload | string | Resized workload name |
| resizeHistory[].container | string | Resized container name |
| resizeHistory[].resource | string | cpu, memory, cpu+memory, or template |
| resizeHistory[].from | string | Previous value |
| resizeHistory[].to | string | New value |
| resizeHistory[].method | string | InPlace, Eviction, or TemplatePersistence |
| resizeHistory[].result | string | Success, Failed, Reverted, Evicted, or TemplatePatched |
| resizeHistory[].reason | string | Free-form string (not an enum) for why a resize was reverted or failed (e.g. oomkill, restart, notready, slo:<name>, infeasible, eviction_last_replica, eviction_denied, eviction_list_failed, eviction_no_selector, envelope_constraint). Empty for successful resizes. |
| workloadErrors[].workload | string | Workload name that encountered an error during reconciliation |
| workloadErrors[].error | string | Human-readable error description |
| canary.phase | string | CanaryInProgress or FullRollout. FullRollout only when every listed app is promoted |
| canary.startTime | Time | Earliest live per-app watch start (or first canary resize) |
| canary.observedGeneration | int64 | Policy generation when this canary cycle started |
| canary.pods | []string | Union of per-app canary pod names |
| canary.workloads[].workload | string | Deployment or StatefulSet name |
| canary.workloads[].phase | string | CanaryInProgress or FullRollout for that app |
| canary.workloads[].startTime | Time | When that app's canary subset was first resized |
| canary.workloads[].pods | []string | That app's canary pod names |
ResourceRecommendationExplanation contains the intermediate fields emitted by
the estimator chain: rawPercentile, overhead, afterOverhead,
burstFactor, afterBurst, confidence, confidenceFactor, afterConfidence, bounds,
boundsApplied, afterBounds, minChangePercent, maxChangePercent,
changeFilterApplied, afterChangeFilter, final, and optional
finalAdjustment.
Condition types¶
| Type | Reasons | Description |
|---|---|---|
Ready |
Monitoring, InsufficientData, NoWorkloadsFound, MetricsUnavailable (alias PrometheusUnavailable), InvalidConfig, WorkloadDiscoveryFailed, WorkloadCRDMissing, ConflictCheckFailed, Paused, PrometheusSeriesCapped |
Overall health. PrometheusSeriesCapped keeps Ready True: the reconcile succeeded and the query result is partial. See Ready reason: PrometheusSeriesCapped. WorkloadCRDMissing means targetRef.kind is Rollout and the argoproj.io/v1alpha1 Rollout CRD is not installed. |
TemplatePersistence |
TemplateWorkloadRef, WorkloadRefUnread |
TemplateWorkloadRef is False when a Rollout spec.workloadRef was read, so Attune does not patch that template. Ready is unchanged. WorkloadRefUnread is False when that object cannot be read. Template persistence does not replace WorkloadRefUnread in the same reconcile. |
Resizing |
InProgress, Idle, CooldownActive |
Latest reconcile, only in resize modes. InProgress means this cycle resized at least one workload in place, or a conflict retry merged a successful in-place row written during this reconcile. A success from an earlier hour does not count. CooldownActive means every workload that has a recommendation this cycle is still cooling and this cycle resized nothing. Idle means this cycle resized nothing and cooldown is not active. |
Degraded |
HighRevertRate |
High revert rate detected (3+ of last 5 reverted) |
ScheduleBlocked |
OutsideWindow, InsideWindow |
Whether the current time is within the configured resize schedule window. An equal start and end never opens; the message says so. |
ResizeBlocked |
NamespaceFrozen, HPAListUnavailable, VPAListUnavailable, PodsDeferred, PodsInfeasible, PodsDeferredAndInfeasible |
Namespace freeze kill-switch (attune.io/freeze=true skips new resize, persist, and CREATE initial sizing; pending safety revert still runs), HPA or VPA list failure (apply skipped; recommendations still compute), or one or more target pods stuck Deferred (kubelet pending) or Infeasible; message includes sample pod names and next actions |
SafetyObservation |
Observing, Evaluating, RestorePending, Incomplete |
Pods still carrying attune.io resize-tracking annotations. Computed from those keys each reconcile |
Print columns¶
kubectl get attunepolicy
NAME TYPE WORKLOADS RECS RESIZED READY AGE
Pass -o wide to include CPU Saved and Mem Saved columns.
Kubernetes Events¶
The operator emits Kubernetes events on the AttunePolicy object.
View them with kubectl describe attunepolicy <name> or
kubectl get events --field-selector involvedObject.kind=AttunePolicy.
| Event Reason | Type | Description |
|---|---|---|
RecommendationsReady |
Normal | First recommendations became available (transitions from 0 to >0 workloads with data) |
Resized |
Normal | A container was successfully resized in-place |
DecreaseSuppressed |
Normal | A CPU or memory decrease was blocked by allowDecrease=false |
ScheduleSkipped |
Normal | Resize was skipped because the current time is outside the configured schedule window |
ResizeFailed |
Warning | An in-place resize API call failed |
BudgetExhausted |
Warning | The per-reconcile resize budget was exhausted before all workloads could be resized. The same cap can still admit this pod on a later cycle |
IncreaseExceedsBudget |
Warning | One container's increase is larger than maxCpuIncreasePerMinute, maxMemoryIncreasePerMinute, maxTotalCpuIncrease, or maxTotalMemoryIncrease. Waiting does not help; raise the cap or lower the target |
InfeasibleBlocked |
Warning | In-place resize skipped because the kubelet marked the pod Infeasible and resizeMethod is InPlaceOnly |
EvictionBlocked |
Warning | Eviction fallback skipped (last live Running replica, missing selector, or pod list failure). spec.replicas and NotReady pods are not counted |
EvictionDenied |
Warning | Eviction API denied the request (typically a PodDisruptionBudget) |
ResizeSkipped |
Warning | A resize was skipped (for example a pod in a bad state, a rollout, or a pod-level envelope). A QoS skip from Burstable or BestEffort names both classes, such as Burstable to Guaranteed. A Guaranteed skip keeps the RequestsAndLimits hint and does not name the destination class. |
Reverted |
Warning | A resize was reverted due to safety observation failure (OOMKill, CPU throttle, restarts, or SLO guardrail breach) |
Evicted |
Warning | A pod was evicted as a fallback when in-place resize was not possible |
StaleRecommendation |
Warning | Recommendations are stale (no fresh Prometheus data) |
CooldownActive |
Normal | Resize deferred because the cooldown period has not elapsed |
ResizeUnchanged |
Normal | Live resources already match the applied target after change filtering, dest-limit clamp, or usage floor |
ResizeDeferred |
Normal | Previous-release Attune no-op Event (same meaning as ResizeUnchanged). Reserved now for kubelet node-capacity deferral; do not treat this reason as an Attune no-op |
WorkloadIdle |
Normal | Apply skipped for this workload because HPA ScaledToZero is True, or spec.replicas is 0 |
HPAConflict |
Warning | An HPA targets the same workload and may conflict with resizing |
HPATargetClamped |
Normal | An auto-tuned CPU or memory utilization target was clamped by updateStrategy.hpaTargetBounds after the limit cap. The resize still applies. One event per clamped metric. |
HPABaseRepaired |
Warning | A stored pod CPU or memory base left out containers from the pre-resize pod sum and the HPA update changed attune.io/original-cpu-request or attune.io/original-memory-request. With a container list, the missing names were added at their pre-resize requests. With no list, the stored quantity was below the pre-resize pod sum and equaled the history old sum, or that sum with native sidecars removed. The event names the annotation. A failed update does not emit this. A quantity that did not change does not emit this, including growth of a full base whose container list already names those containers. One event per rewritten annotation. |
VPAConflict |
Warning | An applying VPA (updateMode other than Off, or unset) targets the same workload. Recommend-only (Off) is coexistence, not a conflict. |
ConfigClamped |
Warning | A policy field was clamped to its allowed range at runtime |
ExportFailed |
Warning | Failed to export recommendations to ConfigMap |
RestartOnResize |
Normal | Container will restart on resize due to RestartContainer resize policy |
MemoryLimitClamped |
Normal | Memory limit decrease skipped due to K8s v1.33 restriction |
PolicyConflict |
Warning | Multiple policies target the same workload |
RolloutInProgress |
Normal | Resize skip for Auto, OneShot, and Canary during a RollingUpdate replacement. Recommendations are still computed. Recreate and OnDelete are not a skip: those pods are resized. A RollingUpdate Deployment is not skipped because availableReplicas is behind, or because a scale-out has not finished, when generation is observed and no old pods remain. A ReplicaSet is not skipped because readyReplicas is behind when generation is observed, so unready pods can be resized. A Rollout waits on phase Paused, Progressing, or Degraded, and on status.abort. Degraded still waits when abort is false and replica counts match. A Healthy or empty phase does not wait because updatedReplicas is behind spec.replicas. The message includes the phase, and abort true when status.abort is set. |
DaemonSetRevisionUnavailable |
Warning | Resize skipped for every pod of a RollingUpdate DaemonSet because the current ControllerRevision could not be read: the list failed, the revision has no hash label, or an owned revision does not match the DaemonSet pod selector. OnDelete does not emit this. |
WorkloadOptOut |
Normal | Workload opted out via annotation |
Events use 1-hour deduplication to prevent log spam. Identical events are emitted at most once per hour; condition changes produce new events immediately. Specific events can be suppressed per-policy using the attune.io/suppress-warnings annotation (comma-separated list of event reasons).
AttuneDefaults¶
Scope: Cluster
Short name: ad
apiVersion: attune.io/v1alpha1
kind: AttuneDefaults
metadata:
name: default
spec:
metricsSource: # same structure as AttunePolicy.spec.metricsSource
prometheus:
address: http://prometheus-server.monitoring:80
historyWindow: 168h
minimumDataPoints: 48
queryStep: 5m
rateWindow: 5m
podAggregation: Max
cpu: # same structure as AttunePolicy.spec.cpu
percentile: 95
overhead: "20"
controlledValues: RequestsAndLimits
memory: # same structure as AttunePolicy.spec.memory
percentile: 99
overhead: "30"
controlledValues: RequestsAndLimits
allowDecrease: false
updateStrategy: # same structure as AttunePolicy.spec.updateStrategy
type: Recommend
cooldown: 1h
autoRevert: true
costPricing: # optional, for EstimatedMonthlySavings computation
cpuPerCoreHour: "0.031" # USD per vCPU-hour (default: $0.031)
memoryPerGiBHour: "0.004" # USD per GiB-hour (default: $0.004)
metricsSource, cpu, memory, and updateStrategy use the same
fields as the AttunePolicy sample. This sample does not repeat them.
Fields that are not copied from that sample:
containerPoliciesis AttunePolicy only. There is no implicit*.targetRef,paused,excludedContainers, andweightare AttunePolicy only.costPricingis only on AttuneDefaults and AttuneNamespaceDefaults.
excludeKnownSidecars is shared. Unset on both the policy and the
defaults object stays true.
hpaTargetBounds does not turn attune.io/auto-tune on. The HPA still
needs that annotation. A successful in-place memory resize retunes
memory utilization. Canary waits until that workload is promoted.
Setting the memory band only clamps that percent.
A limitMultiplier fails reconcile with InvalidConfig when
controlledValues is RequestsOnly after merge. An unset value is
filled with RequestsOnly by built-in defaults. RequestsAndLimits on
AttuneDefaults is inherited by a policy that omits the field, so that
pair does not fail. A policy that sets RequestsOnly does not copy a
defaults multiplier. A policy that omits controlledValues still
copies it, then becomes RequestsOnly, and fails. Set
RequestsAndLimits on that policy, or remove the multiplier from
defaults. A defaults object may still store a multiplier without
RequestsAndLimits. Admission does not see the inherited pair. The
check runs after built-in defaults.
Admission rejects a multiplier set on the policy unless that same
block is already RequestsAndLimits. The message is
cpu.limitMultiplier requires cpu.controlledValues RequestsAndLimits, or remove the multiplier
(and the same sentence for memory). RequestsOnly plus a multiplier
is still rejected by the earlier check. An object already stored
without the mode is not rewritten. It stays InvalidConfig until an
update sets RequestsAndLimits or removes the multiplier.
A policy that already sets metricsSource.cloudwatch does not inherit
cpuUnit from AttuneDefaults. Empty cpuUnit on that policy means
Millicores. The cloudwatch block is inherited only when the policy sets
no provider.
AttuneDefaults fields are merged into every AttunePolicy at reconciliation time. Policy-level values always take precedence.
Cost pricing¶
The costPricing section configures per-unit pricing used to compute
status.savings.estimatedMonthlySavings. If omitted, standard on-demand
Linux pricing is used.
| Field | Default | Description |
|---|---|---|
cpuPerCoreHour |
0.031 |
Cost per vCPU-hour |
memoryPerGiBHour |
0.004 |
Cost per GiB-hour |
Formula: (cpuCoresSaved * cpuPrice + memGiBSaved * memPrice) * 730 hours/month
Reference pricing by provider¶
The defaults use AWS on-demand pricing. Adjust for your environment:
| Provider | Instance type | cpuPerCoreHour |
memoryPerGiBHour |
Notes |
|---|---|---|---|---|
| AWS (default) | m6i on-demand | "0.031" |
"0.004" |
US East, Linux |
| AWS Savings Plans | m6i 1yr | "0.020" |
"0.003" |
~35% discount |
| GCP on-demand | e2-standard | "0.034" |
"0.005" |
US |
| GCP committed | e2-standard 1yr | "0.022" |
"0.003" |
~35% discount |
| Azure PAYG | D4s v5 | "0.036" |
"0.005" |
East US |
| Azure Reserved | D4s v5 1yr | "0.022" |
"0.003" |
~38% discount |
| On-prem | bare metal | "0.010" |
"0.001" |
Amortized hardware |
These are approximate. Use your actual billing data for accurate savings estimates. For reserved instances, use the reserved rate so savings reflect true recoverability.
Webhook validation¶
AttuneDefaults and AttuneNamespaceDefaults both have validating
webhooks that reject invalid costPricing, schedule, Prometheus
address values, and resource maxAllowed above the same ceilings as
AttunePolicy (cpu.maxAllowed greater than 256 cores,
memory.maxAllowed greater than 16Ti). If cpuPerCoreHour or
memoryPerGiBHour is set, the webhook validates that each is a
parseable positive float. Invalid values (e.g., "banana", "-0.5"),
invalid schedule settings, blocked Prometheus addresses, and oversized
maxAllowed are rejected at admission time.
AttuneNamespaceDefaults¶
Scope: Namespaced
Short name: and
apiVersion: attune.io/v1alpha1
kind: AttuneNamespaceDefaults
metadata:
name: production-defaults
namespace: production
spec:
# Same fields as AttuneDefaults.spec, including the exceptions above.
# containerPolicies is not on this object.
metricsSource:
prometheus:
address: http://prometheus-server.monitoring:80
cpu:
percentile: 99
overhead: "30"
memory:
percentile: 99
overhead: "50"
allowDecrease: false
updateStrategy:
type: Canary
cooldown: 2h
autoRevert: true
AttuneNamespaceDefaults provides per-namespace defaults that override cluster-scoped AttuneDefaults. This enables different configurations for different environments (e.g., conservative settings for production, aggressive settings for staging).
Precedence order (per field): policy spec > namespace defaults > cluster defaults > built-in defaults.
When both cluster and namespace defaults exist, fields set on the
namespace object win; fields left unset on the namespace object are
filled from the cluster AttuneDefaults (not from built-ins alone).
This includes costPricing: cpuPerCoreHour and memoryPerGiBHour
merge independently. Example: cluster sets cooldown=10m and
percentile=95; namespace sets only percentile=90 → effective
defaults use percentile=90 and cooldown=10m.
If multiple defaults objects exist at the same scope, selection is
deterministic: the controller uses the object with the lexicographically
smallest metadata.name.