Serverless functions are of course not serverless. They run on servers, but are called serverless because you don't need to think about the server as a developer. You just write your function and deploy it, and the cloud provider takes care of the rest.
Another interesting attribute of serverless functions is that they are generally billed per-invocation, so you're paying only for the time the function is running. This is in contrast to a traditional server where you pay for the server to be running all the time, even if it's not doing anything. This isn't always an advantage though, in fact the more your functions are called, the less affordable this model becomes.
Serverless functions also have the property that because they are invoked "just-in-time", they typically have a startup delay. This is called a "cold start" and can be a problem for some applications.
CNDI provides an authoring and deployment experience just like other Functions-as-a-Service runtimes, except that it runs in your own Kubernetes cluster. You don't have to worry about cold starts or costs that scale poorly with usage.
Like all great tools, Functions-as-a-Service and CNDI Functions are not the right tool for every situation. From this point let's set some assumptions:
- You want to write JavaScript or TypeScript functions.
- You want to write stateless functions that are invoked in response to HTTP Events.
- You're fundamentally a cool and open minded individual.
Now, let's talk options.
If you are looking for the proprietary Functions-as-a-Service offering built-in to your cloud, your choices are AWS Lambda, Google Cloud Functions, or Azure Functions. Even though these are built-in to the cloud, they are still surprisingly lacking in developer experience and each have vendor-specific non-portable APIs.
If you are able to use an up-and-coming cloud provider, I'd recommend Deno Deploy. It's a great platform for writing Functions that are invoked in response to HTTP Events, and it's built on open standards. Pricing is per-invocation, but you get a generous free tier without a credit card (for now!).
If you like the programming model of Deno Functions but you would rather self-host, CNDI Functions is the right tool for you. You can run it in your own Kubernetes cluster in your own cloud, and you don't have to worry about cold-starts or per-invocation pricing.
Sincerely, I would start with option #2. because you'll quickly learn just how elegant Functions can be when they're at their best. If you give it a try and you're a fan, then consider #3.!
With all that said, let's dive into what makes CNDI Functions special.
The CNDI Functions Experience
Getting started with CNDI Functions is similar to getting started with any other CNDI Template:
# eg. cndi create johnstonmatt/my-fns-cluster --template fns
cndi create <github-owner>/<new-github-repo> -t fns
This will prompt you interactively for all required info starting with the
basics like which Kubernetes distribution you are targeting (eg. aws/eks
),
then moving on to asking you about the domain names to use for your new cluster
services.
Using all that info cndi
will create a new repository on GitHub and generate
some source code in the folder ./<new-github-repo>
. Inside you'll see a folder
./functions
, and a couple demo functions.
Your cndi_config.yaml
will include a block for configuring CNDI Functions:
# ./cndi_config.yaml
cluster_manifests: { ... }
applications: { ... }
infrastructure:
cndi:
functions:
hostname: fns.example.com
If you configured a hostname for your Functions when prompted earlier, it will
be shown in your cndi_config.yaml
as infrastructure.cndi.functions.hostname
.
This hostname's DNS records will be automatically updated as well if you are
using CNDI's ExternalDNS and TLS features.
Every function you write should be in your repo as ./functions/my-fn/index.ts
and should call Deno.serve
to handle incoming requests. When you call
cndi overwrite
and push your cndi project to git, your function will be served
at https://fns.example.com/my-fn
.
// ./functions/cowsay/index.ts
import { say } from "npm:cowsay/build/cowsay.es.js";
Deno.serve((req) => {
const { pathname } = new URL(req.url);
// accessible at https://fns.example.com/cowsay?message=hello
if (pathname !== "/cowsay") {
return new Response("Not Found", {
status: 404,
});
}
const query = new URL(req.url).searchParams;
const message = query.get("message");
if (!message || typeof message !== "string" || message.length === 0) {
return new Response("Please provide a message", {
status: 400,
});
} else {
return new Response(say({ text: message }), {
headers: {
"Content-Type": "text/plain",
},
});
}
});
Secrets and Environment Variables
You can also use the fns-env-secret
in cndi_config.yaml
to attach Secret
environment variables to be read by your Functions. The
./functions/hello-world/index.ts
function below reads the GREETING
environment variable.
# ./cndi_config.yaml
cluster_manifests:
fns-env-secret:
kind: Secret
metadata:
name: fns-env-secret
namespace: fns
stringData:
GREETING: $cndi_on_ow.seal_secret_from_env_var(DEFAULT_GREETING)
// ./functions/greet/index.ts
import { STATUS_CODE } from "jsr:@std/http";
const greeting = Deno.env.get("GREETING") || "Hello";
Deno.serve((req) => {
const { pathname } = new URL(req.url);
if (!pathname.startsWith("/greet")) {
return new Response("Not Found", {
status: STATUS_CODE.NotFound,
});
}
const who = pathname.split("/")?.[2] || "world";
const text = `${greeting} ${who}!`;
const result = {
greeting: text,
};
return new Response(JSON.stringify(result), {
headers: {
"Content-Type": "application/json",
},
status: STATUS_CODE.OK,
});
});
This ability to provide Secret values can of course be used for sensitive information too, like database credentials, and CNDI will ensure they remain encrypted by leveraging our Secrets Management features.
CNDI Functions allows you to easily spin up a serverless function runtime based on modern Typescript and web standards, and all you need to do is write the code and push to git.
Big shoutouts to the MIT licensed supabase/edge-runtime which powers much of the CNDI Functions project under the hood.