In this tutorial I’ll show you how to build an ASP.NET Core API that you can deploy to Kubernetes easily using OpenFaaS. We’ll be using steps from the official tutorial provided by the .NET team and explaining any custom steps taken along the way.

Update 2024-04-11 - Migrate all examples to .NET 8

Why is OpenFaaS + ASP.NET Core a good combination?

ASP.NET Core provides a high-performance, lean, and portable runtime that enterprises can use to build robust APIs. The .NET team has worked hard to provide upgrade paths for companies with existing codebases and where that isn’t an option, the familiar language can mean that moving code across from legacy code-bases can be done piecemeal.

 

Kubernetes is the de-facto platform for cloud deployments, but has a significant learning-curve and is hard to get right. OpenFaaS provides a developer-focused abstraction on top of Kubernetes so that you only have to care about building your code and don’t need to spend weeks learning Kubernetes and then keeping up with every change.

What did you just say? “We’re not ready for Serverless yet”? “We’re still trying to learn all the ins and outs of Kubernetes” That’s reason OpenFaaS was created and exists today, to push these details down the stack so that your team doesn’t have to care about them. OpenFaaS provides a cloud native stack for applications developers. Read more about The PLONK Stack

Pre-reqs

We’ll need to install .NET along with some additional tooling for OpenFaaS.

The complete code example is available on GitHub.

Setup OpenFaaS

It’s assumed that you already have Kubernetes and OpenFaaS set up, but if you do not then k3d, KinD, and minikube can make good local options. I like working with remote clusters since they don’t affect the battery life of my laptop, or the CPU/memory of my desktop and are always ready. A good option for a cheap remote cluster may be DigitalOcean.com or Civo.com.

I’ll provide two resources for you to get started:

Install .NET Core 8.0

Head over to the following site and download .NET SDK for your OS:

.NET Core 8.0 download page

This tutorial also works for previous versions if that’s what you’re currently using.

The .NET product automatically reports telemetry, you can turn this off if you wish. I added DOTNET_CLI_TELEMETRY_OPTOUT to my $HOME/.bash_profile file.

Install Docker and faas-cli

  • Get Docker from docker.com

  • Get faas-cli for OpenFaaS

curl -sLFS https://cli.openfaas.com | sudo sh

Alternatively download it from GitHub.

Create a new project with ASP.NET Core

The following steps are based upon the .NET Tutorial - Hello World Microservice

  • Check everything installed correctly
dotnet --info

dotnet --list-sdks
  • Create the microservice

This uses the webapi project type (that’s a REST API endpoint)

dotnet new webapi -o openfaas-api --no-https

The following code shows the contents of the Program.cs that was generated for us::

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

