Recently I've been having a lot of discussions with teams wanting to move towards a cloud-based microservices architecture. And inevitably the question arises whether the best choice would be to go with containers, or a serverless "Functions as a Service" (FaaS) approach.
To keep this discussion from becoming too abstract, let's imagine we're planning to host our application in Azure. Should we create an AKS (Azure Kubernetes Service) cluster and implement each microservice as a container? Or should we use Azure Functions, and implement each microservice as a Function App?
And to keep this article from becoming too long, I'm going to restrict myself to making just a few key points in favour of both approaches.
It's not either/or
First, it's important to point out that hybrid architectures are possible. There is no rule preventing you from using both AKS and Azure Functions, playing to the strengths of each platform. And if you're migrating from a monolith, you may well be running alongside some legacy Virtual Machines anyway.
Also, if you like the Azure Functions programming model, it's quite possible to host Azure Functions in a container. And if you like the consumption-based pricing model and elastic scale associated with serverless, then technologies like Azure Container Instances can be combined with AKS to essentially give you serverless containers.
And while serverless essentially forces you in the direction of PaaS for your databases, event brokers, identity providers etc, you can do exactly the same with containers - there's no reason why they can't reach out to PaaS services for these concerns rather than containerizing everything.
A few strengths of containers
What factors might cause us to favour containers?
Containers are particularly good for migrating legacy services. If you've already implemented a batch process, or web API, then getting that running in a container is much easier than rewriting it for serverless.
Containers make it trivial for us to adopt third party dependencies that aren't easily available (or cost-effective) as PaaS. There's a wealth of open source containerized services you can easily make use of such as Redis, RabbitMQ, MongoDb, and Elasticsearch. You have freedom choose when and if it makes sense to switch to PaaS versions of these services (one nice pattern is to use containerized databases for dev/test environments, but a PaaS database like Azure SQL Database in production).
Containers have a particularly good story for local development. If I have 20 microservices, I can bundle them all into a Docker compose file, and start them all up in an instant. With serverless, you need to come up with your own strategy for how developers can test a microservice in the context of the overall application.
A containerized approach can also simplify the security story. With serverless, you're typically exposing each microservice with a HTTP endpoint publicly on the internet. That means each service could potentially be attacked, and great care must be taken to ensure only trusted clients can call each service. With a Kubernetes cluster, you don't need to expose all your microservices outside the cluster - only certain services are exposed by an ingress controller.
A few strengths of serverless
What are some key strengths of serverless platforms like Azure Functions?
Serverless promotes rapid development by providing a simplified programming model that integrates easily with a selection of external services. For example, with Azure Functions, makes it trivial to connect to many Azure services such as Azure Service Bus, Cosmos DB and Key Vault.
Serverless encourages an event-driven nanoservice model. Although containers place no constraints on what programming models you use, they make it easy to perpetuate older development paradigms involving large heavyweight services. Serverless platforms strongly push us in the direction of event-driven approaches which are inherently more scalable, and promote light-weight small "nanoservices" that can be easily discarded and rewritten to adapt to changing business requirements (a key driver behind the idea of "microservices").
Serverless can offer extremely low cost systems, by supporting a "scale to zero" approach. This is extremely compelling for startups, who want to keep their initial costs to a minimum during a proof of concept phase, and also allows lots of dev/test service deployments in the cloud without worrying about cost. By contrast, with containers, you would almost always have a core number of nodes in your cluster that were always running (so with containers you might control cost either by running locally, or by sharing a Kubernetes cluster).
Serverless also excels in supporting rapid scale out. Azure Functions very quickly scales from 0 to dozens of servers under heavy load, and you're still only paying for the time your functions are actually running. Achieving this kind of scale out is more work to configure with containerized platforms, but on the flip side, with container orchestrators you will have much more control over the exact rules governing scale out.
Both containerized and serverless are excellent approaches to building microservices, and are constantly borrowing each other's best ideas, so the difference isn't huge (and maybe this question won't even be meaningful in 5-10 years).
Which one would I pick? Well, I think for a more "startupy" application, where it's greenfield development with a small number of developers trying to prove out a business idea, I think serverless really shines, whereas for more "enterprisey" applications, with a lot more components, development teams and maybe some legacy components involved, I think containerized approaches are more promising. In fact, most systems I work on are essentially "hybrid" - combining aspects of serverless, containers and plain old virtual machines.