Exploring the Microsoft Developer Control Plane at the heart of the new .NET Aspire

.NET Aspire dashboard with the Microsoft Developer Control Plane resources running behind the scenes

As I write this, less than a week after the end of .NET Conf 2023 and the release of .NET 8, .NET Aspire is in preview version (8.0.0-preview.1.23557.2). Therefore, it’s possible that some aspects may have changed by the time you read this article.

During the .NET Conf 2023, Microsoft announced .NET Aspire, a new .NET workload designed to ease the development of applications and microservices in a cloud-native context. Having personally experienced difficulties with developing and orchestrating multiple microservices in a local environment, I was pleasantly surprised by this announcement.

If you haven’t yet seen the deep-dive video by Glenn Condron and David Fowler about .NET Aspire, I invite you to immediately stop reading this article and watch it. It will better equip you to understand the rest of this discussion.

This isn’t just another high-level introductory article on .NET Aspire. I’m sure many others have already done that, and done it better than I could. What I want to delve into here concerns the inner workings of .NET Aspire, beyond its open-source code.

Being very familiar with the source code of the Tye project — the experiment that inspired Microsoft’s development teams to create .NET Aspire — one of my first reactions was to try to understand the internals of .NET Aspire. Specifically, I was interested in how it orchestrates the resources developers declare in their .NET Aspire host. How does .NET Aspire compile and launch other projects? How does it manage the lifecycle of arbitrary executables? How does it interact with the Docker engine to start containers? How does service discovery work?

In the next few minutes, you will discover that .NET Aspire, as it was presented, is just the tip of the iceberg. Indeed, .NET Aspire is built on top of an undocumented orchestrator, also developed by Microsoft. This is the Microsoft Developer Control Plane, otherwise referred to by the acronym DCP. In short, DCP is a sort of miniature Kubernetes, which can be controlled with tools such as kubectl or the official C# client for Kubernetes.

A different approach from project Tye

In the Tye project, the execution of services, containers, and other executables declared in the tye.yaml YAML configuration was orchestrated by C# code, as can be seen in the source code of the ProcessRunner class and the DockerRunner.

Tye’s application model, being aware of all the resources to orchestrate, thus knows all the URLs, ports, and connection strings of these resources. It can then inject them via environment variables, for example.

The application model of .NET Aspire is similar. Instead of using YAML code, the developer declares their resources with C# code.

The developer then has two ways to execute their host application:

With a simple dotnet run, .NET Aspire will:

  • Build and consolidate its application model based on the resources declared by the developer.
  • Start an instance of the Microsoft Developer Control Plane (DCP).
  • Ask DCP to start the resources declared in the application model.
  • Listen to DCP events and receive logs and traces from the started resources.
  • Start and open the .NET Aspire dashboard.

This main mode of operation is what developers will use to start their local microservices environment. This is particularly what interests us in this article.

The second mode of operation allows .NET Aspire to generate a JSON manifest representing all the resources declared in the developer’s C# code.

This manifest can then be consumed by other tools such as azd to provision the Azure infrastructure necessary for running the application in a cloud environment.

You could also write your own tool that consumes this manifest and outputs Terraform code according to your needs.

The Microsoft Developer Control Plane, an unknown and undocumented component

Start a .NET Aspire host project and list the processes running on your machine. You should see the following processes (here, on Windows):

  • dcp.exe
  • dcpctrl.exe
  • dcpd.exe

These processes are part of the .NET Aspire workload. They can be found in the folder <dotnet-sdk-dir/packs/Aspire.Hosting.Orchestration.<RID>/8.0.0-preview.1.23557.2/tools/. This directory also includes an EULA file outlining the terms of use for “Microsoft Developer Control Plane”. I have searched extensively online, particularly on GitHub, for information regarding DCP, but I haven’t found anything that clarifies its purpose and operation. The DCP’s code does not seem to be open-source, at least not currently. According to this GitHub issue, the URL for the DCP Git repository is https://github.com/microsoft/usvc-apiserver, but it is private.

DCP processes can be found in the .NET SDK directory

