DEV Community

Cover image for Same Architecture, Different Deployment: Why Traffic Shape Beats Diagrams
Suresh
Suresh

Posted on

Same Architecture, Different Deployment: Why Traffic Shape Beats Diagrams

Most deployment advice starts with architecture. Draw the boxes, pick the services, ship it. I believed that was the whole job until a client project showed me otherwise.

Two projects can have the same architecture diagram and still need very different deployments. What decides it is the features of the app and the shape of its traffic. This post is about the project that made that clear to me, and how I ended up on EC2 with Ansible instead of the more fashionable options.

The client

I took over a project and moved it onto EC2 first. The brief was short: keep it secure, and don't let it fall over when traffic jumps.

Traffic was not large, about 3K users a day. What made it interesting was how it moved:

When Traffic What it meant
Normal day About 3K users Two modest servers are enough
Product releases/events Double or more, in bursts Extra capacity for a few hours, not all year
December holidays A different pattern again Don't pay for peak capacity every month

The important detail is that I knew these dates in advance. Fixed servers would have been either oversized all year or too small on release day.

Which setup for which situation

Before the details, here is the short version. Find the row closest to your project.

Your project looks like Go for Trade-off
Steady traffic, small team, spikes on known dates EC2 + Auto Scaling + ALB Cheapest at steady load, but you patch the servers
Sudden, unpredictable spikes ECS on Fargate Nothing to patch, but a higher price per unit
Small event-driven jobs (resize an image, send an email) Lambda Pay only when it runs, but 15 minute limit and DB connection pressure
WebSockets or long-lived connections Fargate or EC2 Connections stay open, a poor fit for Lambda
Heavy 24/7 load ECS on EC2 + Savings Plans Best price at constant load, but two layers to scale
A team already living in Kubernetes EKS Reuses existing skills, big operational cost

ECS and Fargate are sometimes presented as rivals. They are not: ECS is the orchestrator, and Fargate is one way for ECS to run your containers.

The app has to be ready first

Before anyone touches a server, a few things must be true in the code. I have seen more "infrastructure problems" caused by these than by AWS itself.

  • The app is stateless. If sessions or files live on one machine's disk, you cannot run two copies. Sessions go in JWT or Redis, uploads go to S3.
  • There is a /health endpoint. The load balancer and Auto Scaling both rely on it to decide whether an instance is alive.
  • I know my database connection limit. Every new instance brings its own connection pool. Ten instances with a pool of 20 is 200 connections, and the database may not allow that.
  • Cron jobs run in one place. Otherwise they fire on every copy of the API at the same time.

None of this is infrastructure work. It is plain application code, and it decides what you can do afterwards.

The options, judged for this client

Option In its favour Against it For this client
EC2 + Auto Scaling + ALB Cheapest at steady load, full control, my Ansible roles work as they are I patch the OS, new instances take minutes to boot, I tune the scaling My pick
ECS on Fargate Nothing to patch, scales per task Roughly 15 to 40% more per unit than raw EC2, no host access, no GPU Right if nobody wants to own servers
Lambda Near-zero cost when idle, instant scaling 15 minute limit, DB connection pressure, awkward for WebSockets Side jobs only
EKS Huge ecosystem Steep learning curve and running cost Overkill

I chose EC2 with Auto Scaling for three reasons. The traffic is small, I know the spike dates, and I already had Ansible roles that configure the servers. Scheduled scaling covers the known spikes without paying a Fargate premium all year. If nobody on the team wanted to own patching, I would flip that decision and use Fargate without a second thought.

Autoscaling: what scales on what

This is the part where being a developer helped most, because several of the real problems live in the app and not in AWS.

Minimum capacity. Two instances across two availability zones. If one dies, the site stays up.

The scaling rule. I use target tracking, either CPU around 60% or ALB requests per target. Requests per target follows actual traffic, so there are no thresholds to guess.

