The situation
A media company publishes travel photography and video. Four workloads, one platform team of five.
The image resizer runs when a photographer uploads to S3. It generates six sizes of each image and writes them back. Uploads arrive in bursts: nothing for six hours, then 4,000 images in twenty minutes when a shoot finishes. Each resize takes about four seconds.
The transcoding engine is a commercial product licensed per physical core, with an enterprise agreement that has two years to run. It needs a specific operating system version and a GPU, and the vendor supports it only on configurations it has certified.
The web API serves the public site and the mobile app. Traffic is steady on weekdays, roughly double at weekends, and the application is already packaged as a container image. It runs continuously.
The rendering job stitches video sequences overnight. It is a queue of between 200 and 900 independent tasks, each taking twenty minutes to two hours, and the whole batch has to be finished by 06:00. Nothing runs during the day.
The team is five people, and none of them has operated a Kubernetes control plane.
What actually matters
The first question for each workload is what shape its demand has, because that is what decides whether paying for idle capacity makes sense. A workload that runs continuously can justify a server that runs continuously. A workload that runs for twenty minutes after six hours of silence cannot, and a workload that runs for six hours at night and not at all in the day sits between the two. Matching the billing granularity to the demand shape is where most of the money is in a compute decision.
The second is how much of the stack the team has to operate afterwards. Every option here runs the workload. They differ in what is left over: an operating system to patch, a cluster to upgrade, a scaling policy to tune, or nothing. With five people covering four workloads, that residue weighs as heavily as the bill.
Third, constraints that remove options outright. A licence tied to physical cores and a vendor support matrix naming specific configurations act as a filter. When a workload needs visibility of the underlying hardware or a certified operating system build, the serverless options are gone before the comparison starts, and the remaining question is which of the others satisfies the licence.
Finally, runtime limits. Some options place a ceiling on how long a single unit of work can run, and a task that takes two hours does not fit under a fifteen-minute ceiling whatever the billing model looks like. Check the ceiling first, before comparing anything else.
What we’ll filter on
- Billing granularity matches the demand shape, so idle time is not paid for.
- No operating system for the team to patch, unless the workload demands one.
- Handles the workload’s longest single unit of work.
- Satisfies a per-core licence and a vendor-certified configuration where one applies.
- Scales to a burst without a human involved.
The landscape
Amazon EC2 gives virtual machines with full control of the operating system, the instance family, and visibility of the hardware where that is needed. Instance families are shaped for different work: general purpose for balanced loads, compute optimised for processor-heavy work, memory optimised for large in-memory data, storage optimised for high local throughput, accelerated computing for GPUs and other accelerators, and high-performance computing for tightly coupled cluster work. On-Demand billing is per second with a one-minute minimum for Linux, Windows, RHEL and Ubuntu Pro; SUSE Linux Enterprise Server is billed by the hour. What EC2 leaves with the team is the guest operating system, patching, and capacity decisions. Dedicated Hosts add visibility of the physical server and its sockets and cores, which is what a per-core licence usually requires. An Auto Scaling group can place instances on them through a host resource group, though the hosts themselves are allocated rather than elastic.
AWS Lambda runs a function in response to an event, with nothing to provision. It scales from zero up to the account’s concurrency quota, which starts at 1,000 concurrent executions per Region and can be raised on request. Charges are per request and per gigabyte-second of duration, rounded up to the nearest millisecond, so an idle function costs nothing. On the default compute type a single invocation runs for at most 15 minutes, and that ceiling decides whether Lambda is in scope. Lambda Managed Instances, which run functions on EC2 instances that Lambda provisions and patches, raise the ceiling to 90 minutes for asynchronous and event-source-mapping invocations, and bill on EC2 rates plus a management fee rather than per request.
Amazon ECS with EC2 capacity orchestrates containers across a cluster of instances the team owns. It gives container packing and scheduling while leaving the instances to be patched and scaled.
Amazon ECS with AWS Fargate runs the same task definitions with no instances at all. Fargate provisions the compute per task and charges per second, with a one-minute minimum, for the vCPU and memory the task requests, counted from the start of the image pull until the task stops. It removes the operating system from the team’s responsibilities. The container image stays the team’s to build and keep patched, and Fargate offers no GPU option.
Amazon EKS is managed Kubernetes, on EC2 nodes or on Fargate. It suits an organisation with Kubernetes skills or a dependency on the Kubernetes ecosystem. It charges USD$0.10 per cluster per hour while the cluster runs a version in standard support, rising to USD$0.60 once that version moves to extended support, and it leaves a cluster to keep current.
AWS Batch manages batch computing: a job queue, a compute environment whose minimum vCPU count can be zero so it drains to nothing when the queue empties, and dependencies between jobs. It runs jobs on EC2 (including Spot), on Fargate, on ECS Managed Instances or on EKS, and it is built for the shape where a large number of independent jobs have to finish by a deadline.
AWS Elastic Beanstalk takes application code or a container and provisions the environment beneath it: instances, load balancer, scaling group and monitoring. There is no charge for Beanstalk itself, only for the resources it creates, and the team keeps full access to every one of them. Its configuration options cover instance type and scaling but not tenancy, so it cannot place an environment on a Dedicated Host.
Amazon Lightsail bundles a virtual private server, storage and a data transfer allowance at a predictable monthly price, for small steady workloads where simple pricing matters more than flexibility.
Evaluation
Side by side
| Option | Billing matches burst | No OS to patch | Long-running work | Meets a per-core licence | Auto-scales |
|---|---|---|---|---|---|
| EC2 with an Auto Scaling group | ✗ | ✗ | ✓ | ✓ | ✓ |
| EC2 Dedicated Host | ✗ | ✗ | ✓ | ✓ | Partly |
| AWS Lambda | ✓ | ✓ | ✗ (15 min) | ✗ | ✓ |
| ECS on EC2 | ✗ | ✗ | ✓ | Partly | ✓ |
| ECS on Fargate | ✓ | ✓ | ✓ | ✗ | ✓ |
| Amazon EKS | ✗ | Depends | ✓ | Partly | ✓ |
| AWS Batch | ✓ | ✓ | ✓ | ✗ | ✓ |
| Elastic Beanstalk | ✗ | ✗ | ✓ | ✗ | ✓ |
No row wins, because four workloads with four different demand shapes are not one decision. The licence column eliminates every serverless option for the transcoder. The long-running column eliminates Lambda for the renderer. The billing column eliminates always-on instances for the resizer.
Matching the workload to the compute
The solution
The image resizer goes to Lambda, triggered by an S3 object-created notification. Each resize takes four seconds, comfortably inside the fifteen-minute limit, and concurrency scales to the burst without a scaling policy. Between shoots nothing runs and nothing is charged, which no always-on option can match for a workload idle most of the day. Write the resized files to a second bucket, or under a prefix the notification does not cover, because a function that writes back into the bucket that triggered it invokes itself in a loop. Package the image library with the function or supply it as a layer; either way it is the team’s to keep patched.
The transcoder goes on a Dedicated Host with a GPU instance family, bringing the existing licence. The per-core terms need visibility of the physical sockets and cores, which a Dedicated Host provides and a shared tenancy instance does not. A Dedicated Instance gives the same isolated hardware without that visibility, so it does not satisfy the terms. The vendor’s certified operating system build can be installed because the team controls the guest OS. AWS License Manager tracks entitlements by physical core or socket and can block a launch that would exceed the count, so a change in the fleet cannot breach the agreement unnoticed. Revisit the arrangement when the licence comes up for renewal, which is when a managed media service becomes worth comparing again.
The web API goes to ECS on Fargate. It is already a container image, it runs continuously, and Fargate removes the instances and their operating systems from a team that has four workloads and five people. Service auto scaling handles the weekend doubling on a target-tracking policy against average CPU or requests per target, and an Application Load Balancer distributes across Availability Zones. Fargate does not remove the container image from the team’s responsibilities, so the base image still needs rebuilding when its packages are patched. EKS would run this perfectly well, and would add a control plane to operate and a Kubernetes version to keep inside standard support.
The renderer goes to AWS Batch. A job queue of 200 to 900 independent tasks with a hard completion deadline is the shape Batch is built for: set the compute environment’s minimum vCPU count to zero and it drains to nothing once the queue empties, so the daytime cost is nothing. Individual jobs run for up to two hours, which rules out Lambda on either compute type. Because the jobs are independent and restartable, the compute environment can use Spot capacity for most of the fleet at a substantial discount, with the deadline protected by keeping some On-Demand capacity in the mix.
Two things worth saying about what was not chosen. Elastic Beanstalk would deploy the web API quickly and would still leave EC2 instances underneath for the team to patch, so it solves less than Fargate does for this case. And every one of these four workloads could be made to run on EC2 with enough scripting; the reason not to is that four different demand shapes would then be served by one billing model that matches only the web API.
What’s worth remembering
- Match the billing granularity to the demand shape: Lambda charges per request and duration and nothing while idle, Fargate per second for the vCPU and memory a task requests, EC2 per second with a one-minute minimum while the instance exists, busy or not.
- A Lambda invocation runs for at most 15 minutes on the default compute type, and 90 minutes on Lambda Managed Instances when invoked asynchronously or through an event source mapping. Check the ceiling before anything else.
- Fargate removes the instances and their operating systems and leaves the container image with you; ECS on EC2 leaves both. ECS is AWS’s own orchestrator and EKS is managed Kubernetes, so pick EKS for an existing Kubernetes dependency rather than as a default.
- A per-socket or per-core licence usually needs a Dedicated Host, which gives visibility of the physical sockets and cores; Dedicated Instances give isolated hardware without that visibility.
- AWS Batch is for a queue of independent jobs against a deadline: a compute environment with a minimum of zero vCPUs scales up to drain the queue and back to nothing, and restartable jobs make Spot capacity a good fit.
- EC2 instance families are grouped by what they optimise: general purpose, compute optimised, memory optimised, storage optimised, accelerated computing, and high-performance computing.