28

Design Event Scaling, Prove the Current Launch Veto

Distinguish a future scaling design from capabilities that the repository actually implements today.

Run it in the public monorepo

This course is built around the public Zheta Kubernetes Lab monorepo. The excerpt below is runnable source, not pseudocode.

Source: gitops/apps/forge/base/workloads.yaml

serviceAccountName: control-plane
      securityContext: { runAsNonRoot: true, fsGroup: 65532, fsGroupChangePolicy: OnRootMismatch, seccompProfile: { type: RuntimeDefault } }
      containers:
        - name: control-plane
          image: zheta-forge/control-plane:v1
          envFrom: [{ configMapRef: { name: forge-environment } }, { secretRef: { name: control-plane-secrets } }]
          ports: [{ name: http, containerPort: 8080 }]
          readinessProbe: { httpGet: { path: /healthz, port: http }, periodSeconds: 5 }
          livenessProbe: { httpGet: { path: /healthz, port: http }, periodSeconds: 10 }
          resources: { requests: { cpu: 50m, memory: 64Mi }, limits: { cpu: 500m, memory: 256Mi } }

Code to reality

Declared intent
Give one microservice a distinct workload identity, hardened runtime, health contract, and capacity bounds.
Interpreter
Kubernetes admission, scheduler, kubelet, and workload-identity integration interpret different fields.
Software effect
The Pod runs as the control-plane ServiceAccount and publishes readiness only after its health endpoint responds.
Hardware effect
The scheduler reserves requested CPU and memory; the runtime enforces the limit and Linux security context.
Observable evidence
Pod identity, security context, readiness, resource metrics, and a denied excess-authority probe prove the contract.

Start with the people and the result they need

The source tables below remain the detailed contract. Begin with these customer paths:

  • D28-UC-01
    • Person: Publishing operator
    • Job: Determine whether Forge can safely accept an asynchronous generation burst
    • Observable result: The review records that current generation is synchronous and blocks the burst claim
  • D28-UC-02
    • Person: Cloud governance engineer
    • Job: Define the bounded future pod-and-node scaling contract
    • Observable result: The design names metrics, limits, identities, zones, disruption, cost, and terminal product evidence

Turn each customer job into a testable story

Now turn each customer job into a story with a result that an engineer can check:

  • D28-US-01
    • Story: As a publishing operator, I want queue age to drive bounded worker capacity, so that jobs complete without a permanent oversized fleet
    • Observable acceptance: Evidence links queue, KEDA/HPA, pod, NodeClaim, instance, Node, job, latency, scale-down, environment, and run
  • D28-US-02
    • Story: As a cloud governance engineer, I want incompatible capacity requests denied, so that workload labels cannot purchase arbitrary infrastructure
    • Observable acceptance: Denial names pod constraints, NodePool revision, zero matching claims/instances, approved control, cost window, time, and audit ID

Add real state and observable proof

Finally trace each story through the system that owns its state and the evidence that proves the outcome:

  • D28-FLOW-01
    • Trigger: Reviewer invokes current generation
    • Responsible systems: KEDA, external metrics, HPA, scheduler, Karpenter, EC2 Fleet, kubelet, Forge worker
    • Authoritative state: SQS for jobs; Kubernetes API for scaling/scheduling; AWS API for instances; Evidence Store for outcome
    • Owned record: GenerationJob
    • Observable evidence: Request, artifact, audit event, service endpoints, immutable revision, environment, time, and trace ID
    • Failure signal: Stale metric, capped HPA, Pending pod, failed NodeClaim, EC2 capacity error, unregistered node, OOM, or missed queue age
  • D28-FLOW-02
    • Trigger: Release claims elastic generation
    • Responsible systems: Argo CD, admission, KEDA, Karpenter, IAM, budgets/audit
    • Authoritative state: Git and Kubernetes policy state plus AWS infrastructure/audit state
    • Owned record: ScalingPolicyRevision
    • Observable evidence: Missing-source list, rejected decision, reviewer, revision, environment, timestamp, and successful synchronous trace
    • Failure signal: Bound exceeded, forbidden requirement accepted, disruption violation, missing cost tag, or positive control failure

The enterprise problem and today’s slice

