Containers and Functions are “so hot right now”, but what exactly is the difference between the two? I’ve been having this conversation a lot lately and it’s clear there’s no easy answer. In this post, I’ll attempt an answer, and at least we can move the conversation forward so that there’s a better understanding of how containers and functions interrelate. Let’s start with some broad definitions:
Containers: From Docker’s website: “A container image is a lightweight, stand-alone, executable package of a piece of software that includes everything needed to run it: code, runtime, system tools, system libraries, settings.”
Functions: Functions are blocks of code, ideally small and single-purpose. In the context of serverless, they are coordinated and scheduled by a Functions-as-a-service (FaaS) platform such as vendor services AWS Lambda, Azure Functions, and Google Cloud Functions, or open source frameworks such as the Fn Project (our project), OpenWhisk, and OpenFaas.
Serverless: A category of software design where the abstraction layer for developers is at the application tier, above the operating system, infrastructure, and cloud IaaS API’s. In other words, developers never think about infrastructure.
The earliest FaaS entrants to the game (Lambda, Azure, Google, OpenWhisk) give you an API or GUI to upload code. There’s no container exposed, and thus, the user is bound by pre-built environments and less-than-ideal dependency management and build systems. A colorful Twitter thread initiated by Kelsey Hightower highlights the current state of thinking of this model.
The newer generation of frameworks including Fn (our project) are “container native”, meaning the Docker container is a first class citizen and any image can be used as the Function itself.
This is the crux of the confusion — if the function is the container, and container is a function, are they actually different? Furthermore what is the value of a FaaS system if you can just run a container on a platform like Cloud Foundry or Kubernetes? Dr Max of IBM ruminated on this point by raising the question to a panel I was on at DockerCon Europe. That’s when I decided to try and answer the question in longer format here.
Function < Container
A container can contain anything, from a webservice, to a million LOC monolith, to a complete DBMS system, to the data tier that DBMS writes into. These containers can live forever, scale horizontally (or not), take 20 minutes to start, 20 days to run, and on and on.
Contrast this to a Function, which typically has a set of known characteristics:
- Short Running: Functions live for a short amount of time and then the process ends. “Short” is of course subjective, but let’s assume we’re talking 5 minutes or even less.
- Ephemeral: The outer container running the function may only live for a single invocation of the function.
- Stateless: The function does not inherently hold any state. All state must be pushed to structured or unstructured storage.
- Invoked: Functions are invoked by defined events over either HTTP, TCP, or some other protocol.
- Single Purpose: Functions have a clear and defined purpose with minimal API surface area, meaning they take a reasonably short input, and provide a reasonably short output.
- Self-contained: A function can run and serve its purpose by itself. Although a function is likely just part of a broader grouping of functions (eg. many functions create an API, a sequence of functions create a “flow”), by itself, it can run independently as long as it gets its required input. [thanks Tobias Kunze for name suggestion]
Now if we take our function with these characteristics and package it into a container, then we can now think and reason about that container differently, giving rise to a powerful concept — a new atomic unit of compute.
Atomic Unit of Compute
At its core, a function packaged in a container gives us a new “atomic unit of computing”. We can think and talk about our software in terms of building blocks we understand such as an image processing function, sentiment analysis function, and so on. We can share and extend functions as well as build composite applications. Establishing the atomic unit gives us the capability to construct the software equivalents of molecules and complex organisms.
And not only do we get this atomic unit of compute, but we also inherit all the benefits of the container itself such as standardized packaging format, runtimes, storage, transport, etc. Furthermore the platforms and tooling can be specialized and purpose-built for this new atomic unit of compute. This is what is giving rise to a thriving ecosystem around Functions and serverless computing, and this is what the container metaphor represents in the first place.
Now let’s apply this atomic unit to a few concepts important to software development.
Functions as Unit of Scaling
Most software is built with the intention of scaling to many users. This used to be scaling up (larger machines), now it’s scaling out (more machines), and we can do this at much finer grained precision than machines, we can scale up/out at a precision of a single process. No function invocations, no containers needed, no machines needed, repurpose them to perform some other operation. Lots of function invocations, spin up containers utilizing free resources. This is a simplified view, but the characteristics of functions coupled with the universal packaging/runtime of containers gives us a more effective way to scale our applications.
Functions as Unit of Billing
The billing model of “serverless” is an exciting advancement. Instead of running servers or VM’s for days, months, or years, we can now effectively pay in a much more granular way — the time consumed by a particular function, sometimes milliseconds. This enables cost reduction of lightly used apps, cheaper iterations on testing out app features in market, and on and on.
Functions as Unit of Design
Design is the best word I could come up with to describe what I view as “the next coming of SOA”. Functions packaged in containers with their own contract should be inherently shareable, so that composite applications can be built and reused. This also requires function orchestration tools, which is the problem we’re aiming to solve with Fn Flow.
OK I’ve done what I’ve been meaning to do and put out a post on containers, functions, serverless, and atomic units. I hope this helps move us from debate on differences to embracing the combination. I also feel like this topic has been exhausted, but then again I live in a bubble of people talking about serverless computing.
I’ll continue to talk about serverless at a high level, but start to focus more and more on how to bring the architecture and its benefits to the early majority, as well as many more types of applications, even complex ones. We’re slightly stuck in a land of triggers, understandably given the event-driven nature, but let’s evolve.