Here we look at Azure’s managed Kubernetes service, AKS, and deploy a simple cluster to our applications resource group using ARM templates and an Azure DevOps pipeline. When also create our pipeline, configure our application to build and deploy automatically to the resources we configured in the first article.
Earlier in this series, we created a basic, event-driven, cloud-native application with Azure Functions. Then, we learned about containerization and Kubernetes.
While we can do plenty with Functions themselves, we often need some functionality that doesn’t fit into the Functions model. To ensure our cloud application can still dynamically scale and leverage the benefits of being cloud native, it makes sense to use containers, especially for longer-running application components.
In this article, we expand our existing cloud native application by building and deploying a Kubernetes cluster using Azure Kubernetes Service (AKS). Then, in our final post, we’ll deploy container-based components of our application. To do this, we use Azure DevOps and Azure Resource Management (ARM) templates so that our single code base contains code for both infrastructure and application components.
Azure Kubernetes Service
Azure Kubernetes Service provides a simplified Kubernetes cluster managed by Azure. Just specify the configuration and deploy nodes without worrying about managing and monitoring your cluster. Kubernetes hosts a variety of different architectures and Azure supports many different configurations, but for this example, we deploy a simple cluster of nodes to support a single container.
Azure Resource Manager
Azure Resource Manager is the underlying service that manages and deploys resources in Azure. Everything in Azure runs through ARM, but for our purposes, we use ARM templates to define and control the resources required for our cluster.
ARM templates are JSON files that the Resource Manager uses to configure and deploy resources, enabling us to create and manage them similarly to our application code. Azure DevOps (and other tools like GitHub Actions) can also automatically send these files to the Azure Resource Manager for deployment when they are checked in.
Prerequisites and Cleanup
Before we can create a template to deploy resources, we need to configure two access components:
- an SSH keypair to connect to cluster nodes
- a service principal that provides role-based access controls for the Kubernetes cluster to interact with Azure resources
To generate the keypair, open your Azure portal and click on the Cloud Shell () icon on the top menu bar. This opens a Bash or PowerShell interface for working with Azure Resources.
In this shell, type in the command
ssh-keygen -t rsa -b 2048 and follow the prompts with the default save pass and password (you can leave this blank for development, but it’s better practice to supply a password).
Once this process is finished, there should be an identification and public key file saved. Using the upload and download () icon, download these two files for later.
While we are in this cloud shell, let’s also create the service principal for our cluster by running the command
az ad sp create-for-rbac --skip-assignment. This outputs JSON style text to the command window. Save this text to access our cluster, then close the shell window.
Before we create our ARM template, we should clean up our Function App and Azure DevOps project so we have a place to put our infrastructure code. First, create a FunctionApp folder and move all the code we used from the first article into this folder (with the exception of .gitignore, .git and .vscode).
Next, create an empty folder called Infrastructure to store our ARM templates. Don’t forget to check in and push these changes to Azure DevOps.
AKS Cluster Template
Now that our prerequisites are complete and our project is cleaned up, we can create our ARM template. Because of the size and scope of Azure resources, creating templates can be a daunting task. We can create templates from scratch, but the amount of options on a single resource can be overwhelming. If you look at the AKS resource, you will find lots of potential properties to set. Thankfully, the Azure community has lots of quick start templates we can use as a starting point.
Let's use the AKS starting template by copying the azuredeploy.json file into our infrastructure directory as aks_cluster.json. Let’s then step through some of the most important details.
All templates start by defining the $schema and contentVersion properties. From there, we have three major sections:
- Parameters – defines the inputs to the template for configuring resources
- Resources – defines the resources to be deployed in Azure
- Outputs – defines the return values from the template
Well-written templates, like this one, make the resources fairly generic and push most of the flexibility to the parameters. We can use these parameters when deploying templates through the Azure Portal, or we can define them in our Azure DevOps pipelines. We can also specify a configuration file so we don’t need to pass options in through a pipeline.
To do this, we create a new file called aks_cluster_params.json in our Infrastructure folder with the following code:
"value": "ssh-rsa AAAAB3NzaC1yc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000R3JIOgtLQ9ORNTi/PN6Avk7rNi0ZS5BKrbLeieLhrD06ct2E23Hsb0vnK0w5aOQKhedQz1IOV43MAPU6Om+KC65qzVKVQk30wUoV/gSJhS9trYz+Y8L9twVQ92NKMo+UNXpG5Uhh2Hk6F5ysJBB45lo0jw5h4JQ13pEkCxotFrWmn9XUIigMZpoUuv4RhMROyLLNmy1csndMWkq9zgdxdIsw7mAUes6KiaHWN7hPOzL0sYtwlnLWPgP0sWfLT glenn@cc-884047f1-74f86db768-lblpf"
This code configures the Azure location to deploy the cluster, the cluster name, the dns prefix, and the admin user name. Set the last value to the contents of the id_rsa.pub file we created in the prerequisite steps. Save these files and check in your changes.
Azure DevOps Pipelines
Now that we have all the components in our repository, we could just deploy our cluster, but let’s set up the pipeline to also deploy our application.
Click on Pipelines on the left and create a new Pipeline.
For the connect option, select Azure Repos Git, then your project’s repository, and you get a list of templates.
Select the Node.js Function App to Linux on Azure, connect to your Azure Subscription, and select the Function App we created in the first article.
Once this is complete, you will see the template with your subscription details in place. Before we can run this, we need to update some elements because of where we are storing our Function application, as well as updated versions of Node. Specifically:
- On line 35, change the versionSpec for the Node version to 12.x.
- After line 49, add the line
workingDirectory: '$(Build.SourcesDirectory)/FunctionApp'. This points npm to the correct directory to build our source.
- Finally, on line 55 (or 54 before adding the above), change the Archive files
rootFolderOrFile property to '
$(Build.SourcesDirectory)/FunctionApp'. This ensures our build is in the root of the ZIP archive that is created.
Now, click the Save and Run button to start the build and deployment process for our Function application. This deployment template has two stages, build and deploy. We are adding a third stage before build called Infrastructure. Before we can do this, however, we need to ensure our DevOps project can add resources to our subscription.
In the menu panel on the left, select project settings then Service Connections under the Pipelines section.
Select the Azure Resource Manager options and use service principal (automatic) for the authentication method. Fill in the details of subscription, resource group, name, and description, then click save.
Once we set up our service principal, we can add the new stage to our build pipeline. The code for this is as follows:
- stage: Infrastructure
displayName: Deploy Infrastructure
- job: Cluster
displayName: Deploy AKS Cluster
- task: AzureResourceManagerTemplateDeployment@3
displayName: 'Deploy AKS Cluster'
deploymentScope: 'Resource Group'
action: 'Create Or Update Resource Group'
templateLocation: 'Linked artifact'
This stage has one step that deploys our ARM template and configuration file using the
AzureResourceManagerTemplateDeployment task type. If we run through the configuration for this task, we need to populate the following inputs:
azureResourceManagerConnection: the name of our service principal so the pipeline has access to deploy resources
subscriptionId: the unique identifier for our Azure subscription
resourceGroupName: the resource group we are deploying resources into
location: the Azure location where we want to place our resources
csmParametersFile: our ARM template and parameters file locations
It’s also worth noting that with the
deploymentMode set to incremental, resources already in the resource group will remain untouched. Once you save this pipeline code and check it in, the pipeline runs and deploys our cluster. If you head over to your Azure portal, you should see the cluster appear in the resource folder.
We can also connect to our cluster using kubectl and check to see the nodes that are running. The easiest way to do this is open the cloud console in the Azure portal again and use the command:
az aks get-credentials --resource-group TSFunctionTutorial --name TSAppCluster
This connects the already configured kubectl utility to our deployed cluster. Now, if you run
kubectl get nodes, you should see three agent nodes returned and ready.
In this article, we looked at Azure’s managed Kubernetes service, AKS, and deployed a simple cluster to our applications resource group using ARM templates and an Azure DevOps pipeline. When we created our pipeline, we also configured our application to build and deploy automatically to the resources we configured in the first article.
By combining all our application code into a single code base that we can deploy as we check in changes, we can build and deploy our application and infrastructure without any further work.
In the next and final article in this series, we will deploy a containerized application component into our cluster.