Development EKS as the Fast Feedback Environment
Create the first managed Kubernetes cluster without confusing an Active control plane with a usable product.
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/modules/environment/main.tf
resource "aws_subnet" "private" {
count = 2
vpc_id = aws_vpc.this.id
availability_zone = local.azs[count.index]
cidr_block = cidrsubnet(var.vpc_cidr, 4, count.index + 8)
tags = merge(local.tags, {
"kubernetes.io/role/internal-elb" = "1"
})
}
Code to reality
- Declared intent
- Place environment workloads across two private availability-zone subnets.
- Interpreter
- Terraform and the AWS provider create subnets and label them for internal Kubernetes load balancers.
- Software effect
- EKS and its controllers discover private network placement from stable subnet IDs and tags.
- Hardware effect
- AWS allocates isolated address ranges and routes network interfaces across two physical zones.
- Observable evidence
- Terraform outputs, AWS subnet inventory, EKS node addresses, and a private connectivity probe agree.
Start with the people and the result they need
The source tables below remain the detailed contract. Begin with these customer paths:
- D22-UC-01
- Person: Product developer
- Job: Deploy an immutable Forge revision to isolated dev and exercise a generated support application
- Observable result: Dev nodes are Ready, Forge services use the requested digest, and create-to-preview returns traceable evidence
- D22-UC-02
- Person: Platform administrator
- Job: Grant least-privilege developer access without granting AWS account or generated-app authority
- Observable result: Developer can inspect and reconcile the dev namespace only
Turn each customer job into a testable story
Now turn each customer job into a story with a result that an engineer can check:
- D22-US-01
- Story: As a product developer, I want a reproducible dev deployment, so that every code change produces customer-visible evidence before promotion
- Observable acceptance: Evidence links source revision, image digest, cluster/node/pod UIDs, lifecycle response, environment, time, run, and trace
- D22-US-02
- Story: As a platform administrator, I want dev access mapped to a narrow Kubernetes role, so that provider membership does not imply production or…
- Observable acceptance: Accepted dev read and denied privileged writes identify actor, resource, scope, precondition, expected and observed results, and audit IDs
Add real state and observable proof
Finally trace each story through the system that owns its state and the evidence that proves the outcome:
- D22-FLOW-01
- Trigger: Developer requests deployment of a reviewed digest
- Responsible systems: Terraform, AWS EKS, managed node group, kubelet, Kubernetes controllers, Forge services
- Authoritative state: Terraform backend and AWS APIs for infrastructure; Kubernetes API for workloads; Forge stores for product state
- Owned record: DevClusterRevision
- Observable evidence: Account, cluster ARN, node/pod UIDs, Git SHA, digest, response, environment, timestamp, run, and trace IDs
- Failure signal: Active cluster with zero Ready nodes, Pending pod, digest mismatch, absent endpoint, or failed lifecycle response
- D22-FLOW-02
- Trigger: Developer attempts a production-context or cluster-admin mutation
- Responsible systems: AWS IAM, EKS access entry, Kubernetes RBAC and audit, Forge authorization
- Authoritative state: IAM and EKS access configuration plus Kubernetes RBAC; generated app database for app roles
- Owned record: DevAccessMapping
- Observable evidence: Denied verb/resource/context, allowed dev read, IAM session, Kubernetes audit ID, environment, time, and test ID
- Failure signal: Unexpected yes from kubectl auth can-i, wrong context, missing audit, or cross-plane role assumption
The enterprise problem and today’s slice
Enterprise problem: Developers lose feedback or bypass controls when a shared cluster is slow, over-privileged, or reachable through undocumented identities. Whole-course context: The incoming dev VPC plan is reviewable but proves no deployed network; today defines the evidence required from the first Amazon Elastic Kubernetes Service (EKS) control plane and worker capacity. Today’s slice: The real stack names zheta-forge-dev, uses a private-only API, a managed system node group, an EKS access entry, control-plane logs, Secrets encryption, and Pod Identity; the cloud overlay remains fail-closed at zero replicas until real ECR digests and Secrets exist. End-of-day evidence: Only an owner-approved apply from connected-network access may produce cluster ARN, nodes, digest, response, denial, account, time, run, and trace IDs. Still unsolved: No claim of deployed dev or customer response is made without that evidence.
Customer use cases
An Active EKS API is not a customer-visible application, so acceptance must continue through nodes, pods, and a Forge lifecycle probe. The dev environment optimizes fast bounded feedback, not production availability claims.
| Use case ID | Actor | Customer job | Success outcome | Denial or recovery evidence |
|---|---|---|---|---|
| D22-UC-01 | Product developer | Deploy an immutable Forge revision to isolated dev and exercise a generated support application | Dev nodes are Ready, Forge services use the requested digest, and create-to-preview returns traceable evidence | A missing worker or failed rollout remains visible instead of being hidden by EKS control-plane health |
| D22-UC-02 | Platform administrator | Grant least-privilege developer access without granting AWS account or generated-app authority | Developer can inspect and reconcile the dev namespace only | Production context, cluster-admin mutation, and unrelated generated-app tenant access are denied; dev read remains a positive control |
Actor-centred user stories
AWS IAM and Kubernetes role-based access control (RBAC) are separate authorization systems, so both must be tested. These stories join them only through explicit, revocable mappings.
| Story ID | Use case IDs | User story | Observable acceptance conditions |
|---|---|---|---|
| D22-US-01 | D22-UC-01 | As a product developer, I want a reproducible dev deployment, so that every code change produces customer-visible evidence before promotion | Evidence links source revision, image digest, cluster/node/pod UIDs, lifecycle response, environment, time, run, and trace |
| D22-US-02 | D22-UC-02 | As a platform administrator, I want dev access mapped to a narrow Kubernetes role, so that provider membership does not imply production or generated-app permission | Accepted dev read and denied privileged writes identify actor, resource, scope, precondition, expected and observed results, and audit IDs |
End-to-end product flows
The release path crosses infrastructure, cluster, workload, and product controllers, so each owner must produce its own observation. The negative path proves the environment boundary.
| Flow ID | Use case IDs | Path | Trigger | Numbered steps | Terminal evidence |
|---|---|---|---|---|---|
| D22-FLOW-01 | D22-UC-01 | Happy | Developer requests deployment of a reviewed digest | 1. Verify dev AWS identity and network outputs. 2. Apply the dev EKS stack. 3. Verify the intended API endpoint and nodes. 4. Render gitops/apps/forge/overlays/dev. 5. Reconcile workloads. 6. Run create-to-preview smoke. | Account, cluster ARN, node/pod UIDs, Git SHA, digest, response, environment, timestamp, run, and trace IDs |
| D22-FLOW-02 | D22-UC-02 | Denied | Developer attempts a production-context or cluster-admin mutation | 1. Resolve actor and kubeconfig context. 2. Submit the bounded authorization check. 3. Record denial. 4. Read an allowed dev object as positive control. 5. Seal audit evidence. | Denied verb/resource/context, allowed dev read, IAM session, Kubernetes audit ID, environment, time, and test ID |
The smallest useful dev system connects a developer action to a real generated-application preview.
System design derived from the flows
EKS manages the Kubernetes control plane, while the team still owns VPC integration, nodes, access, workloads, and product evidence. That responsibility split explains why ACTIVE alone is insufficient.
| Use case ID | Entry point | Responsible services | Authoritative store | Failure evidence |
|---|---|---|---|---|
| D22-UC-01 | infra/stacks/dev and dev overlay | Terraform, AWS EKS, managed node group, kubelet, Kubernetes controllers, Forge services | Terraform backend and AWS APIs for infrastructure; Kubernetes API for workloads; Forge stores for product state | Active cluster with zero Ready nodes, Pending pod, digest mismatch, absent endpoint, or failed lifecycle response |
| D22-UC-02 | IAM session and Kubernetes access entry | AWS IAM, EKS access entry, Kubernetes RBAC and audit, Forge authorization | IAM and EKS access configuration plus Kubernetes RBAC; generated app database for app roles | Unexpected yes from kubectl auth can-i, wrong context, missing audit, or cross-plane role assumption |
Terraform creates the cluster boundary; Kubernetes schedules the Forge processes onto real EC2-backed nodes. Scoped access is adjacent because it controls who can initiate or inspect that path.
DRY keeps AWS composition in infra/modules/environment; SRP keeps control-plane, generator, runtime, broker, and evidence services separate. Their injected HTTP clients apply IoC/DI, and each service’s route/domain/store split follows MVC. Today, Forge.generate() calls the Generator synchronously over HTTP and stores the returned artifact before responding. The transactional outbox publishes audit events through the broker to Evidence; it does not queue generation work. A successful dev proof must therefore time the request/response path and separately verify its audit event.
Data model and ownership
Cluster access and generated-app access must not share a role record, or infrastructure permission could expose customer rows. This slice uses existing generated-application storage only for the previewed app.
Generated-application database: Required in this slice — the generated application owns its dev tenant, user, and preview domain records independently of provider and cluster access.
| Record or entity | Store and owner | Primary key | Foreign key or opaque reference | Tenant key | Material constraint | Lifecycle and deletion | Use case IDs |
|---|---|---|---|---|---|---|---|
| DevClusterRevision | Terraform backend and AWS EKS, owned by Dev Platform | cluster ARN plus stack revision | Opaque dev VPC, node group, role, and KMS refs | environment_id | Account, endpoint, state, and deployment role are dev-only | Create, upgrade, observe, drain workloads, destroy after data retirement; retain audit | D22-UC-01, D22-UC-02 |
| DevAccessMapping | IAM/EKS access and Kubernetes RBAC, owned by Platform Security | principal ARN plus cluster ARN | Local role-binding ref; no generated-app role inheritance | environment_id | Least privilege, expiry, independent revocation, and audit are mandatory | Grant, review, revoke, retain audit | D22-UC-02 |
| PreviewTenantRecord | Dev generated-app database, owned by the generated application | app_tenant_id | Opaque provider project and release refs | app_tenant_id | App authorization predicate applies to every query | Create for preview, export if required, delete with preview retirement | D22-UC-01 |
The cluster, access mapping, and application tenant now have separate lifecycle records. An opaque project reference correlates them without moving authority between planes.
Verify from substrate to customer
A workload check can query the wrong cluster and look healthy, so print identity and context before observations. Run this labelled snippet from the public zheta-kubernetes-lab monorepo using stable paths infra/stacks/dev and gitops/apps/forge/overlays/dev.
: "${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; }
aws --profile "$AWS_PROFILE" --region "$AWS_REGION" sts get-caller-identity
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.{arn:arn,status:status,public:resourcesVpcConfig.endpointPublicAccess}'
kubectl --context zheta-forge-dev get nodes
./scripts/verify-release.sh dev
kubectl --context zheta-forge-dev -n zheta-forge port-forward service/control-plane 18080:8080
# In another terminal, only after the port-forward is ready:
FORGE_URL=http://127.0.0.1:18080 FORGE_TOKEN="${FORGE_TOKEN:?set the explicit bearer token}" ./scripts/smoke-product.sh
Run the AWS and Kubernetes commands from inside the VPC or a connected network because the module disables the public EKS endpoint; AWS documents that a private-only endpoint is not reachable from the public internet (EKS cluster endpoint). verify-release.sh fails closed until five real ECR image digests and four durable Secrets exist. The fifth image is the inherited broker: cloud environments use SNS/SQS and keep its Deployment at zero replicas, but still pin its tag as part of the immutable release.
| Effect field | What happens |
|---|---|
| Declared intent | Refuse dev release until digest/Secret prerequisites pass, then prove the exact zheta-forge-dev customer path through a deliberate port-forward |
| Interpreter | AWS CLI, Kubernetes API, Kustomize, workload controllers, and Forge APIs |
| Software effect | Desired Deployments and Services reconcile; a preview lifecycle writes provider, runtime, evidence, and app-owned records |
| Hardware effect | EKS nodes consume EC2 CPU, memory, disk, and network for pods and requests |
| Evidence | Account, context, Ready nodes, rollout generations, digests, response, run, and trace IDs |
Key takeaways
Development is the first AWS evidence environment, not a small production account.
- Trace EKS from VPC and nodes to a product response.
- Test IAM, Kubernetes RBAC, and app authorization separately.
- Promote immutable digests; never copy dev credentials or state.
Checklist
The dev cluster is useful only when feedback is fast and bounded.
- [ ] Caller account and kubeconfig context name dev
- [ ] Nodes, workloads, digest, and customer lifecycle are correlated
- [ ] Privileged write is denied while an allowed dev read succeeds
- [ ] Preview data has an explicit retirement path