As developers, we mostly focus on building applications. However, building the app is only half the story. To consider things “done,” we need to get that app deployed into the wild where people can use it. In the .NET world, there’s no shortage of ways we can host our apps, but today, I’d like to focus on building cloud native .NET applications.
In this post, you’ll learn about building cloud native applications in ASP.NET Core. You’ll learn how to design for the cloud, spin up an Azure Kubernetes Service instance, and deploy your application into the cloud. By the end of this post, you’ll have the tools to build and deploy your own cloud native ASP.NET applications.
What do you mean by “cloud native”?
“Cloud native” is one of those terms that sounds obvious, but there’s a more specific definition.
The Cloud Native Computing Foundation defines the term as follows:
“Cloud native technologies empower organizations to build and run scalable applications in modern, dynamic environments such as public, private, and hybrid clouds. Containers, service meshes, microservices, immutable infrastructure, and declarative APIs exemplify this approach.
These techniques enable loosely coupled systems that are resilient, manageable, and observable. Combined with robust automation, they allow engineers to make high-impact changes frequently and predictably with minimal toil.
The Cloud Native Computing Foundation seeks to drive adoption of this paradigm by fostering and sustaining an ecosystem of open source, vendor-neutral projects. We democratize state-of-the-art patterns to make these innovations accessible for everyone.“
From a more practical perspective, “cloud native” means running container-based applications, usually based on stateless microservices, in a Kubernetes cluster.
Why bother?
With all of the different ways we can deploy apps, why should you care about this one? Kubernetes has a steep learning curve and is part of a vast ecosystem of different cloud native tools. Kubernetes is so complex that there are children's books that attempt to explain it to newcomers.
There are a few advantages to living the cloud native lifestyle.
1. Cost
Kubernetes was created by Google to help them maximize the use of their servers. By containerizing applications, you can pack them more densely. By adding an orchestrator like Kubernetes to the mix, you can keep your nodes running at peak efficiency.
2. Power
Kubernetes is like having a data center in a box that you can configure with code. It’s also like having a brilliant systems admin to go with it. Complex deployment patterns like rolling deployments are built into Kubernetes.
3. Flexibility
When you build your infrastructure with code and leverage free cloud native tools, you can deploy applications rapidly and change your infrastructure as needed with minimal downtime. You will no longer have to wait around for humans to configure servers or plug in cables.
Design principles for cloud-friendly apps
There are several design principles to make your applications more cloud friendly. One list you can follow is the 12 Factor App framework. Many of the things on this list are already baked into ASP.NET or standard practice (the first item is “use source control”), but overall it’s a good list of best practices. When building applications for the cloud, here are a few things you should keep in mind.
Build / Release automation
Make sure both your builds and your releases are automated. Getting the code from your code base to an environment shouldn’t be more than a few button pushes (ideally zero). Also, make sure you separate your build automation from your release automation. You should be able to build and release independently.
Dependencies
Most applications are dependent on some form of infrastructure. Common infrastructure dependencies include databases, caching mechanisms, and file systems. When building applications, make sure you abstract these dependencies and use dependency injection to set them at run time. This will make it easier to swap out those mechanisms when moving to the cloud. You should be doing this for testing, so it shouldn’t require a ton of extra effort.
Services in your application should be accessed using URLs. Even if your services are on the same server, you should still use URLs. This keeps your architecture flexible and is useful when you start to deploy onto multiple containers.
Storing state
Do not store state information in your application. When you’re running in a cloud environment, servers and processes are considered disposable. If you’re storing state in your app process, there’s no guarantee that a user is going to hit the same server each request. There’s not even a guarantee that the server will be there at all.
Local state storing mechanisms include session state, in-memory cache, storing state in local files, and configuration files. Instead, use external mechanisms like databases and external cache providers like REDIS. For configuration, inject your configuration using environment variables.
Disposability
Ideally, cloud native apps are crafted as a series of stateless lightweight services. You should be able to start and stop them quickly and cleanly. When running in Kubernetes, your processes will be refreshed often, so make sure you can gracefully shut down your apps. Also, make sure your processes start quickly.
Azure’s container ecosystem
Once you've built an application you'll need to get it running in Azure. For containerized apps, this happens in two phases. The first phase involves building your application image and pushing it to a container registry. A container registry stores compiled Docker images. Think of it as being like a NuGet repository or npm repository. Container registries hold code that you can run. In Azure, you store your container images in the Azure Container Registry.
The second phase involves hosting a compiled image. You can run containers directly on Docker, but most people use a program called an orchestrator. Orchestrators manage fleets of containers and coordinate how many are running, how many resources they can use, and how they interact with each other. There are several open source orchestrators, but most people use Kubernetes. Kubernetes is widely supported, well documentented, and has broad support across cloud platforms.
Azure has broad container hosting support. You can host individual containers using Azure Container Instances. While this approach is fine for simple apps, it’s not robust enough for larger scale applications. You can also run them in Service Fabric. Service Fabric is an older offering geared towards hosting microservices. There’s nothing wrong with Service Fabric, but Azure has released newer hosting solutions that are easier to use. You can also host containers in an App Service Plan, a Platform as an Application (PaaS) service for hosting web and API applications, but that feature is meant to provide compatibility for technologies that aren’t supported in an App Service Plan.
While these all have their place, you’re going to use Azure Kubernetes Service (AKS) to host the application you'll build in this post. AKS is not the easiest hosting platform in Azure, but it’s one of the most powerful. With AKS, you have access to a huge toolbag of open source cloud native tools. You can replicate complex data centers as source code. Deployment patterns that are difficult to do in a normal data center are done by default in Kubernetes.
Prerequisites
You'll need the following to successfully build and execute the project in the tutorial section of this post:
Azure Account – Microsoft provides first-time Azure subscribers with a free 12-month subscription. If you've used up your 12-month trial period the project in this tutorial will incur costs determined by your subscription type and the length of time you maintain the resources created in the project. For a typical US-based Pay-As-You-Go subscription charges are usually less than $50 if you remove the resources promptly. The project includes an Azure CLI command for this purpose.)
Azure CLI – The CLI scripts in this tutorial were written with version 2.0.68.
Docker Desktop for Windows (541 MB) – Docker is used by Visual Studio to package your applications for deployment to Azure. If you are new to Docker, check out the What to know before you install section on the linked page for important information on system requirements and other considerations.
Git – Cloning the project from GitHub or managing will require a Git client.
Visual Studio 2019 – The Community Edition of Visual Studio 2019 is free.
To get the most out of this post you should be familiar with creating ASP.NET Core Web Applications in C# with Visual Studio 2019 or VS Code.
There is a companion repository available on GitHub for the case study project described in this post. You can get the code there if you'd prefer to check it out rather than build it.
Create the project structure
To begin, create a WiscoIpsum directory in your preferred location for projects. In the WiscoIpsum directory, create an infrastructure subdirectory and a src subdirectory. The infrastructure subdirectory will hold the scripts to create the Azure environment and deployment files. The src directory is for the .NET project you will create in a later step.
Build your environment
One of the advantages of using the cloud is that you can create repeatable environments. Instead of clicking buttons in the Azure Portal, you’re going to create a PowerShell script to build your Container Registry and AKS cluster.
Inside the infrastructure directory, create a new file called generate-azure-environment.ps1.
Open it and add the variable declarations in the following code block. Make particular note of the following required modifications:
- Change the value for
$location
to the most appropriate Azure region for your location after checking to ensure the region supports Azure Kubernetes Services. - Substitute your own value for the
$acrName
variable in place of wiscoipsumacr
. The name of the Azure Container Registry (ACR) is public-facing and must be unique across Azure, so make it something distinctive. - Wherever you see the ACR name literal
wiscoipsumacr
used in the subsequent command-line instructions you’ll need to replace it with the value you created for your ACR.