Skip to content

Phase 4 · ORCHESTRATION & IAC

Project 3, Day 1: design the capstone architecture

Day 81 of 90 ~60 min 0/20 in phase Builds on Day 80

By the end of today

  • Design the capstone: a single-node k3s cluster on EC2, provisioned by Terraform
  • Justify k3s-on-one-instance over EKS on cost, and name the HA tradeoff
  • Estimate the monthly bill and list every resource the next four days build

Designing the capstone: one cloud K8s cluster, all of Phase 4

Section 1 of 5 · ~2 min

Welcome to Project 3 — the Phase 4 capstone, and the last project of the 90 days. Over five days you take linkstash, the FastAPI URL shortener you built in Project 1 (image ghcr.io/pushkar/linkstash:v1.0.0, Uvicorn on :8000), and deploy it to a real cloud Kubernetes cluster using everything Phase 4 taught: Terraform (days 76-80) to provision, Kubernetes (days 66-73) to run it, and Helm + Ingress (days 74-75) to package and expose it. Today you build nothing billable — you design, exactly as Project 2’s Day 1 did.

The target architecture is deliberately small. One EC2 instance — a t3.small running k3s, a CNCF-certified Kubernetes distribution packed into a single ~60 MB binary — inside a minimal VPC (10.0.0.0/16, one public subnet in us-east-1a). Terraform builds the network, the instance and an Elastic IP; the instance’s user_data installs k3s on first boot. On that one node you’ll run linkstash and an in-cluster Postgres 16, package the app as a Helm chart, and expose it over HTTPS through k3s’s built-in Traefik ingress with a cert-manager Let’s Encrypt certificate.

Why k3s on one instance, not EKS. A managed EKS control plane costs ~$0.10/hour — about $73/monthbefore a single worker runs. For a five-day capstone you don’t want that meter. k3s gives you a genuine, certified Kubernetes API — kubectl, your manifests and Helm all behave identically — for the price of one small EC2 box. The honest tradeoff: this is one node in one AZ. No control-plane HA, no failover — the opposite of Project 2’s two-AZ design. It’s the right call for learning and demos, and the wrong one for production, where EKS earns its bill.

Real world: the two setups are a food truck versus a restaurant chain’s central kitchen. k3s on one instance is the food truck — one small engine, cheap to run, serves real food, and if it breaks you’re closed for the day. EKS is the industrial kitchen with redundant everything and a manager on every shift: far more resilient, and far more expensive whether or not anyone orders. You pick the truck to learn the trade and prove the recipe.

A named example makes the small-footprint case concrete: Chick-fil-A famously runs Kubernetes at the edge — a small cluster in each of its ~2,800 restaurants — precisely because a lightweight, single-site cluster is the right tool when you don’t need a fleet of managed control planes. Your capstone is that same shape: one node, real Kubernetes, minimal cost.

Your laptop runs Terraform, kubectl and Helm to provision and manage, inside an AWS VPC (10.0.0.0/16, one public subnet in us-east-1a), a single t3.small EC2 instance running k3s. On that node run Traefik ingress, the linkstash Pod and an in-cluster Postgres. An Elastic IP fronts the node, and users reach linkstash over HTTPS on 443. VPC 10.0.0.0/16 · public subnet · us-east-1a (single AZ) your laptop terraform·kubectl·helm users HTTPS :443 t3.small · k3s node Traefik linkstash Postgres 16 Elastic IP 203.0.113.24 apply · kubectl · helm :443 → Traefik :5432
One t3.small runs the whole stack — Traefik, linkstash and Postgres on k3s; the Elastic IP fronts it and users reach the app over HTTPS. Terraform builds it; Helm and kubectl fill it.

Today’s job is to lock this design down — the resource list, the build order, and a monthly cost estimate — so the next four days are execution, not improvisation.

Hands-On Lab

Section 2 of 5 · ~4 min

