The kubernetes provider in the eks-quickstart, eks-addons-access and eks-https-ingress workbooks was configured from the managed cluster resource (host = aws_eks_cluster.this.endpoint). On destroy the endpoint resolved empty, the provider fell back to localhost:80, and refreshing kubernetes_deployment_v1.hello / kubernetes_service_v1.hello failed with 'dial tcp [::1]:80: connect: connection refused'. Move the kubernetes provider and the hello Deployment + Service into a nested workloads/ root module with its own state. It reads cluster identity from the parent via terraform_remote_state and the live endpoint/CA from a data aws_eks_cluster source, so the provider is only ever configured while the cluster exists. Apply parent then workloads; destroy workloads then parent.
title, description, category, tags, resources
| title | description | category | tags | resources | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| EKS Quickstart | Stand up a minimal managed-Kubernetes cluster and a browsable demo app — VPC, IAM roles, an EKS cluster, a single-worker node group, and a load-balanced web page — using Terraform on Spinifex. | Terraform Workbooks |
|
|
Terraform: EKS Quickstart
The smallest end-to-end EKS example on Spinifex: a VPC, the two IAM roles EKS needs, a single-worker cluster, and a demo web app you can open in a browser.
Table of Contents
Overview
Provision a managed Kubernetes cluster on Spinifex with Terraform/OpenTofu — and finish with something you can actually see. As well as the cluster, this workbook deploys a small demo app onto it and publishes it on the worker's public IP. When apply finishes, open the demo_url output: the page is served by nginxdemos/hello and prints the name of the pod that answered. Refresh it and the answering pod changes, so even a non-technical viewer can watch Kubernetes scheduling and load-balancing in real time.
Under the hood it keeps things minimal: a VPC with two public subnets, an IAM role for the control plane, an IAM role for the workers with the three managed policies AWS-managed node groups expect, a public-endpoint aws_eks_cluster, and a single-node aws_eks_node_group. Terraform then uses the Kubernetes provider (authenticating exactly like kubectl does, via aws eks get-token) to deploy the Deployment + NodePort Service.
What you'll learn:
- Configuring the AWS provider to target Spinifex's
eks,ec2,iamandstsendpoints - Creating the EKS cluster and worker IAM roles with faithful managed-policy attachments
- Provisioning an
aws_eks_clusterand a managedaws_eks_node_group - Pointing the Kubernetes provider at the new cluster and deploying a workload from the same
apply - Opening one rule on the auto-managed worker security group to expose a NodePort
What gets created
| Resource | Name | Purpose |
|---|---|---|
| VPC | eks-quickstart-vpc |
Isolated network (10.30.0.0/16) |
| Subnets | eks-quickstart-subnet-a/-b |
Public subnets for the cluster and worker |
| Internet Gateway | eks-quickstart-igw |
Egress so the worker can pull the demo image |
| IAM Roles | eks-quickstart-cluster-role, eks-quickstart-node-role |
Control-plane and worker roles |
| EKS Cluster | eks-quickstart |
Public API endpoint, API auth mode, Kubernetes 1.32 |
| Node Group | default |
One t3.medium worker |
| SG Ingress Rule | eks-quickstart-demo-nodeport |
Opens the demo NodePort on the auto-managed worker SG |
| K8s Deployment | hello |
nginxdemos/hello, 2 replicas |
| K8s Service | hello |
NodePort publishing the demo on the worker |
Spinifex specifics
- The cluster's security groups are auto-managed —
vpc_config.security_group_idsis ignored. To reach a NodePort, this workbook looks the worker SG up by its deterministic name (eks-cluster-<name>-nodegroup-sg) and adds a single ingress rule. - The worker AMI is always Spinifex's
eks-nodeimage;ami_typeis recorded but does not select the image. authentication_modemust be"API"(theAPI_AND_CONFIG_MAPmode is rejected).- The workers need outbound internet (here via the IGW) to pull the demo container image.
Prerequisites:
- Spinifex installed and running (see Installing Spinifex)
- The Spinifex
eks-nodeimage available on the cluster - OpenTofu or Terraform, plus
kubectland the AWS CLI
Instructions
Step 1. Get the Template
git clone --depth 1 --filter=blob:none --sparse https://github.com/mulgadc/spinifex.git spinifex-tf
cd spinifex-tf
git sparse-checkout set docs/terraform-workbooks
cd docs/terraform-workbooks/eks-quickstart
Or create a main.tf file and paste the full configuration below.
Step 2. Deploy
The cluster and the demo app are two root modules with separate state. Apply the cluster first:
export AWS_PROFILE=spinifex
tofu init
tofu apply
This creates the cluster (which bootstraps a control-plane VM and brings up k3s — a few minutes in CREATING), launches the worker, and opens the NodePort. Once it's ACTIVE, deploy the demo app from the nested workloads/ module:
cd workloads
tofu init
tofu apply
Why two modules? The Kubernetes provider in
workloads/reads the cluster endpoint from a livedata "aws_eks_cluster"source, so it's only ever configured while the cluster exists. Keeping it out of the cluster module meansdestroynever tries to refresh a workload against a cluster that's already gone — the failure mode where the provider falls back tohttp://localhost:80and reportsconnection refused. Always destroyworkloads/before the cluster.
Same profile for the Kubernetes provider. The Kubernetes provider authenticates by shelling out to
aws eks get-token, which has to reach the Spinifex STS endpoint. KeepAWS_PROFILE=spinifexexported for both applies.
Step 3. Open the Demo
Run from the cluster module directory (cd .. if you're still in workloads/):
tofu output demo_url
Open that URL in a browser. You'll see the nginxdemos/hello page showing the Server name (the pod that handled the request). Refresh a few times — with two replicas, the pod name alternates, demonstrating that the Service is load-balancing across the cluster.
Step 4. Inspect with kubectl (optional)
aws eks update-kubeconfig --name eks-quickstart --region ap-southeast-2
kubectl get nodes
kubectl get pods -o wide
kubectl get pods -o wide shows the demo pods and which node each landed on.
Cleanup
Destroy in reverse — the demo app first (while the cluster is still up), then the cluster:
cd workloads
tofu destroy
cd ..
tofu destroy
Troubleshooting
Demo URL Doesn't Load
The page is served from a NodePort on the worker's public IP. Work through the chain:
# Is the worker running with a public IP?
aws ec2 describe-instances --filters "Name=tag:spinifex:eks-cluster,Values=eks-quickstart" \
--query 'Reservations[].Instances[].[InstanceId,State.Name,PublicIpAddress]' --output text
# Did the demo pods roll out?
kubectl get pods -o wide
# Is the NodePort rule on the worker SG?
aws ec2 describe-security-groups --filters "Name=group-name,Values=eks-cluster-eks-quickstart-nodegroup-sg" \
--query 'SecurityGroups[0].IpPermissions'
If the pods are Pending, the worker may not be Ready yet — give it a moment. If they're ImagePullBackOff, the worker can't reach Docker Hub; confirm the IGW route and the worker's public IP.
kubernetes provider: connection refused / Unauthorized
The provider runs aws eks get-token against the Spinifex STS endpoint. Confirm the AWS CLI is still pointed at Spinifex and the cluster is ACTIVE:
aws sts get-caller-identity
aws eks describe-cluster --name eks-quickstart --query 'cluster.status'
If the cluster was still CREATING when the provider first tried to connect, just re-run tofu apply.
Cluster Stuck in CREATING
Control-plane bootstrap takes a few minutes. Confirm the underlying VM came up:
aws eks describe-cluster --name eks-quickstart --query 'cluster.status'
aws ec2 describe-instances --profile spinifex
Provider Connection Refused
sudo systemctl status spinifex.target
curl -k https://localhost:9999/