Skip to content

fix(actual-budget): park at 0 replicas in prod#1607

Merged
devantler merged 3 commits into
mainfrom
claude/serene-banzai-c9a486
May 28, 2026
Merged

fix(actual-budget): park at 0 replicas in prod#1607
devantler merged 3 commits into
mainfrom
claude/serene-banzai-c9a486

Conversation

@devantler
Copy link
Copy Markdown
Contributor

What

Park the Actual Budget app at 0 replicas in production. It is currently unused.

  • actual_budget_replicas: "1""0" (HelmRelease initial replicaCount)
  • New: actual_budget_replicas_min: "0", actual_budget_replicas_max: "0" (KEDA HTTPScaledObject floor + ceiling)
  • Parameterize min / max in the base HTTPScaledObject with ${actual_budget_replicas_(min|max):=1} so local stays unchanged (no var → defaults to 1/1, current behavior).

Why

Without pinning the KEDA HTTPScaledObject to 0/0, the previous min: 1, max: 1 would scale the deployment back up to 1 the moment Flux applies the new HelmRelease — defeating the purpose. Doing it via variables (rather than a hetzner-overlay patch) keeps the base flexible and makes the "park / unpark" operation a three-line edit in the prod variables ConfigMap.

Why max: 0 is safe here

Verified against the upstream KEDA HTTP add-on v0.14 CRD (chart version pinned in this repo): the replicas.max field has no minimum: validator, so max: 0 is schema-valid. Behaviorally the interceptor will hang / time out any stray hit on budget.platform.devantler.tech — that is the correct "parked" semantic, since the app is intentionally not in service.

Reversal

To bring it back online:

actual_budget_replicas: "1"
actual_budget_replicas_min: "1"
actual_budget_replicas_max: "1"

(SQLite on RWO PVC still keeps max ≤ 1 whenever it is re-enabled — the inline comment in the ConfigMap captures this.)

Validation

  • ksail workload validate ✅ (254 files)
  • ksail --config ksail.prod.yaml workload validate ✅ (254 files)
  • kubectl kustomize for both clusters/local/ and clusters/prod/

Resource-freeing note

Once this lands, the deployment's previously-requested CPU / memory is released to the scheduler automatically — replicas: 0 means zero Pods, which means zero requests at the cluster level. No additional mechanism is needed to "free" them.

Actual Budget is currently unused. Set prod's HelmRelease replicaCount
to 0 and pin the KEDA HTTPScaledObject to min=0/max=0 so it can't scale
back up under stray traffic.

To make this reversible without touching the base, parameterize
http-scaled-object.yaml's min/max with `${actual_budget_replicas_(min|max):=1}`.
Defaults preserve current local behavior (no variable set → min=1, max=1);
the prod variables ConfigMap pins both to 0 and adjusts the inline comment.

KEDA HTTP add-on v0.14 CRD accepts min=max=0 (verified against upstream
schema) — the interceptor hangs/times out stray hits on
budget.platform.devantler.tech, which is the right semantic for a parked
app. Reverting is a three-line edit in the prod variables ConfigMap.

`ksail workload validate` and `ksail --config ksail.prod.yaml workload
validate` both pass (254 files each).
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Parks the Actual Budget application in the production cluster by scaling both the HelmRelease and its KEDA HTTPScaledObject to zero replicas, while keeping default (non-prod) behavior unchanged via Flux variable substitution defaults.

Changes:

  • Set actual_budget_replicas to 0 in prod variables and introduce actual_budget_replicas_min/max pinned to 0.
  • Parameterize HTTPScaledObject.spec.replicas.{min,max} with ${actual_budget_replicas_(min|max):=1} so environments without overrides retain the existing 1/1 behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
k8s/clusters/prod/variables/variables-cluster-config-map.yaml Pins Actual Budget HelmRelease + KEDA scaling variables to 0 in prod to keep the app parked.
k8s/bases/apps/actual-budget/http-scaled-object.yaml Switches KEDA HTTPScaledObject min/max replicas to Flux-substituted variables with safe defaults.

Comment thread k8s/clusters/prod/variables/variables-cluster-config-map.yaml Outdated
Address Copilot review on PR #1607: list all three variable names
explicitly so someone un-parking the app later cannot misread
'actual_budget_replicas + _min' as a single variable.
@devantler devantler marked this pull request as ready for review May 28, 2026 07:37
Copilot AI review requested due to automatic review settings May 28, 2026 07:37
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

@devantler devantler enabled auto-merge May 28, 2026 07:40
@devantler devantler added this pull request to the merge queue May 28, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 28, 2026
@devantler devantler added this pull request to the merge queue May 28, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 28, 2026
@devantler devantler added this pull request to the merge queue May 28, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 28, 2026
@devantler devantler added this pull request to the merge queue May 28, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 28, 2026
The KEDA HTTP add-on translates HTTPScaledObject into a downstream
ScaledObject. With max=0 the translation produces
MinReplicaCount=1, MaxReplicaCount=0, which KEDA rejects:

  ScaledObjectCheckFailed: ScaledObject doesn't have correct
  Idle/Min/Max Replica Counts specification: MinReplicaCount=1 must
  be less than MaxReplicaCount=0

The upstream CRD schema accepts max=0 (no minimum validator) but the
controller rejects it after translation. Fix by setting max to 1,
matching the headlamp/whoami scale-to-zero pattern: the deployment
stays at 0 with no traffic (HelmRelease replicaCount=0, KEDA min=0)
and only briefly spins up 1 pod if something hits the URL. SQLite
on RWO PVC requires max <= 1 anyway.
@botantler botantler Bot enabled auto-merge May 28, 2026 17:56
@botantler botantler Bot added this pull request to the merge queue May 28, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 28, 2026
@devantler devantler added this pull request to the merge queue May 28, 2026
Merged via the queue into main with commit 6910656 May 28, 2026
9 checks passed
@devantler devantler deleted the claude/serene-banzai-c9a486 branch May 28, 2026 21:33
@github-project-automation github-project-automation Bot moved this from 🫴 Ready to ✅ Done in 🌊 Project Board May 28, 2026
@botantler
Copy link
Copy Markdown
Contributor

botantler Bot commented May 28, 2026

🎉 This PR is included in version 1.12.1 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

2 participants