Console Login

Serverless Without the Handcuffs: Self-Hosted FaaS Patterns on NVMe VPS

Serverless Without the Handcuffs: Self-Hosted FaaS Patterns on NVMe VPS

Let’s get one thing straight: "Serverless" is a marketing term. There are always servers. The only variable is whether you control them, or if you’re paying a premium for a hyperscaler to hide them from you—along with the logs you desperately need when production crashes at 2 AM.

I have managed infrastructure for fintech startups across the Nordics for over a decade. I’ve seen the monthly AWS Lambda bill that makes a CFO cry. I’ve watched latency spikes ruin user experience because a "hot" function went cold in a data center in Frankfurt, adding 40ms to a request originating in Oslo.

If you are building for the Norwegian market, relying entirely on public cloud FaaS (Function as a Service) is a gamble on latency and data sovereignty. With the tightening grip of Datatilsynet (The Norwegian Data Protection Authority) and the Schrems II ruling still echoing in legal departments, knowing exactly where your data is processed isn't just nice—it's mandatory.

Today, we are going to build a Serverless architecture that you actually own. We will use the Event-Driven Pattern, but we will deploy it on high-performance KVM instances right here in Norway. Low latency, fixed costs, and zero vendor lock-in.

The Architecture: Why Self-Hosted FaaS?

The standard pattern for serverless is simple: Trigger → Controller → Ephemeral Container → Database. The problem with AWS or Azure is the "Black Box" controller. You can't tune the scheduler. You can't optimize the disk I/O for image pulling.

By moving this stack to a robust VPS provider like CoolVDS, we gain access to:

  • NVMe Storage: Crucial for container start-up times. Public clouds often throttle IOPS on small instances.
  • Kernel Tuning: We can adjust sysctl parameters to handle high connection churn.
  • Predictable Pricing: A fixed monthly rate for a CoolVDS instance beats a variable bill that spikes with a DDoS attack.

Our stack for today (valid as of March 2024): K3s (Lightweight Kubernetes) + OpenFaaS.

Phase 1: The Foundation (System Tuning)

Before installing any orchestration tools, we must prep the OS. Most VPS images come with generic settings. We need to optimize for high concurrency.

SSH into your CoolVDS instance (Ubuntu 22.04 LTS is recommended):

# optimize-sysctl.sh
cat <> /etc/sysctl.conf
# Allow more connections
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 8192

# Reuse closed connections faster
net.ipv4.tcp_tw_reuse = 1

# Increase file descriptors for high container density
fs.file-max = 100000
fs.inotify.max_user_watches = 524288
fs.inotify.max_user_instances = 512
EOF

sysctl -p

These settings are critical. If you ignore fs.inotify limits, your FaaS platform will crash once you deploy more than 20 functions. I learned this the hard way during a Black Friday event.

Phase 2: The Orchestrator

We don't need the bloat of full K8s. K3s is production-ready, CNCF-certified, and perfect for a single-node or small cluster VPS setup.

curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--disable traefik" sh -

Pro Tip: I disabled the default Traefik because we want tighter control over our Ingress later, or we might want to use the OpenFaaS gateway directly. For this guide, we will use arkade to install OpenFaaS, which simplifies the manifest management.

Phase 3: Deploying the Serverless Engine

We use OpenFaaS because it decouples the "Serverless" experience from the cloud provider. It runs anywhere.

# Install arkade (tool by Alex Ellis)
curl -sLS https://get.arkade.dev | sudo sh

# Install OpenFaaS on K3s
arkade install openfaas

# Retrieve your password
kubectl -n openfaas get secret basic-auth -o jsonpath="{.data.basic-auth-password}" | base64 --decode; echo

At this stage, you have a functional FaaS platform running on a CoolVDS instance in Oslo. The latency from a user in Trondheim or Bergen to this instance is typically under 15ms. Compare that to routing traffic to Ireland or Stockholm.

The Code: An Async Python Worker

