Functions, managed containers or Kubernetes, decided by five variables

The choice is not about elegance. It is traffic shape, cold start tolerance, execution duration, environment control and what your team can actually operate at three in the morning.

The argument usually happens at the wrong level of abstraction. Someone says serverless does not scale, someone else says Kubernetes is over-engineering, and both are describing workloads they have run rather than the one in front of them.

There are five variables that actually decide it, and for most services three of them point the same way.

Traffic shape and idle time

The most important variable, and the one that makes the cost arithmetic obvious once you have measured it.

Functions bill per invocation and per unit of execution time. At zero traffic you pay nothing. That is decisive for anything spiky, occasional or unpredictable: a webhook receiver, a scheduled job, an internal tool used forty times a day, a feature nobody has adopted yet.

Containers on always-on infrastructure bill for time whether or not anyone is calling. That is decisive in the other direction: a service with steady traffic through the working day runs far cheaper per request on a container you already pay for than on per-invocation pricing.

The crossover is not where people guess. Measure requests per second at median and at peak, and the duration of each. A service doing sustained meaningful traffic is usually cheaper on containers; anything with a long idle tail is usually cheaper on functions. Managed container platforms that scale to zero sit between the two and are the reason the middle option exists at all.

Cold start tolerance

A function that has not run recently must be initialised before it can serve. How much that matters depends entirely on who is waiting.

An asynchronous consumer processing a queue does not care. A background job does not care. A user-facing API where a fraction of requests take an extra second does care, and that fraction is not small at low traffic because low traffic is exactly what causes cold starts.

The mitigations are real and each costs something. Provisioned or minimum-instance settings keep instances warm, which removes the problem and also removes the scale-to-zero saving that made you choose functions. A smaller deployment package and a runtime with fast startup help considerably; a heavy framework with a large dependency tree is the usual cause of a slow start. Languages differ, and the difference is large enough to change the decision for a latency-sensitive service.

Execution duration and shape

Functions have a maximum execution time, and it is short relative to some real work. A long video transcode, a large data export or a batch job that runs for an hour does not fit, and the workarounds, chaining invocations or checkpointing state, are more complex than just running a container.

The related constraint is the execution model. A function handles one request at a time per instance in most platforms, so a workload doing lots of concurrent input and output waiting is paying for idle compute. Managed container platforms that allow many concurrent requests per instance are considerably cheaper for that shape, and it is one of the strongest arguments for the middle option.

Also consider what the workload needs to keep. Functions are stateless between invocations, with only a temporary filesystem and no reliable local cache. A service that benefits from a warm in-memory cache or a persistent connection pool works badly as a function and well as a container.

Environment control

Functions give you a runtime and a set of supported languages, with container-image packaging available on most platforms now, which removes much of this constraint. Managed container platforms give you an image and a controlled runtime. Kubernetes gives you everything, including the parts you did not want.

The question to ask is what your workload needs that a constrained environment forbids: a specific system library, a background thread, a sidecar, a GPU, a particular kernel feature, a long-lived connection to something that does not tolerate reconnection.

Most web services need none of these, which is why most web services should not be on Kubernetes for technical reasons. Some genuinely do.

What your team can operate

This is the variable teams weigh last and should weigh first.

Kubernetes is a platform with an on-call rotation, an upgrade cadence and a set of failure modes that require specific expertise, as covered in cluster upgrades and several teams on one cluster. It is the right answer when you have enough services to amortise that cost, or when you need what only it provides.

For a team of five shipping one product, a managed container platform is almost always the better answer, and the comparison for one cloud is in Cloud Run or GKE. You get autoscaling including to zero, a deployment model, traffic splitting and no cluster.

Functions suit teams that want to deploy a unit of logic and stop thinking about it, and they suit event-driven glue better than anything else.

The costs that do not appear in the pricing page

  • Local development and testing. Reproducing a function's environment locally is harder than running a container, and teams underestimate the drag this creates on every change.
  • Observability differs. Tracing across functions requires deliberate context propagation, and the short-lived execution model complicates it, which is the queue-hop problem in traces you actually debug with.
  • Lock-in is in the event bindings, not the code. The handler is portable. The triggers, the identity model and the surrounding managed services are not. That is usually acceptable and should be a decision rather than a discovery.
  • Concurrency limits and quotas. Per-account concurrency ceilings are real and are found during a traffic spike.
  • Downstream pressure. A function scaling to a thousand concurrent instances will open a thousand database connections unless something stops it. This is the most common serverless outage we see, and the fix is a connection proxy or a concurrency limit.

The shape that usually wins

Most estates end up mixed, and that is correct rather than a failure of discipline.

Functions for event-driven glue, scheduled jobs, webhook handlers and anything with a long idle tail. A managed container platform for request-serving services with normal requirements, which is most of them. Kubernetes when you have enough services, enough teams or enough specific requirements to justify owning it.

Route the decision per workload rather than picking one platform and forcing everything onto it. The cost of running two platforms is real but smaller than the cost of running the wrong one for half your workloads.

What to do this week

Take your three most expensive services and, for each, write down two numbers: requests per second at the median hour, and the percentage of the day with essentially no traffic. That table answers the platform question for most services without any further analysis, and it usually contradicts at least one decision someone made two years ago. We build it during the architecture phase of a cloud engagement.

ConsultorIA

Want this done on your cloud?

A ten-day read-only assessment is free, and Skyline lets you see your estate on a map before you write to us.

Related articles

Modules people reuse instead of copying

The two failures are a module that wraps one resource and adds nothing, and a module that does everything and nobody dares change. A minimal interface, safe defaults and honest versioning are what separate them.