keda · git:20260516.afdf992 · 2026-05-16 · sha256 7dd48246c6a89764

keda git:20260516.afdf992A

Immutable. This exact content is served forever at /api/v1/blob/7dd48246c6a89764.

---
description: KEDA ScaledObject and ScaledJob generation rules — auth patterns, scaling safety, best practices
globs: ["**/scaledobject*.yaml", "**/scaledjob*.yaml", "**/keda*.yaml", "**/triggerauth*.yaml", "**/scaled-object*.yaml", "**/scaled-job*.yaml"]
alwaysApply: false
---

# KEDA Rules

Always generate ScaledObject and ScaledJob resources with all of the following. Missing any item is a Critical finding.

## Required fields on every ScaledObject

```yaml
spec:
  scaleTargetRef:
    apiVersion: apps/v1           # Always explicit — don't rely on the default
    kind: Deployment              # Or StatefulSet — be explicit
    name: <exact-deployment-name>

  minReplicaCount: 1              # Use 0 only when cold-start latency is acceptable and documented
  maxReplicaCount: <N>            # Always set — never omit the ceiling

  pollingInterval: 30             # 30s is a safe default; lower only for latency-sensitive queues
  cooldownPeriod: 300             # 5-minute cooldown prevents thrashing

  advanced:
    restoreToOriginalReplicaCount: true   # Always set — restores replicas on ScaledObject deletion
```

## Required on every trigger

```yaml
triggers:
  - type: <scaler>
    metadata:
      activationThreshold: "<N>"  # or activationQueueLength / activationLagThreshold
      # Prevents activation on noise/sparse events
    authenticationRef:
      name: <trigger-auth-name>   # Always use TriggerAuthentication — never inline credentials
```

## TriggerAuthentication: prefer Pod Identity

```yaml
# ✅ Best practice — no static credentials
spec:
  podIdentity:
    provider: aws                 # aws | azure | gcp | aws-eks

# ⚠️ Only when Pod Identity is unavailable — rotate keys; scope the Secret tightly
spec:
  secretTargetRef:
    - parameter: <param>
      name: <secret-name>
      key: <key>
```

## Scaler-specific minimum IAM permissions (least privilege)

| Scaler | Minimum permission |
|---|---|
| `aws-sqs-queue` | `sqs:GetQueueAttributes`, `sqs:GetQueueUrl` — NOT `sqs:ReceiveMessage` |
| `kafka` | consumer group describe + offset fetch — NOT produce |
| `azure-servicebus` | `Listen` on the specific queue or subscription only |
| `prometheus` | No auth for cluster-internal Prometheus |

## Cron scaler rules

```yaml
# Cron windows must not overlap.
# Always pair with a Prometheus/queue trigger as a safety net for unexpected spikes.
# Use idleReplicaCount or minReplicaCount >= 1 to keep a warm pod during off-hours.
# Always set timezone explicitly — never rely on UTC as an unstated assumption.
- type: cron
  metadata:
    timezone: Europe/Berlin       # IANA timezone — always explicit
    start: "0 8 * * 1-5"
    end: "0 20 * * 1-5"
    desiredReplicas: "10"
```

## ScaledJob required fields

```yaml
spec:
  jobTargetRef:
    template:
      spec:
        restartPolicy: Never      # Required — Jobs must not use OnFailure with KEDA
        activeDeadlineSeconds: 3600  # Required — prevents zombie jobs
        containers:
          - resources:
              requests: { cpu: "500m", memory: "512Mi" }
              limits: { memory: "2Gi" }   # memory limit required; omit cpu limit
```

## Never generate

- Static credentials inlined directly in `ScaledObject` metadata — always use `TriggerAuthentication`
- `SQS` scaler with `sqs:ReceiveMessage` — KEDA only reads depth, it does not consume
- A separate `HorizontalPodAutoscaler` targeting the same Deployment — KEDA manages its own HPA
- `minReplicaCount: 0` without a comment explaining cold-start acceptance
- Overlapping cron windows — they produce undefined behavior
- `pollingInterval < 10` on SQS — each poll is a billable AWS API call
- Kafka scaler without `lagThreshold` set — defaults are rarely appropriate

## HPA conflict check

Before generating a ScaledObject, note if an HPA already targets the same Deployment. If it does, include:

```bash
# Delete existing HPA before applying ScaledObject
kubectl delete hpa <existing-hpa> -n <namespace>
```