18

Separate Infrastructure State from Workload State

Diagnose and reconcile four desired-state owners instead of trusting one magical “declarative” status.

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: infra/stacks/prod/main.tf

provider "aws" {
  region              = var.region
  allowed_account_ids = [var.account_id]
  assume_role {
    role_arn = var.deployer_role_arn
  }

Code to reality

Declared intent
Bind production infrastructure changes to one expected AWS account and an explicit deployer role.
Interpreter
Terraform configures the AWS provider, assumes the role, and rejects credentials for any other account.
Software effect
The plan is scoped to the production state and account boundary before resource reconciliation begins.
Hardware effect
Only an authorized apply can allocate billable AWS networking, control-plane, compute, and data resources.
Observable evidence
Caller identity, reviewed plan, remote state lock, and AWS resource tags agree on the production account.

Start with the people and the result they need

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

  • D18-UC-01
    • Person: Platform engineer
    • Job: Assign one mutating owner to every infrastructure and workload resource
    • Observable result: Network, cluster, bootstrap, workload, and generated-app records have non-overlapping writers
  • D18-UC-02
    • Person: Incident commander
    • Job: Find the failing boundary when a generated application is unavailable
    • Observable result: Diagnosis proceeds from AWS substrate to intended API, IaC state, Kubernetes, GitOps, and customer 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:

  • D18-US-01
    • Story: As a platform engineer, I want each resource assigned to one desired-state writer, so that control loops cannot continuously overwrite one another
    • Observable acceptance: Inventory names resource, environment, writer, state store, identity, deletion owner, and rejected conflict
  • D18-US-02
    • Story: As an incident commander, I want boundary-specific observations in dependency order, so that I repair the real owner instead of trusting an…
    • Observable acceptance: Case contains freshness, expected and observed status, failing owner, bounded action, recovery, unaffected control, environment, time, run, and traces

Add real state and observable proof

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

  • D18-FLOW-01
    • Trigger: Engineer proposes a new production workload
    • Responsible systems: IaC policy checker, Terraform or equivalent engine, cluster lifecycle, GitOps renderer/controller, generated-app owners
    • Authoritative state: Git plus IaC backend for infrastructure intent/mapping; Git plus Kubernetes API for workloads
    • Owned record: OwnershipAssignment
    • Observable evidence: Actor, resource inventory, writer, state store, environment, expected and observed policy, source revisions, timestamp, run, and evidence digest
    • Failure signal: Duplicate writer, missing deletion owner, shared production identity, or workload embedded in foundation state
  • D18-FLOW-02
    • Trigger: Dev customer probe fails while GitOps last reported healthy
    • Responsible systems: AWS inventory, cluster API probe, IaC state reader, Kubernetes observers, GitOps controller, customer probe runner
    • Authoritative state: Provider APIs for resource reality; each controller’s store for its claim; Evidence Store for correlation
    • Owned record: InfrastructureIntent
    • Observable evidence: Boundary observations, stale or failed signal, owner-directed action, recovered response, positive control, environment, timestamps, run, cluster,…
    • Failure signal: Missing substrate, wrong API endpoint, stale state, unready workload, stale Git comparison, or failed response

The enterprise problem and today’s slice

Enterprise problem: Zheta Forge can report a synced workload while its cluster API or AWS substrate is unavailable, and two tools that both mutate one resource can fight forever or destroy the wrong layer. Whole-course context: The incoming evidence is a deterministic Kubernetes render with one Git owner; today places that workload inside the larger infrastructure chain. Today’s slice: We separate Infrastructure as Code (IaC) configuration and state, managed-cluster lifecycle, Kubernetes reconciliation, and GitOps workload ownership from local through production. End-of-day evidence: An ownership matrix and bounded dev failure show substrate existence, intended API reachability, IaC state agreement, Kubernetes convergence, Git revision, customer response, unaffected control, environment, time, run, and trace IDs independently. Still unsolved: Terraform’s concrete plan and state mechanics and AWS account implementation remain deferred.

Customer use cases

“Desired state” is incomplete unless the team names which controller owns which resource and what its green signal proves. These use cases cover safe change and bottom-up diagnosis.

Use case IDActorCustomer jobSuccess outcomeDenial or recovery evidence
D18-UC-01Platform engineerAssign one mutating owner to every infrastructure and workload resourceNetwork, cluster, bootstrap, workload, and generated-app records have non-overlapping writersOwnership policy rejects a Deployment declared by both Terraform and GitOps or a cluster declared by GitOps
D18-UC-02Incident commanderFind the failing boundary when a generated application is unavailableDiagnosis proceeds from AWS substrate to intended API, IaC state, Kubernetes, GitOps, and customer evidenceGreen upper-layer status is not accepted when lower boundary or customer probe fails; unaffected control is recorded

Actor-centred user stories

Declarative tools complement one another only when their responsibility boundaries are explicit. These stories make ownership conflict and scoped recovery observable.

Story IDUse case IDsUser storyObservable acceptance conditions
D18-US-01D18-UC-01As a platform engineer, I want each resource assigned to one desired-state writer, so that control loops cannot continuously overwrite one anotherInventory names resource, environment, writer, state store, identity, deletion owner, and rejected conflict
D18-US-02D18-UC-02As an incident commander, I want boundary-specific observations in dependency order, so that I repair the real owner instead of trusting an unrelated green badgeCase contains freshness, expected and observed status, failing owner, bounded action, recovery, unaffected control, environment, time, run, and traces

End-to-end product flows

A successful GitOps sync proves only a comparison against the Kubernetes API it reached; it cannot prove that customers, another API endpoint, or physical capacity are healthy. The flows record each boundary before reconciliation.

Flow IDUse case IDsPathTriggerNumbered stepsTerminal evidence
D18-FLOW-01D18-UC-01HappyEngineer proposes a new production workload1. Classify every resource by owner. 2. Validate no address has two writers. 3. Plan infrastructure without application objects. 4. Render workload without cloud foundation objects. 5. Approve identities and deletion owners.Actor, resource inventory, writer, state store, environment, expected and observed policy, source revisions, timestamp, run, and evidence digest
D18-FLOW-02D18-UC-02RecoveryDev customer probe fails while GitOps last reported healthy1. Capture failed response. 2. Verify AWS resource existence and runtime. 3. Probe the intended Kubernetes API. 4. Compare IaC state with real resource. 5. Inspect Kubernetes controllers. 6. Refresh GitOps comparison. 7. Repair through failing owner and reprobe affected and unaffected apps.Boundary observations, stale or failed signal, owner-directed action, recovered response, positive control, environment, timestamps, run, cluster, revision, and trace IDs

The operator begins with a named environment and finishes at the customer response. The following design explains why several independent observations are needed between those points.

System design derived from the flows

IaC, managed-cluster lifecycle, Kubernetes, and GitOps are not interchangeable automation choices; they own successive layers. A green signal is evidence only for the boundary that emitted it.

Use case IDEntry pointResponsible servicesAuthoritative storeFailure evidence
D18-UC-01Architecture ownership reviewIaC policy checker, Terraform or equivalent engine, cluster lifecycle, GitOps renderer/controller, generated-app ownersGit plus IaC backend for infrastructure intent/mapping; Git plus Kubernetes API for workloadsDuplicate writer, missing deletion owner, shared production identity, or workload embedded in foundation state
D18-UC-02Customer incident and environment inventoryAWS inventory, cluster API probe, IaC state reader, Kubernetes observers, GitOps controller, customer probe runnerProvider APIs for resource reality; each controller’s store for its claim; Evidence Store for correlationMissing substrate, wrong API endpoint, stale state, unready workload, stale Git comparison, or failed response

IaC owns AWS networks, EKS, capacity, identity integration, and bootstrap; Kubernetes owns live workload reconciliation; GitOps owns the rendered declarations it applies. The generated application still owns its tenants and domain data, while runtime identities and provider memberships remain separately authorised.

Data model and ownership

Calling Git the single source of truth hides provider reality and controller observations, so the platform must retain distinct records for intent, mapping, live state, and evidence. None silently overwrites another.

Generated-application database: Not created in this slice — durable infrastructure, ownership, controller, and evidence records are sufficient; existing application databases remain owned by their generated applications.

Record or entityStore and ownerPrimary keyForeign key or opaque referenceTenant keyMaterial constraintLifecycle and deletionUse case IDs
OwnershipAssignmentPlatform architecture repository, owned by Platform Governanceenvironment plus resource_addressOpaque writer identity and state backend refsenvironment_idExactly one mutating owner and one deletion owner per resource addressPropose, approve, supersede with migration proof, retain historyD18-UC-01, D18-UC-02
InfrastructureIntentGit and IaC backend, owned by Infrastructure Platformcommit_sha plus module_pathProvider resource IDs through state mappingAWS account and environment IDReviewed intent, state lock, and execution identity are environment-scopedPlan, apply, observe, reconcile, retire after dependent workloadsD18-UC-01, D18-UC-02
WorkloadIntentApplication Git repository, owned by Delivery Platformcommit_sha plus rendered_object_keyOpaque cluster and release refsenvironment_idGitOps is sole writer for declared workload object; immutable image digest requiredRender, approve, reconcile, supersede, delete after traffic removalD18-UC-01, D18-UC-02
BoundaryObservationEvidence Store, owned by Operationsobservation_idOpaque provider, state, cluster, Git revision, release, and trace refsenvironment_idSource, read identity, freshness, expected, and observed values are mandatoryCapture read-only, seal in incident/run, expire by evidence policyD18-UC-02

The inventory directs repair to one divergent owner, after which evidence must reconnect substrate observations to the customer path. This prevents a workload resync from masquerading as an infrastructure repair.

Compare the same layers across environments

Local Kubernetes preserves control-loop behavior but collapses machine, disk, network, and power failure domains onto one laptop, so it is not production high-availability evidence. Dev, staging, and production use separate AWS accounts and clusters while promoting the same artifact and packaging model.

LayerLocalDev, staging, and production on AWSInvariant question
HardwareOne laptop, even with several Kind nodesProvider capacity across selected Availability ZonesWhere do CPU, memory, disk, network, and power failure domains come from?
Host/runtimeDocker containers acting as nodesEKS worker compute and container runtimeWho patches and replaces hosts?
OrchestratorKind Kubernetes API and controllersManaged EKS control plane and Kubernetes controllersWhich API holds desired workload state?
DeliveryLocal GitOps controllerPer-environment Argo CDWhich Git revision and identity may reconcile?
ApplicationSame immutable Zheta Forge artifactSame digest promoted through accountsWhat customer evidence proves equivalent behavior?

Run a read-only ownership check

Diagnosis must observe before mutation because state surgery can erase the record needed for recovery. These commands compare independent owners in one named environment.

: "${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; }
terraform -chdir=infra/stacks/dev state list
aws --profile "$AWS_PROFILE" --region "$AWS_REGION" eks update-kubeconfig --name zheta-forge-dev --alias zheta-forge-dev
aws --profile "$AWS_PROFILE" --region "$AWS_REGION" eks describe-cluster --name zheta-forge-dev --query 'cluster.status'
kubectl --context zheta-forge-dev get nodes
kubectl --context zheta-forge-dev -n argocd get application forge-dev -o wide
test -n "${FORGE_URL:?set FORGE_URL to a reachable dev control-plane URL}"
FORGE_URL="$FORGE_URL" FORGE_TOKEN="${FORGE_TOKEN:?set the explicit bearer token}" ./scripts/smoke-product.sh

These exact paths and names come from infra/stacks/dev, infra/modules/environment, argocd/applicationsets/forge-dev.yaml, and scripts/smoke-product.sh. Because the EKS API is private-only, run aws --profile "$AWS_PROFILE" --region "$AWS_REGION" eks update-kubeconfig and every kubectl command from within the VPC or a connected network, as required by the Amazon EKS private-endpoint documentation.

Effect fieldWhat happens
Declared intentObserve IaC mapping, AWS resource, Kubernetes runtime, GitOps comparison, and customer response independently
InterpreterTerraform CLI, AWS API, Kubernetes API, Argo CD API, and application server each answer for their boundary
Software effectNo desired state changes; evidence bundle gains fresh observations
Hardware effectAPIs and application consume small CPU, memory, and network capacity to answer
EvidenceState addresses, AWS status, nodes, Git revision/status, response, environment, freshness, and trace

Key takeaways

One delivery system contains several desired-state owners, and none provides universal truth.

  • Diagnose substrate, intended API, IaC mapping, Kubernetes, GitOps, then customer path.
  • Give every resource one writer and one deletion owner.
  • Local multi-node reconciliation is useful but does not prove independent failure domains.

Checklist

The ownership model is safe when every repair has one direction.

  • [ ] Infrastructure and workload resources have non-overlapping writers
  • [ ] Environment inventory names account, cluster, network, identity, state, Git, secrets, backup, and deletion owners
  • [ ] Read-only observations precede any repair or state surgery
  • [ ] Recovery includes affected and unaffected customer evidence