End the Obsession with Servers
By: Matt Butcher Despite their bloated code, security posture, and wasteful resource usage, we as developers remain fully bought into the idea that to build services, we must build long-running always-on servers. Serverless v1, as a programming pattern, may have been too clumsy and slow. But the new generation of serverless is powerful enough to entice us away from the socket server paradigm. A Half-hearted Turn The microservice design pattern calls for services to be “disposable” in the sense that an instance of the service can be killed off and another instance started without losing any important information. Statelessness is not just a virtue, but requirement of the pattern. And yet… In the ever-popular 12-Factor Manifesto, the sixth factor states: Twelve-factor processes are stateless and share-nothing. Any data that needs to persist must be stored in a stateful backing service, typically a database. The pattern is clear: Do not store state internally from one request to another. That makes services stateless. And yet… PHP has been a top 5 programming language, according to RedMonk, since at least 2012. As a runtime, a fresh PHP interpreter is started for each inbound request, and is shut down as soon as the response is sent. And yet… The UNIX design philosophy, which is at the heart not just of operating systems but of the Internet at large, says Do (only) one thing well. From sed and awk to sendmail and sshd, this simple philosophy built the computing foundation we use across the cloud today. And yet… And yet… for some reason we have dug in our heels on a peculiar dogma: That every service we supply needs to be wrapped in its own socket server. Maybe that was a good idea once, but the time has come to challenge that dogma. Waste, Insecurity, and Difficulty According to a DataDog report, the typical containerized socket server sits 80% idle. But it still consumes (or reserves, in Kubernetes’ case) most of the resources it uses when 90%+ utilized. In cloud cost terms, you are paying to for your app to do nothing. A typical Kubernetes node can only run 30 containers because it reserves so many resources (even when idle) that a node can’t run any more… even though most of the time they are idle. The problem is not limited waste, but includes security. Coming in with hundreds of thousands of lines of code of external dependencies, servers are a mess of imports and security issues. From socket libraries to SSL implementations, the very fact that you are running a network daemon means you are inheriting substantial attack surface and significant exposure to library bugs and hacks. A DataDog survey published just last week points out that 15% of in-production services have known but unpatched vulerabilities, and draws a strong correlation between the size of a release and its likelihood to contain security issues. Even while we studiously avoid storing state locally, even as we design our microservice to run as one among many replicas, we still hold fast to the notion that all the logic must be wrapped up in a long-running server process that puts us, as developers, right at the heart of an operational concern that should not be ours: Is the server running? Enshrouding our business logic in long-running servers has many negative implications. And yet… here we are with PHP as a top language, 12-factor still an influential manifesto, the microservice pattern still being the favored child… and inexplicably we continue to wrap all of these things in long running server processes. And we do this uncritically. We simply don’t ask why. We just do it. “Nobody gets fired for buying IBM.” Nobody gets questioned for taking a few hundred lines of business logic and wrapping them in tens of thousands of lines of other people’s server framework. The best bet is to externalize the server—to remove it from the business logic altogether. Let something else manage the sockets, the packets, the DNS, the TLS. Let something else manage restarting and health checking and stream optimization. Reduce your code to just the business logic. Solve the problem at hand, reduce potential vulnerabilities, and (in so doing) delegate all of the management to software whose job is to manage. The name for this pattern is Serverless Functions. Serverless v2 Serverless functions have been a hit. Amazon claimed a few years ago that they handle 10 trillion Lambda functions a month. But despite their popularity, they have some major drawbacks. Back in the first generation of serverless, the preeminent serverless platforms (AWS Lambda, Azure Functions, Google Cloud Functions) came with strong vendor lock-in. And even the open source equivalents like KNative and OpenWhisk were far too slow to sit on the front line facing actual users with actual performance expectations. People don’t want to wait an extra half-second for every single network request. Serverless v1 simply wasn’t up to the performance

By: Matt Butcher
Despite their bloated code, security posture, and wasteful resource usage, we as developers remain fully bought into the idea that to build services, we must build long-running always-on servers. Serverless v1, as a programming pattern, may have been too clumsy and slow. But the new generation of serverless is powerful enough to entice us away from the socket server paradigm.
A Half-hearted Turn
The microservice design pattern calls for services to be “disposable” in the sense that an instance of the service can be killed off and another instance started without losing any important information. Statelessness is not just a virtue, but requirement of the pattern. And yet…
In the ever-popular 12-Factor Manifesto, the sixth factor states:
Twelve-factor processes are stateless and share-nothing. Any data that needs to persist must be stored in a stateful backing service, typically a database.
The pattern is clear: Do not store state internally from one request to another. That makes services stateless. And yet…
PHP has been a top 5 programming language, according to RedMonk, since at least 2012. As a runtime, a fresh PHP interpreter is started for each inbound request, and is shut down as soon as the response is sent. And yet…
The UNIX design philosophy, which is at the heart not just of operating systems but of the Internet at large, says Do (only) one thing well. From sed
and awk
to sendmail
and sshd
, this simple philosophy built the computing foundation we use across the cloud today. And yet…
And yet… for some reason we have dug in our heels on a peculiar dogma: That every service we supply needs to be wrapped in its own socket server. Maybe that was a good idea once, but the time has come to challenge that dogma.
Waste, Insecurity, and Difficulty
According to a DataDog report, the typical containerized socket server sits 80% idle. But it still consumes (or reserves, in Kubernetes’ case) most of the resources it uses when 90%+ utilized. In cloud cost terms, you are paying to for your app to do nothing. A typical Kubernetes node can only run 30 containers because it reserves so many resources (even when idle) that a node can’t run any more… even though most of the time they are idle.
The problem is not limited waste, but includes security. Coming in with hundreds of thousands of lines of code of external dependencies, servers are a mess of imports and security issues. From socket libraries to SSL implementations, the very fact that you are running a network daemon means you are inheriting substantial attack surface and significant exposure to library bugs and hacks. A DataDog survey published just last week points out that 15% of in-production services have known but unpatched vulerabilities, and draws a strong correlation between the size of a release and its likelihood to contain security issues.
Even while we studiously avoid storing state locally, even as we design our microservice to run as one among many replicas, we still hold fast to the notion that all the logic must be wrapped up in a long-running server process that puts us, as developers, right at the heart of an operational concern that should not be ours: Is the server running? Enshrouding our business logic in long-running servers has many negative implications.
And yet… here we are with PHP as a top language, 12-factor still an influential manifesto, the microservice pattern still being the favored child… and inexplicably we continue to wrap all of these things in long running server processes. And we do this uncritically. We simply don’t ask why. We just do it. “Nobody gets fired for buying IBM.” Nobody gets questioned for taking a few hundred lines of business logic and wrapping them in tens of thousands of lines of other people’s server framework.
The best bet is to externalize the server—to remove it from the business logic altogether. Let something else manage the sockets, the packets, the DNS, the TLS. Let something else manage restarting and health checking and stream optimization. Reduce your code to just the business logic. Solve the problem at hand, reduce potential vulnerabilities, and (in so doing) delegate all of the management to software whose job is to manage.
The name for this pattern is Serverless Functions.
Serverless v2
Serverless functions have been a hit. Amazon claimed a few years ago that they handle 10 trillion Lambda functions a month. But despite their popularity, they have some major drawbacks.
Back in the first generation of serverless, the preeminent serverless platforms (AWS Lambda, Azure Functions, Google Cloud Functions) came with strong vendor lock-in. And even the open source equivalents like KNative and OpenWhisk were far too slow to sit on the front line facing actual users with actual performance expectations. People don’t want to wait an extra half-second for every single network request. Serverless v1 simply wasn’t up to the performance requirements.
But those days, and their antiquated infrastructure, are passing. Swapping out the clunky VM or container-based runtimes with something more nimble like WebAsembly means that cold starts go down from 200-500 milliseconds to under one millisecond. Code weight is reduced substantially, with the most sensitive tasks (networking, TLS, etc.) externalized. The number of library dependencies (and hence vulnerability alerts) drops down commensurately. And instead of running just a few long-running containers on a single Kubernetes node, we’ve been able to pack upwards of 3,000 serverless-style applications on the exact same profile.
So why continue in the dogma that every microservice needs a full-on socket server? Why shoulder more burden, both security-wise and operationally, than we need to? Simplicity is a virtue. We should embrace it.