Let's look at a real-world pattern: Image Resizing. This is the "Hello World" of heavy processing. We want to accept a payload, acknowledge it immediately, and process it in the background.

Create a function:

faas-cli new --lang python3-http resize-worker

Here is the handler logic. Note how we handle the context. In a VPS environment, we don't have S3 built-in, so we might use MinIO (self-hosted S3) or local NVMe storage for temporary processing.

# resize-worker/handler.py
import logging
from PIL import Image
import io
import os

def handle(event, context):
    # Strict type checking
    if event.method != 'POST':
        return {
            "statusCode": 405,
            "body": "Method not allowed"
        }

    try:
        # Simulate processing logic
        logging.info("Received image payload")
        
        # In a real scenario, 'event.body' contains bytes
        # We leverage the raw speed of CoolVDS NVMe here
        # writing to /tmp is incredibly fast on these instances
        
        with open("/tmp/temp_image.jpg", "wb") as f:
             f.write(event.body)

        # Mock resizing to prove compute capability
        # This would spike CPU, which CoolVDS handles via KVM isolation
        # ensuring no steal time from neighbors.
        
        return {
            "statusCode": 200,
            "body": "Image processed successfully",
            "headers": {"Content-Type": "text/plain"}
        }
        
    except Exception as e:
        logging.error(e)
        return {"statusCode": 500, "body": str(e)}

Configuration for Performance

The magic happens in the stack.yml. This is where you define the constraints. Unlike AWS Lambda where you pick "Memory" and hope for CPU, here we define requests and limits explicitly.

version: 1.0
provider:
  name: openfaas
  gateway: http://127.0.0.1:8080
functions:
  resize-worker:
    lang: python3-http
    handler: ./resize-worker
    image: myrepo/resize-worker:latest
    environment:
      write_debug: true
      read_timeout: 10s
      write_timeout: 10s
    # These annotations are crucial for autoscaling behavior
    annotations:
      com.openfaas.scale.min: 1
      com.openfaas.scale.max: 20
      com.openfaas.scale.factor: 20
    # Hard limits prevent OOM kills impacting the host
    limits:
      memory: 256Mi
      cpu: 500m
Pro Tip: Set com.openfaas.scale.min: 1. This prevents the "Cold Start" problem entirely. Because you are paying a flat rate for your CoolVDS VPS, keeping one container warm costs you nothing extra, unlike cloud providers that charge for idle execution time or provisioned concurrency.

Security: The Nginx Gatekeeper

Never expose the OpenFaaS gateway directly. Use Nginx as a reverse proxy to handle SSL termination and basic DDoS protection. This fits the "Pragmatic CTO" approach—security at the edge.

# /etc/nginx/conf.d/openfaas.conf
upstream openfaas {
    server 127.0.0.1:8080;
}

server {
    listen 80;
    server_name faas.your-domain.no;

    location / {
        proxy_pass http://openfaas;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        
        # Rate limiting to protect backend
        limit_req zone=one burst=20 nodelay;
        
        # Buffering off for streaming responses
        proxy_buffering off;
    }
}

Why This Stack Wins in 2024

We are seeing a shift. The initial excitement of "NoOps" is fading as the bills arrive. Engineering teams in Oslo and Stockholm are realizing that maintaining a K3s cluster on stable virtual machines is not difficult, especially with modern tooling.

  1. Data Residency: You can point to the server location in Norway. That keeps compliance officers happy.
  2. Hardware Control: When your function needs disk I/O, you get raw NVMe speeds. On CoolVDS, we don't oversell storage I/O. Your read/write speeds are yours.
  3. Cost Efficiency: Run 50 microservices on a single VPS for the price of 200 NOK/month. Try doing that with Azure Functions consumption plans once you hit moderate traffic.

Serverless is a pattern, not a product. It allows developers to focus on code. But someone still has to manage the infrastructure. By taking ownership of that layer, you trade a small amount of operational work for massive gains in performance, cost control, and privacy.

Don't let latency dictate your architecture. Deploy a high-performance environment on CoolVDS and take back control of your stack.