Let’s engage in some debugging within the publicly available code of .NET Aspire to figure out how it engages with DCP.

Starting DCP through .NET Aspire

At the beginning of the .NET Aspire host startup, the DcpHostService class launches an instance of the dcp.exe process.

There is a mention of kubeconfig. Initially, I thought that Kubernetes was being used for resource orchestration. With a breakpoint in the decompiled code, here is what the content of this kubeconfig file looks like:

My first reflex was to try to list all the pods with kubectl pointing to this kubeconfig file:

But it did not work. Indeed, the server does not seem to recognize the resource type pods:

I then tried to list the supported resource types:

The result was much more interesting:

It appears that this DCP server, which resembles Kubernetes, supports resource types specific to .NET Aspire, especially Executable and Container.

Having started my .NET Aspire host with the starter template, I slightly modified my setup to have a frontend server, two backend server replicas, and a Redis container:

I then tried to describe the Container resource associated with my Redis container:

My Redis cache is indeed orchestrated by DCP. I can also see the Docker instance that was started in Docker Desktop:

Redis container in Docker Desktop, orchestrated by DCP

DCP, a resource orchestrator controlled by .NET Aspire

Subsequently, I went through almost the entire codebase of .NET Aspire to better understand the nature of the project. I was still struggling to differentiate between the open-source .NET Aspire project and the responsibilities of the Microsoft Developer Control Plane.

With the help of Rider, I was able to debug the .NET Aspire code and see how it interacts with DCP. Once DCP is started and the application model (the list of resources and their dependencies) is consolidated, .NET Aspire uses the .NET Kubernetes client to execute “Kubernetes-ish” commands on DCP. The C# class in .NET Aspire responsible for interacting with DCP is ApplicationExecutor.

Here is the content of one of the HTTP requests sent by the .NET Aspire host to DCP to start my Redis container:

All these clues lead to the belief that DCP is the central element of .NET Aspire for local development. It is DCP that knows how to:

  • Interact with Docker Desktop and start containers.
  • Interact with the operating system and start arbitrary processes.
  • Expose the ports of the started resources to the host machine.
  • Manage the lifecycle of the started resources.
  • Handle multiple replicas of the same resource.

What a pity that at this stage there is no documentation on DCP. Running dcp.exe --help does not yield anything very useful:

The command start-apiserver, which is used by .NET Aspire to start DCP, is not found there. One can try to invoke it manually with .\dcp.exe start-apiserver --help:

This provides us with a bit more information. At this stage, we could imagine being able to interact directly with DCP. For that, we would need to know the specification of the resources supported by DCP. Fortunately, the open-source code of .NET Aspire already contains part of this information. In the folder src/Aspire.Hosting/Dcp/Model/, one can find the specifications of all the resources supported by DCP.

It seems there is still a long way to go for .NET Aspire and the Microsoft Developer Control Plane. For instance, the specification of a container describes that one could declare the command to execute within the container as well as the arguments. However, the public C# API of .NET Aspire does not yet allow these properties to be defined. Thus, it is impossible to start an instance of Redis or any other container with custom arguments.

Conclusion

.NET Aspire is a project which, in my opinion, has the potential to become an indispensable tool for facilitating the development of cloud-native applications, even if those applications are not developed with .NET. The Aspire dashboard and the presence of observability and instrumentation as first-class citizens will undoubtedly allow many developers to become aware of what is happening in their applications.

However, the public C# API of .NET Aspire does not yet allow today to exploit the full potential of the Microsoft Developer Control Plane. It is also not possible to stop or restart applications once the .NET Aspire host has started. If one of the resources crashes, the entire application must be restarted. The number of issues on GitHub has noticeably increased since the announcement of .NET Aspire at .NET Conf 2023. The Microsoft development teams will have a lot of work in the coming weeks to decide and prioritize the next features to implement.

For me, the big surprise was discovering the existence of the Microsoft Developer Control Plane. This program, probably coded in Go, seems much more complex than the C# code of .NET Aspire. I hope it will become open-source one day.

Leave a Reply

%d bloggers like this: