Skip to content

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
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:

  • containerPolicies is AttunePolicy only. There is no implicit *.
  • targetRef, paused, excludedContainers, and weight are AttunePolicy only.
  • costPricing is 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.