Commit 91ef958
* feat(jobs): deploy_idle_scaler — scale-to-zero idle descheduler (Task #54)
Worker half of scale-to-zero. New periodic job (every 2 min) that patches
idle, healthy, not-pinned deployments to replicas=0 (~$0 compute) — reversible
via the api wake endpoint. Sibling to deployment_expirer (idle ≠ expired).
Flag-gated behind DEPLOY_SCALE_TO_ZERO_ENABLED (default OFF): Work()
short-circuits at DEBUG when off — no k8s patch, no DB write (proven by
TestDeployIdleScaler_FlagOffNoOp: zero SQL issued, zero scale calls). Fail-open
when k8s is unreachable (nil client → WARN per tick, other jobs unaffected).
Idle SIGNAL (stated honestly): deployments.last_activity_at (api migration 068)
— stamped at create, bumped on deploy/redeploy/wake. v1 idle = "no
deploy/redeploy/wake for N min" (default 30, floored at 5), NOT per-HTTP
traffic, because the api/worker are not in the request path and no
nginx-ingress request scrape is wired yet. Follow-up noted in the job header to
lift this to traffic-based idle.
CAS double-guard: candidate SELECT and the scaled_to_zero UPDATE share the
healthy + not-zeroed + not-always-on predicate, so a row that raced into a
woken/pinned/redeployed/expired state between SELECT and UPDATE is skipped
(0 rows), never wrongly slept. NotFound Deployment = skip (torn down), not fail.
Metric (rule 25): instant_deploy_scaled_to_zero_total{outcome} (scaled_down |
woke_up | wake_failed | scale_failed) + instant_deploy_idle_apps gauge, primed
in metrics_test.go. Alert+tile+catalog ship in the infra PR.
Tests: flag-off no-op, nil-k8s no-op, scale-down happy path (+counter+gauge),
CAS-race skip, NotFound skip, scale-error → scale_failed, idle-minutes floor,
provider_id→namespace derivation, real k8sDeployScaleClient vs fake clientset.
make gate GREEN.
Awaiting operator enable of DEPLOY_SCALE_TO_ZERO_ENABLED to verify real
scale-down in prod.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(jobs): idle-scaler error-branch + Kind/cluster-ctor coverage (#54)
Closes the 100%-patch gaps on deploy_idle_scaler.go: Kind(), the no-config
cluster-constructor error path (CI-only, gated like the status client),
list-query error, scan error, foreign provider_id skip, db-flip error,
gauge-sample error. Work() and the SQL helpers now 100%.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(scale-to-zero): cover idle-scaler cluster-ctor + config env-parse + AddWorker wiring
Closes the 6 uncovered changed lines flagged by the patch-coverage gate:
- config: DEPLOY_SCALE_TO_ZERO_IDLE_MINUTES parse branch (valid override +
sub-5/non-numeric floor-to-30) now exercised directly.
- deploy_idle_scaler: introduce newDeployScaleClientset package-var seam so
NewK8sDeployScaleClientFromCluster's success return is testable without a
reachable cluster; add a success test alongside the NoConfig error test.
- workers: extract the idle-scaler k8s-client wiring (the AddWorker else-branch)
into a unit-testable buildIdleScaleK8s helper; cover both the success and the
fail-open (nil) branches via the seam.
Flag remains default-OFF; behaviour unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 695f026 commit 91ef958
7 files changed
Lines changed: 1005 additions & 0 deletions
File tree
- internal
- config
- jobs
- metrics
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
| |||
178 | 179 | | |
179 | 180 | | |
180 | 181 | | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
181 | 191 | | |
182 | 192 | | |
183 | 193 | | |
| |||
294 | 304 | | |
295 | 305 | | |
296 | 306 | | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
297 | 321 | | |
298 | 322 | | |
299 | 323 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
126 | 126 | | |
127 | 127 | | |
128 | 128 | | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
129 | 159 | | |
130 | 160 | | |
131 | 161 | | |
| |||
0 commit comments