The "Serverless" Lie: Why Your Billing Meter Spins Faster Than Your CPU
Let's clear the air immediately. "Serverless" is a misnomer that marketing departments love. There are always servers. The difference is whether you control them, or if you're renting them by the millisecond at a 400% markup. I've spent the last decade debugging distributed systems, and the trend of moving everything to AWS Lambda or Azure Functions often results in two things: a massive architectural mess and a bill that makes CFOs cry.
In 2022, with the GDPR landscape shifting under our feet (thanks, Schrems II), relying on US-owned managed clouds for sensitive Norwegian data is a gamble. Datatilsynet isn't known for its sense of humor regarding data transfers. Furthermore, the round-trip latency from Oslo to Frankfurt or Dublin adds up. When you are building for the Nordic market, physics matters.
This article details a battle-tested pattern: running your own FaaS (Function as a Service) platform using OpenFaaS on top of K3s. This gives you the developer experience of serverless with the cost predictability and raw IOPS performance of a dedicated CoolVDS NVMe instance.
The Architecture: Why K3s and OpenFaaS?
Full Kubernetes (K8s) is heavy. If you deploy a standard K8s cluster on a mid-sized VPS, half your RAM is gone before you deploy a single pod. That’s why we use K3s. It’s a compliant, lightweight Kubernetes distribution that strips out legacy cloud provider bloat.
OpenFaaS sits on top. It abstracts away the Kubernetes complexity, allowing you to deploy Docker containers as functions. It’s portable. If you build it here, you can move it anywhere. No vendor lock-in. No proprietary `template.yaml` files that only work in one cloud.
Step 1: The Foundation
First, we need a clean Linux environment. I recommend Debian 11 (Bullseye) or Ubuntu 20.04 LTS. CentOS is effectively dead to me after the stream shift. On a CoolVDS instance, the NVMe storage is critical here. Cold starts in serverless depend entirely on how fast `containerd` can pull and unpack layers from the disk. Spinning rust (HDD) will kill your performance.
Pro Tip: Before installing K3s, disable swap. Kubernetes schedulers despise swap memory and will behave erratically if it's enabled.
sudo swapoff -a
# Comment out swap in /etc/fstab to make it permanent
sudo sed -i '/ swap / s/^/#/' /etc/fstab
Step 2: Deploying K3s
We will use the install script provided by Rancher. It's safe, fast, and sets up systemd services automatically.
curl -sfL https://get.k3s.io | sh -
Once installed, verify the node is ready. It should take less than 30 seconds on a proper VPS.
sudo k3s kubectl get node
You should see `Ready` status. If it hangs, check your firewall. You need port 6443 open for the API server.
Step 3: Installing OpenFaaS with Arkade
Alex Ellis (founder of OpenFaaS) built a tool called `arkade` which acts as a marketplace for K8s apps. It simplifies the Helm chart madness.
# Install arkade
curl -sLS https://get.arkade.dev | sudo sh
# Install OpenFaaS
arkade install openfaas
This command installs the core components: the Gateway, the Provider, and Prometheus for auto-scaling. Yes, OpenFaaS auto-scales your functions based on traffic demand, just like Lambda, but you define the rules.
Configuring the Gateway
By default, the gateway is not exposed to the public internet for security. To access it, we port-forward. For a production environment, you would put Nginx or Traefik in front. Here is a production-ready Nginx snippet to proxy traffic to the OpenFaaS gateway, handling the necessary headers.
upstream openfaas {
server 127.0.0.1:8080;
}
server {
listen 80;
server_name functions.yourdomain.no;
location / {
proxy_pass http://openfaas;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Critical for long-running functions
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
}
The "Cold Start" War Story
Last year, I migrated a Node.js image processing pipeline from a public cloud to a self-hosted cluster. The client was complaining about 2-second cold starts. On the public cloud, we had no control over the underlying hardware. We were sharing disk I/O with thousands of other noisy neighbors.
We moved the workload to a CoolVDS instance with dedicated NVMe. We tuned the kernel to be aggressive about networking. Here is the `sysctl.conf` tuning we applied to handle the bursty traffic of serverless workloads:
# /etc/sysctl.conf
# Allow more connections to be handled simultaneously
net.core.somaxconn = 4096
# Increase the range of ephemeral ports
net.ipv4.ip_local_port_range = 1024 65535
# Reuse specific TCP connections
net.ipv4.tcp_tw_reuse = 1
# Increase backlog for incoming packets
net.core.netdev_max_backlog = 5000
After applying these changes and rebooting (`sysctl -p`), the cold start time dropped to roughly 400ms. The combination of local NVMe speed and kernel tuning made the difference. We weren't fighting for resources anymore.
Creating a Function
Let's deploy a simple function. Install the CLI:
curl -sL https://cli.openfaas.com | sudo sh
Now, scaffold a Node.js function:
faas-cli new --lang node16 my-processor --prefix my-docker-hub-user
This creates a `my-processor.yml` stack file. This is your infrastructure-as-code. It looks like this:
version: 1.0
provider:
name: openfaas
gateway: http://127.0.0.1:8080
functions:
my-processor:
lang: node16
handler: ./my-processor
image: my-docker-hub-user/my-processor:latest
labels:
com.openfaas.scale.min: 1
com.openfaas.scale.max: 20
environment:
write_debug: true
Note: Notice the com.openfaas.scale.min: 1 label. This keeps one replica hot at all times, eliminating cold starts completely for the first concurrent user. This is often expensive in public clouds (