Budget about 30 minutes. Drive this from your WSL2 Ubuntu terminal as your IAM user (not root), in us-east-1, with Docker running. Nothing here creates a billable resource — today is design plus three read-only checks. Account IDs, ARNs and image digests are unique to you — yours will differ from the samples.

What this costs: ₹0 today. This is a planning day — aws sts get-caller-identity, listing AZs, and pulling an image you already built are all free, and you create zero AWS resources. The meter starts on Day 82, when terraform apply launches the t3.small ($0.0208/hr, ~$15/month if left 24/7) and allocates an Elastic IP ($0.005/hr, ~$3.65/month). The VPC, subnet, gateway, route table and security group are all free. Call it ~$19/month if you forget it’s running — and ₹0 on a free-tier t2.micro if you tear down the same day. The cost table you build in step 4 is the number to remember; Day 85 ends with terraform destroy.

# 1. Confirm you're your IAM user (NOT root) and pin the region for the whole project.
aws sts get-caller-identity
export AWS_DEFAULT_REGION=us-east-1
# Output (your Account and Arn will differ):
# {
#     "UserId": "AIDA...EXAMPLE",
#     "Account": "123456789012",
#     "Arn": "arn:aws:iam::123456789012:user/devops-you"
# }
# 2. Pull the linkstash image you shipped in Project 1 — Day 83 runs this exact tag on the cluster.
docker pull ghcr.io/pushkar/linkstash:v1.0.0
# Output (the digest is yours-will-differ):
# v1.0.0: Pulling from pushkar/linkstash
# Digest: sha256:9f0c...e21a
# Status: Downloaded newer image for ghcr.io/pushkar/linkstash:v1.0.0

Lay out the capstone repo now — one folder per layer. Day 82 fills terraform/, Day 83 fills k8s/, Day 84 fills chart/, and Day 85 writes README.md:

# Repo layout — ~/linkstash/deploy/capstone/
terraform/     VPC, subnet, IGW, route table, k3s-sg, EC2 + user_data (k3s), Elastic IP
k8s/           Postgres Deployment/PVC/Service/Secret, linkstash ConfigMap  (Day 83)
chart/         Helm chart: templated linkstash Deployment/Service/Ingress/ConfigMap  (Day 84)
README.md      architecture + how to run + teardown — the portfolio piece  (Day 85)

The security-group plan scopes each port to exactly who needs it — linkstash’s Traefik serves 80/443, SSH is yours alone, and the Kubernetes API stays closed:

# Security-group plan (k3s-sg — tightest scope that still works)
inbound  22    from YOUR.IP/32 only    (admin — SSH to the node)
inbound  80    from 0.0.0.0/0          (Traefik — HTTP + ACME challenge)
inbound  443   from 0.0.0.0/0          (Traefik — HTTPS)
# 6443 (Kubernetes API) NOT opened — reach the cluster over SSH instead

This is the build order for the next four days — read it as your checklist:

# Resource inventory & build order
Day 82  Terraform    : VPC, public subnet, IGW, route table, k3s-sg, EC2 t3.small
                       (+ user_data installs k3s), Elastic IP  → node Ready
Day 83  Kubernetes   : in-cluster Postgres (Deployment+PVC+Service+Secret),
                       linkstash ConfigMap + Deployment + Service  → pods Running
Day 84  Helm/ingress : chart/linkstash, Traefik Ingress, cert-manager + TLS  → HTTPS
Day 85  e2e + destroy: smoke test, README, git push, THEN terraform destroy → ₹0

Now estimate the monthly bill if this stack were left running 24/7 (730 hrs). These are us-east-1 2026 list rates — yours will differ with traffic and account terms:

# Monthly cost estimate (left running 24/7 — the number Day 85 avoids)
Resource                       Rate                 ~Monthly
EC2 t3.small (on-demand)        ~$0.0208/hr          ~$15
Elastic IP (1 public IPv4)      $0.005/hr            ~$3.65   (bills even when stopped)
EBS gp3 root (8 GB)             ~$0.08/GB-month      ~$0.64
VPC / subnet / IGW / SG / RT    free                 $0
                                              TOTAL  ~$19 / month
