.NET Aspire was published a couple of weeks ago and we started to utilize it immediately in one new project. We knew that there would be some hiccups because .NET Aspire is still in preview. The official release is planned for the first half of 2024.
This blog post shares some experiences and lessons learned while using .NET Aspire in Azure. I recommend you read also my earlier post about the Azure Developer CLI because it's a highly recommended tool with .NET Aspire.
What is .NET Aspire?
.NET Aspire is an opinionated stack for building resilient, observable, and configurable cloud-native applications with .NET. It includes a curated set of components enhanced for cloud-native by including service discovery, telemetry, resilience, and health checks by default. Combined with a sophisticated but simple local developer experience, .NET Aspire makes it easy to discover, acquire, and configure essential dependencies for cloud-native apps. Introducing .NET Aspire: Simplifying Cloud-Native Development with .NET 8
Practically, .NET Aspire is designed to improve developer's productivity and experience of building cloud-native microservices and distributed applications. Especially, it helps to handle and streamline the orchestration between different parts of your cloud-native app which makes developers' life more easier. Combined use of Azure Developer CLI and .NET Aspire makes deployments super smooth and straightforward.
You may have heard about Project Tye already before .NET Aspire which is an orchestrator tool developed by Microsoft to make developing microservices easier. Most probably Project Tye has also influenced the development of .NET Aspire.
Lessons learned so far
.NET Aspire is currently (12/23) in preview and it's evolving rapidly so some of these issues may have been resolved already while reading this.
1. Azure Functions are not supported (yet)
According to FAQ, Azure Functions are not supported in preview 1 of .NET Aspire. Microsoft has stated that they are planning to add support later in the future.
The initial plan in our project was to use Azure Functions for subscribing events. Due to a lack of Azure Function support, we decided to implement an event subscribing capability using background worker processes. This is one workaround until Azure Functions are supported.
2. Deployment to Azure without defining the infrastructure
.NET Aspire application hosted in Azure Container App Environment can be deployed to Azure using Azure Developer CLI without defining infrastructure with Bicep. This was confusing to notice at first. I tried to find bicep files from the solution but there is no any. It seems that Azure Developer CLI/.NET Aspire has some internal logic to handle the infrastructure deployment of Azure Container App Environment.
All you need to do is initialize your .NET Aspire project with azd init and deploy. By default, the following Azure resources are created during the infrastructure provisioning:
- Azure Container Registry
- Azure Log Analytics Workspace
- User Managed Identity
- Azure Container Apps Environment
This is a quick way to test applications but in a more complex environment, full control of infrastructure creation is required. If you want to include infrastructure files in your project and perhaps modify the infrastructure then you need to execute infrastructure synthetization.
azd infra synth
This command creates an infra folder with bicep modules and all modules are free to modify. infrastructure synthetization command is currently (12/23) an alpha release.
3. Confusion with local environment configurations
By default Azure Developer CLI's project initialization command (azd init) creates the following environment-specific folder structure under .azure folder.
│ ├── dev [ Environment folder ]
│ │ ├── .env [ Environment configuration file]
│ └── config.json [ AZD and Container Apps Environment configuration file ]
The folder structure contains the environment variable file (.env) for key-value pair types of configurations and config.json file for common configurations. Azure Container Apps / .NET Aspire uses a config.json file to determine e.g. services that will be exposed to the public internet. It's not clear to me what it's the actual purpose of this "local" configuration because these exposed services are also determined in application-specific YAML-manifest files.
By default, these local environment folders are excluded from the source control in .gitignore file.
After initialization .NET Aspire application can be deployed to Azure from a local machine by using azd up command.
Issues in Azure deployment pipelines
Local application provision and deployment from a local machine to Azure works fine with azd up command but then some challenges occured in Azure deployment pipelines.
Execution of azd up command in the deployment pipeline causes the following error:
ERROR: failed registering service hooks, failed getting services: importing services: generating app host manifest: selecting public services: no default response for prompt 'Select which services to expose to the Internet'
- task: AzureCLI@2
displayName: Provision Infrastructure
$environment = $Env:AZURE_ENV_NAME;
azd up --environment $environment --no-prompt
As the error says local agent in Azure DevOps tries to find the config.json file's exposed services configuration but it cannot find it because it's not in the source control.
This error can be bypassed including environment-specific files in the source control, and especially config.json. We decided to include only config.json file in the source control.
4. Should we use local environment files (.env) in Azure?
The deployment pipeline originally used Azure DevOps pipeline variables to use different settings per environment. I started to consider if we would use these local environment-specific files from source control in deployment pipelines and leave only sensitive variables in pipeline variables / KeyVault. This would enable to use of the same kind of configuration handling in local development and deployment pipelines. Let's try this.
After some try and error, I concluded that only the default variables (AZURE_LOCATION and AZURE_SUBSCRIPTION) were automatically detected from .env files. For some reason, custom environment variables are not working which is a show-stopper for this option.