app.MapGet("/weatherforecast", () =>
{
    var forecast =  Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();

app.Run();

record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

You can view it with VSCode at ./openfaas-api/Program.cs

We need to package the service in a container for OpenFaaS to be able to serve traffic, but for now let’s try it out locally.

  • Test the service outside of a container
cd openfaas-api/
dotnet run

Then access the URL given such as http://localhost:5026 - add WeatherForecast to the path to form the URL for the controller: http://localhost:5026/WeatherForecast

Now hit Control + C.

  • Get it into a Docker container

You’ll need Docker installed, so check that it’s present with:

docker --version

We’ll simply use the example from the tutorial, but edit it for the name we picked:

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY openfaas-api.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -c release -o /app

FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "openfaas-api.dll"]

Save this file in the openfaas-api folder

The restore step is done before the COPY . . step, so that Docker can apply caching and avoid running dotnet restore unnecessarily.

The team have also used a multi-stage build so that the Docker image that ships only has the runtime components available and not any SDKs, which bulk out the image.

Now create openfaas-api/.dockerignore, this file tells Docker to ignore any obj or bin files we create when debugging outside of Docker.

Dockerfile
[b|B]in
[O|o]bj

We should also create a openfaas-api/.gitignore file to prevent us committing any build output to git.

Run: cp .dockerignore .gitignore

  • Create an OpenFaaS stack.yml file

Let’s use the OpenFaaS template called dockerfile to define a new template for the project and call it api.

faas-cli new --lang dockerfile api

Now you’ll see a folder generated called api containing a Dockerfile and api.yml

Let’s edit stack.yml and have it point at our Dockerfile we created earlier:

version: 1.0
provider:
  name: openfaas
  gateway: http://127.0.0.1:8080

functions:
  api:
    lang: dockerfile
    handler: ./api
    image: api:latest
  • Let’s rename the file to the default stack.yml
  • And also change the handler folder to point at the existing folder
  • Then add our Docker Hub username or private Docker container registry as a prefix to image:
# The text above remains the same

functions:
  api:
    lang: dockerfile
    handler: ./openfaas-api
    image: alexellis2/api:latest

You should now have:

./stack.yml
./openfaas-api/
./openfaas-api/Dockerfile
./openfaas-api/Program.cs
# etc
  • Attempt to build your OpenFaaS image
faas-cli build

Successfully built 1507c4b8b07b
Successfully tagged alexellis2/api:latest
Image: alexellis2/api:latest built.
[0] < Building api done.
[0] worker done.

You should see the image built successfully, but we need to make a couple of additional tweaks before we can ship this.

  • Add the OpenFaaS watchdog

The OpenFaaS API expects Docker images to conform to a runtime workload contract, we can either implement that in our code by changing the HTTP port and adding a health-check, or by using the OpenFaaS watchdog component.

FROM  ghcr.io/openfaas/of-watchdog:0.9.15 as watchdog

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY openfaas-api.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -c release -o /app

FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /app .
COPY --from=watchdog /fwatchdog /usr/bin/fwatchdog
RUN chmod +x /usr/bin/fwatchdog

ENV ASPNETCORE_HTTP_PORTS=80
ENV fprocess="dotnet openfaas-api.dll"
ENV upstream_url="http://127.0.0.1:80"
ENV mode="http"

ENTRYPOINT ["fwatchdog"]

The changes add the fwatchdog process as the entrypoint to the container. It receives all incoming HTTP traffic and forwards it to the port exposed by .NET Core on TCP/80, it also adds a health-check for Kubernetes and metrics / instrumentation with Prometheus.

See also: OpenFaaS workloads

You can run the function image locally as a test before deploying it to OpenFaaS.

faas-cli local-run

Then access the site as before: http://localhost:8080/WeatherForecast

Hit Control + C when you’re done.

  • Deploy on Kubernetes with OpenFaaS

Now we can deploy to Kubernetes using OpenFaaS and faas-cli.

export OPENFAAS_URL="" # Set a remote cluster if you have one available

faas-cli up

The faas-cli up command runs the following:

  • faas-cli build to output a local Docker image
  • faas-cli push to transfer the image into a registry the Kubernetes cluster can access
  • faas-cli deploy the OpenFaaS API will create Kubernetes objects within the cluster and start the Pod

The name of the endpoint is api, so we can now access the endpoint through a set URL:

# Synchronous

$OPENFAAS_URL/function/api

# Asynchronous / queued

$OPENFAAS_URL/async-function/api

You can view logs with faas-cli logs api

And check any other status and invocation metrics with faas-cli describe logs.

  • View the OpenFaaS UI

You can view your endpoint in the OpenFaaS UI, but note that invoke will go to the / route, for which we have no listener at present.

Taking things further

  • Templates for C#

We used a dockerfile template which uses your provided Dockerfile. I picked that option to show you how easy it is to adapt any existing code that you may have. Part of the value OpenFaaS can deliver is that it can abstract away the Dockerfile and any boilerplate code you may normally have to write.

We can do the same for C# and there’s at least three templates you may be interested in which are in the function store:

faas-cli template store list |grep csharp

csharp                   openfaas           Classic C# template
csharp-httprequest       distantcam         C# HTTP template
csharp-kestrel           burtonr            C# Kestrel HTTP template

You can think of faas-cli new as performing the same job as dotnet new, but the tool can hide away the Dockerfile and gory details if you prefer to work that way.

The csharp-kestrel template looks like this:

using System;
using System.Threading.Tasks;
 
namespace Function
{
    public class FunctionHandler
    {
        public Task<string> Handle(string input)
        {
            return Task.FromResult($"Hello! Your input was {input}");
        }
    }
}

It’s hard to argue that the code above isn’t easier to maintain than the full instructions we went through for the ASP.NET Core example from the .NET team, however I believe that both are valid use-cases for OpenFaaS and Kubernetes users.

  • Templates for other languages

Here’s an example of the golang-middleware template which reduces an entire Golang microservice down to a couple of lines:

faas-cli template store pull golang-middleware

faas-cli new --lang golang-middleware logger

Here’s what we got:

logger.yml
logger/handler.go

And the contents of the logger:

package function

import (
        "fmt"
        "net/http"
)

func Handle(w http.ResponseWriter, r *http.Request) {
    log.Println(w.Header)
}
  • Find out details about templates

You can run faas-cli template store describe <NAME> to discover the GitHub repo and author behind each template.

The templates go through basic quality control upon submission to ensure a small Docker image, a non-root user and that each README file has detailed usage examples.

faas-cli template store describe csharp-kestrel

Name:              csharp-kestrel
Platform:          x86_64
Language:          C#
Source:            burtonr
Description:       C# Kestrel HTTP template
Repository:        https://github.com/burtonr/csharp-kestrel-template
Official Template: false
  • Auto-scaling

Functions can be autoscaled horizontally or scaled to zero with the OpenFaaS Standard autoscaler

  • Secrets and API keys

You can learn how to securely manage APIs and secrets using the following blog post: Configure your OpenFaaS functions for staging and production

  • Versioning of .NET runtimes

You may find that for various reasons you need to support .NET Core 2.0, 2.1, 2.2 and even 3.1 all at the same time.

That’s fine, you can create different templates or you can just specify the runtime for each service in the Dockerfile we created earlier.

  • 12-factor configuration

You can apply 12-factor configuration through the environment section of your OpenFaaS stack.yml file.

See also: Configure your OpenFaaS functions for staging and production

Wrapping up

In a short period of time we were able to deploy an ASP.NET Core application using .NET 8.0 or 9.0 to Kubernetes, have it scale out and build into an immutable Docker image. OpenFaaS made this task much simpler than it would have been if we’d tried to program directly against Kubernetes.

If you aren’t quite convinced yet, then watch my KubeCon talk on the PLONK Stack that combines OpenFaaS with Kubernetes and several other CNCF projects like Prometheus and NATS to create a platform for application developers.

Finally, feel free to reach out if you need help with any cloud native problems you’re trying to solve, or if you could use an external perspective on what you’re building from OpenFaaS Ltd: alex@openfaas.com.

Alex Ellis

Founder of @openfaas.