To the cloud and beyond!


To The Cloud And Beyond!

Last week my son and I joined several other families on our first Cub Scout camping trip. It was a great father/son experience even when we were hit by a record rainfall very unusual for this time of the year.

The camp was not too far from home, but it required a lot of preparation and was remote enough that I was not able to send the weekly newsletter, which was sad since I had a lot to share.

In any case, we came back on Sunday, and by Wednesday I had successfully completed the deployment of the Game Store application to the Azure cloud, and today I'll share a few details of what I learned and remembered along the way.

On to this week's update.

An Azure-ready app

For a quick refresher, here's how the Game Store application, which is core to the upcoming .NET Developer Bootcamp, looks like when running in a dev box:

The beauty of it is that you don't need any cloud resources to run the entire app in your box. All infra services run as simple Docker containers.

But a key part of the bootcamp is to show how to properly deploy the app to Azure. For this, you turn all your microservices into Docker images that will run as containers in the cloud.

But what about the infrastructure services? You don't really want to deploy them as docker images; instead, you should use native Azure services, which are designed for the heavy demands of a Production environment.

After deploying the app to Azure it will look like this:

Here's a quick summary of the services that support the Game Store application in Azure:

  • Container Apps. A simpler version of Azure Kubernetes Service that runs your microservices as containers without having to deal with a Kubernetes cluster.
  • Container Registry. A repository for your microservice container images.
  • PostgreSQL Flexible Server. A cloud version of PostgreSQL used by the Catalog and Ordering microservices.
  • Cosmos DB for MongoDB. A NoSQL database compatible with the MongoDB API. Used by the Basket microservice.
  • Storage. To hold the game images uploaded by users to the Catalog.
  • Service Bus. For command style messages sent by the Ordering microservice as part of the order processing saga.
  • Event Hubs. Used as a Kafka cluster for Catalog to publish any events that Basket and Ordering listen to create their cache of games.
  • Entra ID. To provide OpenID Connect access/identity tokens, as well as roles and other claims.
  • Key Vault. To store secrets, particularly connection strings.
  • Managed Identities. To provide an identity that can be used to grant the microservices different types of access to Azure services.
  • Communication Services. To send email notifications when orders are complete.

There will likely be more Azure services in this mix once the observability pieces are added later, but for now, that's enough to make the app work end-to-end in the cloud.

Now let's see what sort of changes were needed to run the app in Azure.

Using .NET Aspire Azure components

.NET Aspire has built-in support for multiple Azure services. This works pretty well in a few cases, like here where I ask Aspire to publish the PostgreSQL resource as an Azure PostgreSQL flexible server resource:

All the Catalog microservice has to do to connect to the PostgreSQL database in Azure is install the relevant NuGet package (Aspire.Npgsql.EntityFrameworkCore.PostgreSQL) and add this one line, which is the same line used to connect to PostgreSQL locally:

.NET Aspire will take care of provisioning the PostgreSQL Azure resource and then inject an environment variable into the Catalog microservice container in Azure with the corresponding connection string:

But how do you actually deploy your microservices and the infrastructure services to Azure?

Enter the Azure Developer CLI

.NET Aspire is tightly integrated with the Azure Developer CLI, a tool meant to simplify the steps needed to go from your box to the Azure cloud.

Assuming you have declared all your Azure resources (and how they relate to your microservices) in your .NET Aspire AppHost project, all you do is run this command from the AppHost dir:

Which will kick off the provisioning process:

That takes care of the infrastructure part. Then, time to deploy those microservices, which you can do with 1 more command:

Which will turn your microservices into Docker images, publish them to your Container Registry, and run them as containers in Azure Container Apps:

You can also do azd deploy "your-microservice" to deploy an individual microservice, which can save a lot of time. There's also this command to do both infra provisioning and microservice deployment in one shot:

Now, as I eventually found, the moment you need to introduce the smallest customization to those Azure resources, you'll notice the Aspire APIs are not as friendly as they should be. For instance, how to modify this to allow public access to my blobs?

There is an overload for AddAzureStorage() you can use for this, but it is not very intuitive and is currently experimental, so you don't know how much that will change later.

Fortunately, there is a nice second layer of customization you can use here, but for that, we need to dig deeper into what's going on behind the scenes.

