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 ID | Actor | Customer job | Success outcome | Denial or recovery evidence |
|---|---|---|---|---|
| D18-UC-01 | Platform engineer | Assign one mutating owner to every infrastructure and workload resource | Network, cluster, bootstrap, workload, and generated-app records have non-overlapping writers | Ownership policy rejects a Deployment declared by both Terraform and GitOps or a cluster declared by GitOps |
| D18-UC-02 | Incident commander | Find the failing boundary when a generated application is unavailable | Diagnosis proceeds from AWS substrate to intended API, IaC state, Kubernetes, GitOps, and customer evidence | Green 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 ID | Use case IDs | User story | Observable acceptance conditions |
|---|---|---|---|
| D18-US-01 | D18-UC-01 | As a platform engineer, I want each resource assigned to one desired-state writer, so that control loops cannot continuously overwrite one another | Inventory names resource, environment, writer, state store, identity, deletion owner, and rejected conflict |
| D18-US-02 | D18-UC-02 | As an incident commander, I want boundary-specific observations in dependency order, so that I repair the real owner instead of trusting an unrelated green badge | Case 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 ID | Use case IDs | Path | Trigger | Numbered steps | Terminal evidence |
|---|---|---|---|---|---|
| D18-FLOW-01 | D18-UC-01 | Happy | Engineer proposes a new production workload | 1. 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-02 | D18-UC-02 | Recovery | Dev customer probe fails while GitOps last reported healthy | 1. 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 ID | Entry point | Responsible services | Authoritative store | Failure evidence |
|---|---|---|---|---|
| D18-UC-01 | Architecture ownership review | IaC policy checker, Terraform or equivalent engine, cluster lifecycle, GitOps renderer/controller, generated-app owners | Git plus IaC backend for infrastructure intent/mapping; Git plus Kubernetes API for workloads | Duplicate writer, missing deletion owner, shared production identity, or workload embedded in foundation state |
| D18-UC-02 | Customer incident and environment inventory | AWS inventory, cluster API probe, IaC state reader, Kubernetes observers, GitOps controller, customer probe runner | Provider APIs for resource reality; each controller’s store for its claim; Evidence Store for correlation | Missing 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 entity | Store and owner | Primary key | Foreign key or opaque reference | Tenant key | Material constraint | Lifecycle and deletion | Use case IDs |
|---|---|---|---|---|---|---|---|
| OwnershipAssignment | Platform architecture repository, owned by Platform Governance | environment plus resource_address | Opaque writer identity and state backend refs | environment_id | Exactly one mutating owner and one deletion owner per resource address | Propose, approve, supersede with migration proof, retain history | D18-UC-01, D18-UC-02 |
| InfrastructureIntent | Git and IaC backend, owned by Infrastructure Platform | commit_sha plus module_path | Provider resource IDs through state mapping | AWS account and environment ID | Reviewed intent, state lock, and execution identity are environment-scoped | Plan, apply, observe, reconcile, retire after dependent workloads | D18-UC-01, D18-UC-02 |
| WorkloadIntent | Application Git repository, owned by Delivery Platform | commit_sha plus rendered_object_key | Opaque cluster and release refs | environment_id | GitOps is sole writer for declared workload object; immutable image digest required | Render, approve, reconcile, supersede, delete after traffic removal | D18-UC-01, D18-UC-02 |
| BoundaryObservation | Evidence Store, owned by Operations | observation_id | Opaque provider, state, cluster, Git revision, release, and trace refs | environment_id | Source, read identity, freshness, expected, and observed values are mandatory | Capture read-only, seal in incident/run, expire by evidence policy | D18-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.
| Layer | Local | Dev, staging, and production on AWS | Invariant question |
|---|---|---|---|
| Hardware | One laptop, even with several Kind nodes | Provider capacity across selected Availability Zones | Where do CPU, memory, disk, network, and power failure domains come from? |
| Host/runtime | Docker containers acting as nodes | EKS worker compute and container runtime | Who patches and replaces hosts? |
| Orchestrator | Kind Kubernetes API and controllers | Managed EKS control plane and Kubernetes controllers | Which API holds desired workload state? |
| Delivery | Local GitOps controller | Per-environment Argo CD | Which Git revision and identity may reconcile? |
| Application | Same immutable Zheta Forge artifact | Same digest promoted through accounts | What 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 field | What happens |
|---|---|
| Declared intent | Observe IaC mapping, AWS resource, Kubernetes runtime, GitOps comparison, and customer response independently |
| Interpreter | Terraform CLI, AWS API, Kubernetes API, Argo CD API, and application server each answer for their boundary |
| Software effect | No desired state changes; evidence bundle gains fresh observations |
| Hardware effect | APIs and application consume small CPU, memory, and network capacity to answer |
| Evidence | State 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