BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles WebAssembly and Containers: Orchestrating Distributed Architectures with .NET Aspire

WebAssembly and Containers: Orchestrating Distributed Architectures with .NET Aspire

Key Takeaways

  • .NET Aspire is designed to simplify distributed application development by allowing developers to define application architecture using C#.
  • While .NET Aspire is not intended to replace production-level systems like Kubernetes, it offers a powerful local orchestration toolset that enhances the development environment.
  • .NET Aspire supports popular container runtimes such as Docker Desktop and Podman, enabling developers to run different application components and dependencies directly on their local machine.
  • The introduction of Fermyon.Aspire.Spin within .NET Aspire allows the addition of serverless WebAssembly applications to the distributed architecture. Spin supports a variety of programming languages for creating WebAssembly applications.
  • The .NET Aspire Dashboard offers crucial insights into the behaviour of the distributed application at runtime. It provides access to structured application logs, metrics, and environment variables.

Running, composing, and debugging distributed applications on the local developer machine can be difficult, error-prone, and time-intensive. Those daily tasks could be dramatically simplified thanks to .NET Aspire.

In this article, we will quickly dive into .NET Aspire and illustrate how you can orchestrate next-generation distributed (read cloud-native) applications that consist of containers, WebAssembly workloads, and their dependencies.

What is .NET Aspire

.NET Aspire is an opinionated stack allowing you to orchestrate distributed applications consisting of different, interconnected components and their dependencies. In the realm of .NET Aspire, orchestration refers to managing, connecting, and configuring all components of a distributed (cloud-native) application to enhance the local development environment and its workflows (inner-loop).

It's important to note that .NET Aspire's orchestration isn't intended to replace the robust systems used in production environments, such as Kubernetes. (.NET Aspire overview)

Basically, .NET Aspire allows you, as a developer, to express your distributed application architecture using C# code. Orchestration in .NET Aspire assists with the following common concerns:

  • Application composition: Enabling the specification of all the pieces that make up your application.
  • Service discovery and connection string management: Injecting the right connection strings, network configurations, and service discovery information to streamline the developer experience.

To simplify integration with popular services and platforms such as PostgreSQL, Redis, and others, you can use .NET Aspire components, which are also distributed as NuGet packages. Components are designed to work seamlessly with .NET Aspire orchestration and address common cloud-native concerns such as adding health checks and contributing telemetry data.

Last but not least, .NET Aspire integrates seamlessly with different development environments, ranging from full-blown Visual Studio, through to Visual Studio Code and the dotnet CLI.

General Availability of .NET Aspire was announced by Microsoft during Microsoft Build 2024.

Getting Started with .NET Aspire

As part of this article, we will explore .NET Aspire using the dotnet CLI. To get started, ensure you have the latest dotnet CLI (8.0) installed on your machine. You can install .NET Aspire by adding the corresponding workload to your .NET installation. You add the aspire workload using the following commands:

# Update workload manifests
dotnet workload update

# Install the .NET Aspire Workload
dotnet workload install aspire

# Verify .NET Aspire Workload installation
dotnet workload list

Container Runtime Integration

Different application components and/or dependencies may be executed as containers on your local machine. Currently, .NET Aspire supports two container runtimes: Docker Desktop and Podman. Although .NET Aspire defaults to Docker Desktop as the container runtime, you can switch to Podman by setting the DOTNET_ASPIRE_CONTAINER_RUNTIME environment variable to podman:

# Use podman as container runtime
export DOTNET_ASPIRE_CONTAINER_RUNTIME=podman

Exploring a .NET Aspire AppHost

.NET Aspire orchestration goes to a special kind of project called the AppHost. We will not explore creating an AppHost project step-by-step as part of this article. Instead, we want to highlight some of the .NET Aspire-specific characteristics here. If you have never used or heard of .NET Aspire before, we would recommend you explore the official .NET Aspire documentation. It does a great job of explaining the core concepts of .NET Aspire and guiding you through the steps of orchestrating, running, and debugging distributed applications on your local machine.

Let’s take a look at a fairly simple orchestration consisting of a .NET WebAPI and a Redis cache (Redis cache capabilities are contributed by the Aspire.Hosting.Redis NuGet-package):

var builder = DistributedApplication.CreateBuilder(args);

var redis = builder.AddRedis("cache");

builder.AddProject("webapi", "../Fermyon.AspireShowcase.WebApi/Fermyon.AspireShowcase.WebApi.csproj")
    .WithOtlpExporter()
    .WithReference(redis);

builder.Build().Run();

