Full Teardown and Wrap-up
You've reached the end
The seventy-one preceding articles built and dissected a complete Kubernetes cluster. That cluster runs on six real EC2 instances, and every hour it runs is a sum of money. This final article cleans up the infrastructure, but first it's direct about cost — because "cleanup" isn't always deletion.
Three choices and their prices
The cluster has six instances (1× t3.small as the load balancer, 5× t3.medium for control plane + workers), each machine with a gp3 root disk of 20 GB, and one Elastic IP attached to the load balancer. List prices in ap-southeast-1 for the three ways to handle it:
| Choice | Compute | EBS (120 GB gp3) | Elastic IP | ~Total/month |
|---|---|---|---|---|
| Keep running | ~$212 | $11.52 | within IPv4 price | ~$245 |
| Stop (keep to save) | $0 | $11.52 | $3.65 | ~$15 |
| Teardown (delete entirely) | $0 | $0 | $0 | $0 |
The three numbers are very different, and the choice depends on intent. Done learning, never touching it again → teardown, back to $0. Want to come back and practice → stop: a stopped instance bills no compute, but the EBS disk keeps all its state intact (etcd, certificates, configuration), so aws ec2 start-instances and the cluster runs again. ~$15/month for a six-node HA cluster ready to switch back on is cheap. Below is the full teardown; if you choose stop, all you need is aws ec2 stop-instances --instance-ids <6 ids> and to stop there.
Teardown in the right order
Delete in order so you don't hit a dependency block. Start by terminating the six instances — because the root volumes are set DeleteOnTermination=true, the six gp3 disks delete themselves along with them, no manual cleanup:
REGION=ap-southeast-1
aws ec2 terminate-instances --region $REGION --instance-ids \
i-01e955f527ff25a57 i-05d8b7584a933394a i-07d62211877ed360b \
i-0ee0d05a3f68f2b73 i-0a33782c408f5bf09 i-0f1ab7628507cb9cd
aws ec2 wait instance-terminated --region $REGION --instance-ids <6 ids>
Check the disks went with them (no available volume left over billing):
aws ec2 describe-volumes --region $REGION \
--query 'Volumes[?State==`available`].VolumeId'
[]
Return the Elastic IP — once the instance is terminated, the EIP becomes "unattached" and starts billing if left there, so release it:
aws ec2 release-address --region $REGION --allocation-id <alloc-id-of-203.0.113.10>
Remove the IAM set up in Article 43 for EBS CSI (you have to detach the policy and pull the role out of the instance profile before deleting):
aws iam remove-role-from-instance-profile --instance-profile-name k8s-scratch-ebs-csi --role-name k8s-scratch-ebs-csi
aws iam delete-instance-profile --instance-profile-name k8s-scratch-ebs-csi
aws iam detach-role-policy --role-name k8s-scratch-ebs-csi --policy-arn arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy
aws iam delete-role --role-name k8s-scratch-ebs-csi
Finally clean up the small remaining things: the key pair, security group, and VPC if it was created specifically for the cluster (Article 3):
aws ec2 delete-key-pair --region $REGION --key-name k8s-scratch
# delete the dedicated security group + subnet + VPC if present (once no ENI is using them)
EBS snapshots have nothing to delete — the demo snapshots from Article 44 were cleaned up right then. After these steps, the account is back to the state it was before Article 3, with nothing billing.
Looking back on the journey
The cluster started from nothing. Part I built it piece by piece by hand: a self-signed CA then all the certificates (Articles 2–4), a three-node etcd (Article 6), API server / controller-manager / scheduler run by systemd instead of static pods (Articles 7–8), the kubelet and container runtime on the workers (Articles 10–11), then wiring pod networking and CoreDNS by hand, up to the smoke test turning the whole cluster green (Article 16). No kubeadm, so every flag, every cert, every route had to be understood before it could be built — that was the whole point.
Once built, the cluster became the lab for the rest. We went through the pod and workload-controller lifecycle (Parts III–IV), scheduling and autoscaling (Parts VII–VIII), storage with real EBS CSI (Part IX), then replaced the entire datapath: from kube-proxy iptables to Cilium eBPF kube-proxy-less, identity-based NetworkPolicy, Ingress and Gateway API (Part X). Part XI tightened security from authentication to Secrets and their detours. Part XII extended the API server itself — CRDs, webhooks, operators, aggregation. Part XIII kept the cluster alive: etcd backup, version skew, observability, leader election — mostly verified by real operations, including one controlled leader failover. And Part XIV used this very v1.36 cluster to try the features that just graduated in that release: admission via CEL (replacing webhooks), in-place pod resize, new storage (OCI volume, VolumeAttributesClass), node log query, and fine-grained kubelet authorization.
The thread running through it all isn't memorizing kubectl commands, but seeing what each high-level feature rests on underneath: kubectl logs is a CRI file on disk, resources.limits is memory.max in cgroup v2, a Service ClusterIP is a few lines of eBPF, a custom resource is a key in etcd. Building by hand from scratch gives you exactly that view — when something goes wrong on a production cluster, you know where to look because you assembled every layer yourself.
All the series' manifests, scripts, and commands are at github.com/nghiadaulau/kubernetes-from-scratch. The cluster can be rebuilt at any time from Part I — and now you know what every piece in it does.
Wrap-up
The infrastructure is cleaned up, or stopped to save it, your call — keep running ~$245/month, stop ~$15/month (EBS keeps state, can be switched back on), teardown $0. The teardown procedure in order: terminate the instances (taking the gp3 disks along thanks to DeleteOnTermination), release the Elastic IP, remove the EBS CSI IAM role/instance profile, delete the key pair and dedicated VPC — back to exactly the state before Article 3. And looking back: from a single self-signed certificate, we built by hand a complete HA Kubernetes cluster and then used it to deep-dive nearly every Kubernetes concept, always verified on a real cluster. What's left after deleting the infrastructure isn't the cluster, but understanding what sits beneath what — something that doesn't disappear when the instances terminate.
Thank you for going the whole distance.
You've reached the end