-- for comparison, NOT used here --
EKS control plane               $0.10/hr             ~$73     (before any worker node)
# 3. Confirm the region resolves and prove nothing is billing yet — you created zero resources today.
aws ec2 describe-availability-zones --query 'AvailabilityZones[0].ZoneName' --output text
aws ec2 describe-instances --query 'Reservations[].Instances[].InstanceId' --output text
# Output — the AZ you'll deploy into, then an empty instance list:
# us-east-1a
#

Read the plan back: one t3.small running k3s in one public subnet, a security group that opens only 80/443 to the world, an in-cluster Postgres, Helm + Traefik + TLS on top, and a ~$19/month price tag that exists only if you forget to tear down. Every command over the next four days traces back to this page — that is what a planning day buys you.

Common Errors & Fixes

Section 3 of 5 · ~3 min

These are the design mistakes this planning day exists to prevent — each surfaces as a real problem the moment you start building on a bad plan. Read the reasoning slowly; the judgement is the skill.

Common error: Reaching for EKS “because it’s real Kubernetes” and watching the bill start before you deploy anything:

Amazon EKS · Cluster "linkstash" · status ACTIVE
Estimated charges (month to date): $18.20   ← control plane only, 0 workloads

Why: An EKS cluster bills its control plane at ~$0.10/hour the moment it’s ACTIVE, whether or not a single pod runs — about $73 for a full month, before you add nodes, a load balancer or a NAT gateway. For a five-day learning capstone that’s pure waste, and it’s easy to leave running.

Fix: Use single-node k3s on a t3.small (this design). You get a certified Kubernetes API for a few dollars a month, and terraform destroy on Day 85 returns it to ₹0. Reach for EKS when HA and a managed control plane actually earn their cost — not to learn the API.

How you’d spot it in prod: A Cost Explorer line for “Amazon Elastic Kubernetes Service” that’s non-zero while your workloads are idle means you’re paying for control planes you’re not using — consolidate clusters or move dev/demo work to something lighter.

Common error: Planning to keep Postgres’ data on the node’s local disk and treating it like it’s durable:

# design note (the trap):
Postgres → PVC on k3s local-path → /var/lib/rancher/... on the ONE node
"the data is safe" ← only until that single instance dies

Why: k3s’s local-path StorageClass writes to a directory on the node’s own disk. That survives a pod restart, but not the instance being terminated — and a single-node cluster has no second copy. It’s fine for a demo, dangerous if you mistake it for production durability.

Fix: Name the tradeoff in your design today: in-cluster Postgres on local-path is deliberate for cost, and the production answer is managed RDS with backups and Multi-AZ failover — exactly what Project 2 used. Write that in the README so the limitation is a choice, not an accident.

How you’d spot it in prod: A stateful workload whose only copy of data is a node-local volume is a single point of failure — if your backup story is “the PVC,” you don’t have one.

Common error: Leaving the Kubernetes API (6443) open to the internet in the security-group plan “so kubectl works from anywhere”:

inbound  6443  from 0.0.0.0/0    ← the whole world can reach your cluster API

Why: The API server is the control plane of your cluster; exposing 6443 to 0.0.0.0/0 invites credential-stuffing and exploit scans against the single most sensitive endpoint you have. Convenience today, incident tomorrow.

Fix: Leave 6443 closed in k3s-sg (this plan does) and reach the cluster over SSH — you already scope 22 to your own IP. If you must expose the API, scope it to your /32, never the world. Open only 80/443, the app’s real front door.

How you’d spot it in prod: A security-group or firewall rule allowing 6443 (or 2379-2380, etcd) from 0.0.0.0/0 is an audit red flag — the control plane should never be internet-wide reachable.