Enterprise problem: A plausible autoscaling diagram can be mistaken for a deployed product capability. That creates both launch risk and surprise AWS spend. Whole-course context: Day 22 proved that generation is currently a synchronous control-plane-to-generator HTTP call; the outbox/broker path carries audit evidence, not generation jobs. Today’s slice: KEDA, Karpenter, a generation-job queue, a worker contract, and their AWS prerequisites are absent from the current repository. We design the future two-loop boundary, then turn that absence into a fail-closed launch veto. End-of-day evidence: Source checks show the missing controllers and async job path, while the synchronous product test remains the positive control. Still unsolved: No queue-driven pod or node scaling may be claimed until implementation, deployment, and an observed burst dossier exist.

Customer use cases

Pod scaling and node provisioning solve different layers and can fail independently. Customer evidence must end with completed generated work, not a replica or instance count.

Use case IDActorCustomer jobSuccess outcomeDenial or recovery evidence
D28-UC-01Publishing operatorDetermine whether Forge can safely accept an asynchronous generation burstThe review records that current generation is synchronous and blocks the burst claimAny claimed queue, ScaledObject, NodePool, or completed async job without source and runtime evidence fails review
D28-UC-02Cloud governance engineerDefine the bounded future pod-and-node scaling contractThe design names metrics, limits, identities, zones, disruption, cost, and terminal product evidenceThe launch gate remains closed while controller manifests, AWS prerequisites, or negative capacity tests are absent

Actor-centred user stories

A rising replica count can hide Pending pods, and a launched instance can fail to register. These stories connect every control loop to customer work and cost.

Story IDUse case IDsUser storyObservable acceptance conditions
D28-US-01D28-UC-01As a publishing operator, I want queue age to drive bounded worker capacity, so that jobs complete without a permanent oversized fleetEvidence links queue, KEDA/HPA, pod, NodeClaim, instance, Node, job, latency, scale-down, environment, and run
D28-US-02D28-UC-02As a cloud governance engineer, I want incompatible capacity requests denied, so that workload labels cannot purchase arbitrary infrastructureDenial names pod constraints, NodePool revision, zero matching claims/instances, approved control, cost window, time, and audit ID

End-to-end product flows

The happy path follows demand down to physical CPU and memory and back to a completed job. The denial proves policy without broad destructive capacity changes.

Flow IDUse case IDsPathTriggerNumbered stepsTerminal evidence
D28-FLOW-01D28-UC-01Positive controlReviewer invokes current generation1. Trace Forge.generate(). 2. Observe synchronous Generator HTTP. 3. Verify artifact persistence. 4. Verify the separate outbox-to-evidence event. 5. Record latency and revision.Request, artifact, audit event, service endpoints, immutable revision, environment, time, and trace ID
D28-FLOW-02D28-UC-02DeniedRelease claims elastic generation1. Search for the job producer and consumer. 2. Search for KEDA and Karpenter desired state. 3. Check AWS controller prerequisites. 4. Record every absence. 5. Reject the launch claim. 6. Preserve synchronous generation as the positive control.Missing-source list, rejected decision, reviewer, revision, environment, timestamp, and successful synchronous trace

Queue and completed artifact are the smallest customer-facing scaling model.

System design derived from the flows

KEDA changes workload demand; Karpenter changes machine supply. Kubernetes scheduling connects them by exposing unsatisfied pod constraints.

Use case IDEntry pointResponsible servicesAuthoritative storeFailure evidence
D28-UC-01SQS queue and worker DeploymentKEDA, external metrics, HPA, scheduler, Karpenter, EC2 Fleet, kubelet, Forge workerSQS for jobs; Kubernetes API for scaling/scheduling; AWS API for instances; Evidence Store for outcomeStale metric, capped HPA, Pending pod, failed NodeClaim, EC2 capacity error, unregistered node, OOM, or missed queue age
D28-UC-02Git-reviewed ScaledObject, NodePool, EC2NodeClassArgo CD, admission, KEDA, Karpenter, IAM, budgets/auditGit and Kubernetes policy state plus AWS infrastructure/audit stateBound exceeded, forbidden requirement accepted, disruption violation, missing cost tag, or positive control failure

The expanded path distinguishes queue interpretation, pod demand, placement, and physical machine supply.

The target design would assign business jobs to a Generator worker, replica demand to KEDA, placement to the scheduler, and machine supply to Karpenter. That is a design target, not current behavior. Today SRP and IoC/DI apply to the synchronous Generator HTTP port; PubSub carries domain evidence through the transactional outbox. Adding asynchronous generation requires an idempotent job model, producer, consumer, retry/dead-letter contract, and end-to-end tests before either autoscaler is useful.

