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:
- OpenFaaS Deployment on Kubernetes - start here if you’re confident with deploying Kubernetes
- OpenFaaS step-by-step workshop - start here if Docker and Kubernetes are brand new to you
Install .NET Core 8.0
Head over to the following site and download .NET SDK for your OS:
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 imagefaas-cli push
to transfer the image into a registry the Kubernetes cluster can accessfaas-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.