Cloud Architecture Interview Questions

Section 4 of 5 · ~1 min

These four are what a screening round asks once “can you run Kubernetes?” becomes “can you design and cost a deployment?” — cover each answer, say your own version out loud first, then compare, because recalling before revealing is what makes it stick. The four questions and answers render right after this note.

Go Deeper

Section 5 of 5 · ~1 min

Optional extras if you have ~30 more minutes today:

  • 10 min — Rebuild the step-4 estimate in the AWS Pricing Calculator for one t3.small + one Elastic IP + 8 GB gp3, then add an EKS cluster and watch the total jump — the exact tradeoff this design avoids.
  • 10 min — Skim the k3s architecture docs and note how one binary embeds the API server, scheduler, containerd, Traefik and local-path storage — the batteries you’ll lean on days 82-84.
  • 5 min — Re-read Project 2’s Day 1 (day 62) architecture and list, in one line each, what’s different here: single node vs two AZs, k3s vs Fargate, in-cluster Postgres vs RDS, Traefik vs ALB. Naming the deltas is half the interview answer.
  • 5 min — Read the AWS Well-Architected reliability pillar and note honestly where this single-node design doesn’t meet it — then remember that’s a deliberate, documented cost tradeoff, not an oversight.
Why run this capstone on single-node k3s instead of EKS? Both

Cost and learning speed. A managed EKS control plane bills about $0.10/hour — roughly $73/month — before one worker node runs; a t3.small running k3s is a few dollars a month, near-free on the free tier. k3s is a CNCF-certified Kubernetes distribution, so kubectl, my manifests and Helm behave exactly as on any cluster — I learn the real API, not a toy. The honest tradeoff is that this is one node in one Availability Zone: no control-plane HA and no failover, the opposite of Project 2's two-AZ design. So k3s is right for learning, demos and edge; EKS is the answer once uptime justifies the bill.

Why spend a whole day designing before you touch Terraform? Both

Because the expensive mistakes on a cloud project are architectural, not typos, and they're cheapest to fix on paper. A planning day forces three decisions up front: what each resource is and why it exists, which ones bill by the hour, and the exact order to build them. I leave today with a resource inventory, a monthly cost estimate, and a build sequence — so the next four days are execution, not improvisation. It's the same discipline as Project 2: the day drawing the topology and pricing it is what separates a deploy that works from one that surprises you on the invoice or leaves resources billing after you think you're done.

In this design, which resources cost money and which are free? Both

The two that bill by the hour regardless of traffic are the EC2 instance — a t3.small at about $0.0208/hour, ~$15/month if left up — and the Elastic IP, a public IPv4 at $0.005/hour (~$3.65/month) that keeps charging even while the instance is stopped. The 8 GB gp3 root volume is under a dollar a month. Everything else in the design — the VPC, the subnet, the internet gateway, the route table and the security group — is free to exist. So an idle demo still costs the instance plus the IP, which is exactly why Day 85 ends with terraform destroy rather than just stopping the box.

How does this one capstone reuse all three Phase-4 tools? Both

Each owns a layer. Terraform (days 76-80) provisions the AWS foundation — the VPC, the EC2 instance and its Elastic IP — and installs k3s through the instance's user_data, so the whole environment is code I can rebuild or destroy with one command. Kubernetes (days 66-73) runs the workload: Deployments, a Service, a ConfigMap and a Secret describe linkstash and its in-cluster Postgres declaratively. Helm (days 74-75) packages those manifests into a versioned chart with values, and a Traefik Ingress plus cert-manager expose the app over HTTPS. Terraform builds the cluster, Kubernetes runs the app, Helm ships and exposes it — the three tools stacked exactly as a real team layers them.

Mark Day 81 complete

Tomorrow you write the Terraform — a VPC, subnet, security group, EC2 and Elastic IP — and terraform apply brings up the single-node k3s cluster.

Stuck on today’s lab? Ask in Mission 90 Q&A