Assuming that the WebAPI project is instrumented using OpenTelemetry (which could also be achieved using .NET Aspire Service Defaults), you can trace invocations using the .NET Aspire Dashboard when running the AppHost project locally.

Traces of a .NET WebAPI project in the .NET Aspire Dashboard

Introducing Fermyon.Aspire.Spin

At Fermyon, we strongly believe that .NET Aspire is a productivity booster for many developers and organizations, and that’s why we started building an extension for .NET Aspire, allowing you to add Serverless WebAssembly Applications (Spin Apps) to your distributed application architectures. With Fermyon.Aspire.Spin, you can add Spin Apps written in any language that compiles down to WebAssembly (the wasm32-wasi platform) - to your application using the IDistributedApplicationBuilder interface.

Capabilities Provided by Fermyon.Aspire.Spin

For the sake of flexibility, Fermyon.Aspire.Spin allows you to add Spin Apps either by referencing the source code of your Spin Apps, or by pointing to an existing OCI reference that could be pulled from any OCI-compliant container registry (such as Azure Container Registry).

On top of that, you can use Fermyon.Aspire.Spin to do the following:

  • Create Runtime Configurations on the fly using:
    • Dependencies defined in .NET Aspire.
    • SQLite databases, provided by Spin.
    • SQLite key-value stores, provided by Spin.
    • Hybrid LLM inferencing, using Fermyon Cloud.
  • Specify environment variables passed to your Spin App using the environment variable configuration provider (SPIN_VARIABLE_).
  • Upon starting the .NET Aspire Host, you can:
    • verify that spin CLI is installed using the CheckForSpin lifecycle hook.
    • authenticate against private/protected OCI registries using the SpinRegistryLogin lifecycle hook.
    • install the required spin CLI plugins using the InstallSpinPlugin lifecycle hook.

Installing Fermyon.Aspire.Spin

Installing Fermyon.Aspire.Spin is as easy as adding the corresponding NuGet package to your .NET Aspire AppHost project:

# Adding Fermyon.Aspire.Spin
dotnet add package Fermyon.Aspire.Spin

Orchestrating Containers and WebAssembly

Once you’ve installed Fermyon.Aspire.Spin you can use the extension methods provided by the NuGet package to orchestrate your distributed application. All extension methods are contributed to the Aspire.Hosting namespace - meaning you don’t have to import additional namespaces to access those extension methods. The AddSpinApp method is the most prominent one. It allows you to add Spin Apps to the DistributedApplicationBuilder.

See the following sample, constructing a distributed application consisting of a Spin App, a .NET API and a Redis cache:

var builder = DistributedApplication.CreateBuilder(args);

var redis = builder.AddRedis("cache");

builder.AddProject("webapi", "../Fermyon.AspireShowcase.WebApi/Fermyon.AspireShowcase.WebApi.csproj")
    .WithOtlpExporter()
    .WithReference(redis);

builder.AddSpinApp("spin-app", "../spin-app", 3000)
    .WithSpinEnvironment("LogLevel", "DEBUG");

builder.Build().Run();

Generating Runtime Configurations with .NET Aspire

You can control behavior for different host capabilities of a Spin App by providing a Runtime Configuration File. Fermyon.Aspire.Spin provides a SpinRuntimeConfigurationBuilder, which you can use to create a tailored Runtime Configuration File:

var rtc = SpinRuntimeConfigurationBuilder.Create("sample.toml")
    .WithRedisKeyValueStore("default", redis);

You may want to share Runtime Configuration Files across multiple Spin Apps. There are many scenarios in which this is useful. A great example is a distributed architecture in which multiple Spin Apps should use the same Redis instance as a key-value store. You can pass the SpinRuntimeConfigurationBuilder (rtc) to multiple Spin App of your distributed architectures, as shown here:

builder.AddSpinApp("spin-app", "../spin-app", 3000)
    .WithSpinEnvironment("LogLevel", "DEBUG")
    .WithRuntimeConfig(rtc);

Running Spin Apps with .NET Aspire

Spin Apps are spawned using the spin up command, when running your .NET Aspire Host project. Additionally, the --build flag is automatically injected by Fermyon.Aspire.Spin to ensure the latest version of your source code is compiled down to WebAssembly.

Consuming Spin Apps from OCI registries

On top of orchestrating Spin Apps by referencing the path to the application source code, you can also reference existing Spin Apps by providing the corresponding OCI reference:

builder.AddSpinApp("existing-app",
    OciReference.From("thorstenhans/hello-world-spin-rust", "0.0.1"),
    3006); 