Under the hood: it's all Bicep!

It's not quite evident at first glance, but what the Azure Developer CLI (azd) is doing to provision your Azure resources is turning the app model you declared in AppHost into a bunch of Bicep resources.

Bicep is an increasingly popular language and tool to define and manage cloud resources on Azure.

Instead of manually clicking around in the Azure portal to set up databases, storage accounts, etc., you write a Bicep file that describes what you want your infrastructure to look like. Then, Azure takes that file and creates or updates the resources according to your specifications.

This is what we know as Infrastructure As Code, or IaC, an incredibly useful practice in the world of IT and DevOps.

But where are those Bicep files? Well, time to reveal the magic with this single command:

That will translate your .NET Aspire app model into Bicep files. But, instead of placing them into some temp directory, it dumps them right into your repo:

You can now go ahead and explore each of those files to understand exactly how each resource will be provisioned. You could even modify any of those files to alter the properties of the resources.

However, it's best to not touch those files since they will get overridden the next time you run azd infra synth. Please notice that this command effectively exits your app model from C# into Bicep, which is not very convenient.

What I like to do instead is grab a copy of the generated Bicep for the resource I'm interested in, delete all the stuff that azd infra synth generated, and then add my own Bicep file.

For instance, this is what I came up with for my Storage account:

Notice how easy it was to enable public access for blobs over there. Then, all you do is add that file as another resource to your app model, and pass that output blob endpoint as an environment variable to the microservice:

There are more nuances to this, like how to grant permissions to your microservices managed identity so that they can properly interact with the Storage account or any other Azure resource. But it's not too hard and I'll go over all of that in detail in the Bootcamp.

The end result

Here for a few screenshots of the app running fully in the Azure cloud:

Ohh, and one more thing you get for free:

That's the .NET Aspire dashboard deployed alongside the other Azure resources, so you can easily check the logs, metrics and even distributed traces of your microservices as they interact with any of their dependencies. Very useful!

There's A LOT more that I had to do to make this work (Aspire service discovery on Azure just doesn't work!), and I'll go over all of that in the bootcamp, but now it's time to switch to another critical aspect I don't want to miss before jumping into the DevOps side of things:

Integration Testing

Until next week!

Julio


Whenever you’re ready, there are 3 ways I can help you:

  1. Building Microservices With .NET:​ A complete program designed to transform the way you build cloud-ready .NET systems at scale.
  2. Building .NET REST APIs: A carefully crafted pathway to learn how to build production-ready .NET based REST APIs, step by step.
  3. Patreon Community: Join for exclusive discounts on all my in-depth courses and access my Discord server for community support and discussions.
  4. Promote yourself to 16,000+ subscribers by sponsoring this newsletter.

11060 236th PL NE, Redmond, WA 98053
Unsubscribe · Preferences

The .NET Saturday

Join 16,000+ subscribers for actionable .NET, C#, Azure and DevOps tips. Upgrade your skills in less than 5 mins every week.

Read more from The .NET Saturday

The Pillars of Observability After completing the Game Store application, the last week was all about scripting the first few modules of the upcoming .NET Cloud Developer Bootcamp, which essentially means creating a detailed Word document for each lesson, describing exactly how that lesson should go. I don't know how many content creators do this, since it's a long (and sometimes tedious) process, but I find it essential to make sure each concept and technique is introduced at exactly the...

DevOps: Part 2 It's done! A couple of days ago I finally completed the Game Store system, the distributed .NET Web application that will drive the upcoming .NET Cloud Developer Bootcamp (Is that a good name? Let me know!). I'm amazed by how much the tech has advanced in the .NET and Azure world in the last few years. There's so much going on in this field that I have no idea how folks are solving today's chaotic puzzle to learn cloud development with .NET. I was lucky enough to enter the .NET...

DevOps: Part 1 Wow, getting the Game Store web application deployed to Azure via Azure DevOps was one of the most challenging things I've done so far as part of the .NET Developer Bootcamp project. But, somehow it all worked out, and the end result is really nice. The complexity came from me trying to fit both the Azure infra deployment and the CI/CD process into the .NET Aspire model, which is only poorly supported at this time. But, having worked on dozens of Azure deployments and CI/CD...