Data model and ownership

Deleting a Kubernetes Node object does not prove its EC2 instance stopped billing, so lifecycle evidence crosses systems without merging authority. Generated artifacts remain application-owned outputs.

Generated-application database: Not changed in this slice. There is no current GenerationJob aggregate. Artifacts remain in the existing provider-owned artifact store; a future job record needs an explicit owner and tenant key before async work is implemented.

Record or entityStore and ownerPrimary keyForeign key or opaque referenceTenant keyMaterial constraintLifecycle and deletionUse case IDs
GenerationJobGenerated-app/control domain store, owned by Generator servicejob_idOpaque queue message and artifact refsapp_tenant_idIdempotency key unique; tenant scope and terminal state enforcedEnqueue, claim, complete/fail, retain, export/delete with appD28-UC-01, D28-UC-02
ScalingPolicyRevisionGit/Kubernetes API, owned by Compute Platformpolicy UID plus generationLocal workload and NodeClass refsenvironment_idMin/max, aggregate resources, zones, identities, disruption, and tags boundedReview, reconcile, supersede, remove after worker retirementD28-UC-01, D28-UC-02
CapacityEvidenceEvidence Store, owned by FinOps/SRErun_id plus timestampOpaque job, pod, NodeClaim, instance, Node, and cost refsenvironment_idPositive and denied paths share policy and time windowAppend, seal, retain by operations policy, expireD28-UC-01, D28-UC-02

Job, policy, and capacity proof now have explicit owners and retention rules across the full scaling chain.

Prove absence before designing the two loops

Source absence is the decisive current evidence. The future design can use KEDA’s event metrics to feed HPA demand (KEDA scaling deployments) and Karpenter NodePools to constrain machine supply (Karpenter NodePools), but official documentation does not make those controllers exist in this repository.

: "${AWS_PROFILE:?set the approved AWS profile}" "${AWS_REGION:?set the intended AWS region}" "${EXPECTED_ACCOUNT_ID:?set the intended 12-digit AWS account}"
actual_account="$(aws --profile "$AWS_PROFILE" --region "$AWS_REGION" sts get-caller-identity --query Account --output text)"
[ "$actual_account" = "$EXPECTED_ACCOUNT_ID" ] || { echo "AWS account mismatch" >&2; exit 1; }
test ! -e argocd/addons/keda.yaml
test ! -e argocd/addons/karpenter-prod.yaml
! rg -n 'kind: (ScaledObject|NodePool|EC2NodeClass)' argocd gitops
! rg -n 'GenerationJob|enqueue_generation|generation_queue' services
rg -n 'self\.generator\.generate|artifact\.generated' services/control_plane/domain.py
echo "LAUNCH VETO: asynchronous generation and elastic node supply are not implemented"
Effect fieldWhat happens
Declared intentReject elastic-generation claims until both control loops and the async product boundary exist
InterpreterShell and ripgrep inspect the immutable source revision; a human reviewer owns the launch decision
Software effectNo desired state changes; the release remains blocked and synchronous generation remains truthful
Hardware effectNo KEDA- or Karpenter-driven Pods or EC2 machines are allocated by this proof
EvidenceMissing paths and kinds, present synchronous call, rejected decision, revision, actor, and timestamp

Three authorization planes and lifecycle rule

Submitting an application job does not grant permission to change NodePools, and a Karpenter role does not read tenant data. Provider policy owns plans, runtime policy owns capacity, and the generated application owns jobs and artifacts.

Decision rule: scale pods for work and nodes for unschedulable constraints; retire jobs and workers before scaler policy, drain nodes before instance termination, and verify AWS billing state after Kubernetes cleanup.

Key takeaways

Elasticity will be two control loops joined by scheduling evidence; today it is a documented launch gap.

  • Do not claim KEDA, Karpenter, or queued generation from a design diagram.
  • Bound replicas, machine types, zones, aggregate resources, disruption, and cost.
  • Finish at job latency and customer artifact, not controller status.

Checklist

Scaling is production-ready only when it is effective, bounded, and reversible.

  • [ ] Repository absence checks are captured at an immutable revision
  • [ ] Synchronous generation and separate audit delivery are traced truthfully
  • [ ] Launch decision explicitly vetoes elastic-generation claims
  • [ ] Future acceptance requires burst, denial, scale-down, and cost evidence