In the previous snippet, a Spin App is created based on a public OCI artifact from the Docker Hub.

It’s quite common to use private registries that require authentication for distributing OCI artifacts. Fermyon.Aspire.Spin supports private registries by providing the SpinRegistryLogin lifecycle hook. The snippet below illustrates how you can add a Spin App by referencing an OCI artifact from Azure Container Registry (ACR), which requires authentication:

var builder = DistributedApplication.CreateBuilder(args);
builder.Services.AddScoped(sp =>
{
    var credentials = new OciRegistryCredentials()
    {
        LoginServer = "aspirespin.azurecr.io",
        User = "aspire-host",
        Password = builder.Configuration.GetValue<string>("ACR_PASSWORD")!
    };
    return new SpinRegistryLogin(credentials);
});

builder.AddSpinApp("existing-app",
    OciReference.From("aspirespin.azurecr.io/qr-generator", "0.0.1"),
    3007);
// ...

Verify that Spin CLI is installed

To run Spin Apps through .NET Aspire, the Spin CLI must be installed on developer machines and Continuous Integration (CI) systems. Fermyon.Aspire.Spin provides a simple yet useful Lifecycle Hook called CheckForSpin, which you can use to verify that Spin CLI is installed on the current machine.

If the Lifecycle Hook cannot locate the Spin CLI on a particular developer machine, a corresponding exception will be raised upon bootstrapping the distributed application.

var builder = DistributedApplication.CreateBuilder(args);
builder.Services.AddScoped<CheckForSpin>(); 

// ...

Installing Spin Plugins

The Spin CLI comes with a powerful plugin infrastructure in place, allowing developers to extend the CLI and add even more capabilities. To ensure that your Spin Apps work across different developer machines, you can use the *InstallSpinPlugin* Lifecycle Hook and install Spin CLI plugins when starting the .NET Aspire Host project.

The kube plugin is a great example. It allows scaffolding Kubernetes Deployment Manifests for your Spin Apps when you want to run your Spin Apps on SpinKube or Fermyon Platform for Kubernetes. The following snippet uses the InstallSpinPlugin Lifecycle Hook to ensure the kube plugin for Spin CLI is installed:

var builder = DistributedApplication.CreateBuilder(args);

builder.Services.AddScoped<InstallSpinPlugin>(sp => new InstallSpinPlugin("kube"));
// ...

Observing Spin Apps with .NET Aspire

Spin has telemetry baked in and integrates seamlessly with OpenTelemetry. In the context of .NET Aspire, configuring OpenTelemetry for your Spin Apps is as easy as calling the WithOtlpExporter method on your Spin App:

builder.AddSpinApp("spin-app", "../spin-app", 3000)
    .WithSpinEnvironment("LogLevel", "DEBUG")
    .WithRuntimeConfig(rtc)
    .WithOtlpExporter();

Running the distributed application again and sending HTTP requests to the Spin App results in distributed traces being collected and sent to the OpenTelemetry Endpoint provided by .NET Aspire:

Distributed Traces of a Spin App and underlying Redis key-value store in the .NET Aspire Dashboard

Exploring Spin Apps using the .NET Aspire Dashboard

The .NET Aspire Dashboard provides important insights about all the components of your distributed applications, and Spin Apps are no exception here. The .NET Aspire Dashboard allows you to drill into:

  • Structured Application Logs
  • Console Logs (stdout)
  • Metrics
  • Environment Variables

As for distributed traces, Spin Apps contribute fundamental metrics through OpenTelemetry, which will be automatically picked up by .NET Aspire and visualized in the Metrics section of the .NET Aspire Dashboard.

Metrics of a Spin App visualized by the .NET Aspire Dashboard

Conclusion & Feedback

Orchestrating distributed applications consisting of Spin Apps, .NET projects, and containers with .NET Aspire reduces friction and drastically streamlines the local development experience. Instead of writing hundreds of lines of declarative code, you can simply use C# to configure and connect the individual parts of a distributed application.

The .NET Aspire Dashboard is the central place to understand how a distributed application behaves at runtime, explore actual configuration data of individual application components, and dive deep into telemetry data generated by the application.

Having first-class support for using Spin Apps with .NET Aspire is an additional step on the journey of making your applications fast, secure, and portable, leveraging the capabilities provided by Spin and WebAssembly in general.

You can find the source code here. File an issue if you are missing something, experiencing defects, or having trouble integrating your Spin Apps with .NET Aspire.

About the Author

Rate this Article

Adoption
Style

BT