Known events. A scheduled action raises the minimum before a release or before December. A new instance takes minutes to become useful, so waiting for the spike to start is already too late.

Boot time. I bake an AMI after the Ansible role runs. New servers start ready instead of installing everything at boot.

Health checks. Use the ELB health check type, pointed at /health, so a broken instance is replaced automatically.

The database. Compare the maximum connections against instances multiplied by pool size. This is the most common surprise when a Node app scales out.

One Node-specific trap is worth knowing. Node runs on a single core per process. On a 2-vCPU instance running one Node process, CPU may never go much past 50%, so a CPU-based rule never fires. Run one process per core (PM2 cluster mode works) or scale on requests per target instead.

Before any release day, load test staging with k6 or Artillery. It tells you more than any estimate.

Security: EC2 versus Fargate

Concern EC2 Fargate
OS patching Me AWS
SSH access I have to lock it down None (ECS Exec if you need a shell)
Isolation I manage the shared host Each task is isolated
IAM Instance role Task role per service
Host-level security agents Possible Not possible

If compliance requires an agent on the host, that points to EC2. Otherwise Fargate gives you a smaller attack surface for less work.

On EC2 my Ansible hardening role covers the basics: key-only SSH with root login disabled and no public SSH (Session Manager or a locked-down IP), servers and database in private subnets with security groups chained from ALB to app to DB, secrets kept in Ansible Vault or Secrets Manager and never in the repo, and automated RDS backups together with a restore I have actually tested.

Ansible and Terraform: who does what

I work with Ansible, and this setup lives in it.

  • Roles configure the servers: packages, users, SSH hardening, nginx, Docker.
  • A playbook deploys a new version one server at a time, with a health check between each.
  • Ansible Vault holds the deploy-time secrets.
  • A dynamic inventory (aws_ec2) finds the instances that Auto Scaling created, by tag. Instances come and go, so a hard-coded host list breaks quickly.
  • The same roles bake the AMI.

Before changing anything, I preview it:

ansible-playbook -i inventory/prod.aws_ec2.yml site.yml --check --diff
Enter fullscreen mode Exit fullscreen mode

And this is a trimmed version of the deploy playbook:

- hosts: web
  become: true
  serial: 1            # one server at a time, the ALB keeps traffic on healthy ones
  vars_files:
    - secrets.yml      # encrypted with ansible-vault

  tasks:
    - name: Run the new version of the API
      community.docker.docker_container:
        name: api
        image: "{{ app_image }}:{{ app_version }}"
        state: started
        recreate: true
        restart_policy: always
        published_ports: ["3000:3000"]
        env:
          DB_HOST: "{{ db_host }}"
          DB_PASSWORD: "{{ db_password }}"

    - name: Wait until /health answers
      ansible.builtin.uri:
        url: http://localhost:3000/health
        status_code: 200
      register: health
      retries: 10
      delay: 3
      until: health.status == 200
Enter fullscreen mode Exit fullscreen mode

I don't use Terraform on this project, but it is worth saying where it fits. It is strong at creating and tracking cloud resources such as the VPC, ALB, RDS and IAM, with state you can review. A team that rebuilds environments often may prefer it for that layer and keep Ansible for what runs inside the servers. The two are not rivals.

When I would choose differently

  • If nobody wants to patch servers, or traffic becomes unpredictable, I would move to Fargate.
  • If the load turns into a heavy 24/7 one, I would look at ECS on EC2 with Savings Plans.
  • If the team grows into Kubernetes, EKS starts to make sense.
  • For small side tasks like image resizing or emails, I would add Lambda next to the main app.

What I took from it

The server was never the real decision. The traffic shape, the app being ready to scale, and a setup I can repeat with one command were. Pick the simplest option that survives your busiest day, keep it in code, and load test it before that day arrives.

If you have deployed for a client with spiky traffic, I would like to hear what you chose and what you would change.

Top comments (0)