Back to Blog
2024-09-129 min read

Kubernetes, Lambda, and Fargate Are Different Abstractions, Not Just Different Services

They're not three flavors of the same thing. They're different deals about what you own versus what AWS owns. The right pick depends on which parts of that deal you actually want.

AWSKubernetesLambdaFargate

Kubernetes, Lambda, and Fargate Are Different Abstractions, Not Just Different Services

The comparison gets framed as if you're picking a service. You're not. You're picking how much of the runtime and operating surface you want to own, and what kind of work you want the rest of your team to be doing six months from now.

Lambda, Fargate, and Kubernetes sit at three different points on that ownership curve. Once you see them that way, the "which one should I use" question turns into a different question — which surface am I willing to operate? — and the answer falls out almost mechanically.

What you own vs. what AWS owns Less control More control Lambda function runtime: AWS scaling: AWS 15-min cap Fargate container, no nodes runtime: you scaling: you (HPA-style) long-running OK EKS / k8s cluster + control plane runtime: you scaling, scheduling: you policy surface, all of it

Lambda: a deal about the runtime

The Lambda deal is straightforward. AWS owns the runtime, the scaling, the OS, the patching, the cold-start machinery. You own a function. You don't get to keep state, you don't get to run for more than 15 minutes, you don't get to choose how your code is packaged beyond the rules of the platform.

In exchange you get scale-to-zero, scale-to-thousands, and nothing to operate when traffic is zero.

When that deal is good:

  • The unit of work is genuinely an event — a file arrived, a webhook fired, a queue has a message, a cron tick happened.
  • Throughput is wildly variable. Lambda costs nothing at idle and scales out by the thousands without you doing anything.
  • The runtime fits comfortably inside what Lambda likes (sane package size, no long-lived connections, no weird native dependencies).

When the deal goes bad: when the work doesn't fit the function model. A two-hour ETL job in Lambda is a Lambda misuse. A websocket server is a misuse. A binary with 4GB of CUDA libraries is a misuse. The right answer in those cases is to stop trying to bend the workload to fit the function and pick the abstraction that wants a long-running process.

ts// The Lambda shape: short, stateless, one event in, side-effects out.
export async function handler(event: { key: string }) {
  const document = await loadFromS3(event.key)
  const summary = await summarize(document)
  await persistResult(event.key, summary)
}

Fargate: a deal about the host

Fargate is the deal where AWS owns the host and you own the container. You package your code however you want. You set CPU and memory explicitly. You write a task definition. You scale it. You configure the health checks. AWS makes sure there's somewhere to run the container, gives you a network interface, and bills you per second per task.

The big difference from Lambda is conceptual: Fargate doesn't pretend your work is a function. It's a long-lived process holding connections and serving requests, the same shape your application has been since the 1990s. You think about it the same way you'd think about a Linux box, except the Linux box isn't your problem.

When this deal is good:

  • The service is meant to run continuously. APIs, queue workers, background processors, anything that holds connection state.
  • You want explicit, predictable resource sizing — 2 vCPU and 4GB, not whatever the runtime decides today.
  • You want container discipline (immutable images, per-task IAM, repeatable deploys) without the cost of node operations.

When it stops fitting: when you actually need something Fargate doesn't give you. GPUs aren't in the deal as of this writing. Daemonset-style sidecars on every host aren't a fit. Heavy local-disk workloads aren't a fit. If you find yourself fighting Fargate's constraints, the system is asking for a different abstraction.

Kubernetes: a deal about owning the platform

Kubernetes isn't a service. It's an operating model you put on top of compute. The deal is that you take on the cluster, the control plane (managed by EKS, but still yours to operate at the workload level), the policy surface, the networking model, the upgrade cycle, and in exchange you get one consistent primitive for running anything.

That deal is good when you have anything to run. Many services. Many teams. Workloads with different shapes that need consistent scheduling, networking, and policy. Operations like canary deploys, mesh-level mTLS, fleet-wide observability — things that are real wins at scale and pointless overhead for three services.

EKS-on-Fargate is a hybrid version of the deal: you get the Kubernetes API surface and lose the node operations. It's a genuinely useful middle ground for teams that want pod-level abstractions without managing a node fleet. The constraints are documented and they are real — no daemonsets, no GPUs, no privileged containers, no EBS volume attachment for pods. Inside those constraints, it's a thoughtful product.

The version of this deal that goes wrong is when a team adopts Kubernetes for one or two services. The platform surface is the whole point. If you're not using it, you're paying for it anyway.

The three deals side-by-side

| | Lambda | Fargate | EKS |

|---|---|---|---|

| Unit of deployment | function | container task | pod |

| You own the runtime? | no | yes (image) | yes (image) |

| You own the OS? | no | no | partially (you control kubelet/node config on EC2 nodes) |

| Max single execution | 15 min | unbounded | unbounded |

| Scale to zero | yes (free) | possible but loses warm capacity | usually no, in practice |

| Local state | no | per-task ephemeral | per-pod ephemeral, plus volumes |

| Sweet spot | events, glue, bursty handlers | long-running services | platform for many services |

| Where it goes wrong | bending non-function work into functions | trying to do GPU or per-host things | adopting it for too few services |

What I'd ask before picking

A short list of the questions that have actually decided this for me, in order:

  • How long does one piece of work run? (15-minute boundary matters.)
  • Is the work event-shaped or service-shaped? (Function vs long-running process.)
  • How many independent services will this team operate in 12 months? (One or two: Fargate. Twenty: maybe EKS.)
  • Do I need GPUs, daemonsets, custom networking, or shared-host scheduling tricks? (Yes: EKS with nodes.)
  • Who's on call for the infrastructure layer? (If it's "everyone, sometimes," keep the surface small.)

The decision that consistently aged well in my experience was the one that matched workload shape to abstraction shape and didn't try to be clever about the rest. Picking the most sophisticated option you can defend isn't architecture. Picking the simplest one that fits is.