Task 02 - Introduce infrastructure as code (20 minutes)
Introduction
Now that we have some code, we need an environment to deploy it to! The term Infrastructure as Code (IaC) refers to using code-based templates repeatedly and consistently to create the dev, test, and prod infrastructure environments. We can automate the deployment of Azure services we need with an Azure Resource Manager (ARM), Bicep, or other type of template, which we can invoke as part of a GitHub Actions workflow.
Review the following articles:
In this task, there are instances where commands are run using the Azure CLI. The recommendation is to use Azure Cloud Shell for the cli, especially when using MCAPS accounts.
Description
In this task, you will deploy out new Azure infrastructure for the Munson’s Pickles and Preserves Team Messaging System. You will deploy this to three environments: dev
, test
, and prod
. Each environment will have its own Web App, though for the sake of this proof of concept, all environments will share a single resource group, App Service Plan, Application Insights instance, and Azure Container Registry.
We will use GitHub Actions to automate the deployment of our Azure infrastructure. For our application, we will deploy 3 environments: dev
, test
and prod
. Each environment will have its own Web App. For the sake of this proof of concept, however, all of our environments will share a single Resource Group, App Service Plan, Application Insights instance, and Azure Container Registry.
We will lay out the resources in a single resource group for this training for two reasons. The first reason is to facilitate ease of cleanup at the end, as you will only need to delete one resource group. The other reason is to reduce the cost of training, as you will create only one App Service Plan and one Application Insights instance. In a real scenario, you would likely create separate resources for each environment, either in separate resource groups on the same subscription or separate resource groups in different subscriptions.
- In the
InfrastructureAsCode
folder, there is a file calledmain.bicep
. Use GitHub Copilot to help you finish out the file definition. It should include configuration settings for an App Service Plan, a Web App, Application Insights, and Azure Container Registry in your resource group. - Review the use of the
uniqueString
function. This helps create unique names for your resources. This function is not random, but is instead a hash function based on your resource group ID, which provides a consistent but likely unique 13-character string. This function is useful to avoid naming conflicts in Azure. - Review the
environment
andlocation
parameters. These have default values but you are able to override them as well in order to create other resources in different environments. - Create a resource group, giving it a memorable name.
- Configure OpenID Connect (OIDC) access from GitHub to Azure using a federated credential. To do so, you will need to perform the following steps, using the az cli:
- Create an application registration in Microsoft Entra ID. Keep track of the
appId
andid
values. - Create a service principal based on your application registration. Keep track of the
appOwnerOrganizationId
andid
values.appOwnerOrganizationId
is the id of tenant in which you are creating your application registration. - Create a role assignment granting Contributor access to your resource group for the service principal you have created.
-
Create a new federated credential. You may wish to use a parameters JSON file with contents like:
{ "name": "GitHubDevOpsCredential", "issuer": "https://token.actions.githubusercontent.com", "subject": "repo:{your_account}/TechExcel-Implementing-DevOps-practices-to-accelerate-developer-productivity-code", "description": "Deploy Azure resources from the TechExcel DevOps practices GitHub repo", "audiences": [ "api://AzureADTokenExchange" ] }
There are two valid ways of authorizing a GitHub Actions runner to access resources in Azure. The simpler way is to create an application registration and service principal in Microsoft Entra ID and then save the client secret in GitHub as a GitHub Actions secret. This mechanism is reasonably secure but comes with the downside that, in the event your client secret becomes exposed, an attacker can exploit this client secret until you change it. The other mechanism is to use OpenID Connect and federated credentials. This creates a series of short-lived credentials (lasting 1 hour), so in the event that a credential is exposed, an attacker has a limited time frame available to exploit the credential before it expires. For this reason, we strongly prefer using OpenID Connect whenever possible, as it provides greater security at a minor one-time cost in setup effort.
- Create an application registration in Microsoft Entra ID. Keep track of the
- Create four GitHub repository-level Actions secrets to store the following information:
- The application registration ID
appId
– call itAZURE_CLIENT_ID
. - The app owner organization ID for your service principal
appOwnerOrganizationId
– call itAZURE_TENANT_ID
. - The ID of the Azure subscription – call it
AZURE_SUBSCRIPTION_ID
. - The resource group name – call it
AZURE_RG
.
GitHub has multiple types of secrets you can create: you are able to create secrets for Actions, Codespaces, and Dependabot. Be sure to create your secrets for GitHub Actions, not for Codespaces or Dependabot.
- The application registration ID
- Create a new GitHub Actions workflow called
deploy.yml
. This workflow should run on a manual trigger–that is, not triggered by a push or pull request. - Use GitHub Copilot to help you configure the workflow to accomplish the following tasks:
-
Use a service principal to log into Azure using your secret and configuration variable values.
When using the
azure/login
GitHub Action with a federated credential, you will need to includeenable-AzPSSession: true
in thewith:
section. -
Use the “Deploy Azure Resource Manager (ARM) Template” action to call the Bicep template in your repo.
The name is a little confusing here as this action supports both ARM and Bicep as the template file. This is because Bicep is a transparent abstraction of ARM. For more details check out this article.
-
-
Manually run your workflow. When your workflow completes successfully, go to the Azure portal to see the
dev
environment.If you are using an MCAPS account, you may need to register several Azure resource providers, as these might not be on by default. The az cli commands to register these are as follows:
az provider register -n Microsoft.ContainerRegistry -c az provider register -n Microsoft.Web -c az provider register -n Microsoft.OperationalInsights -c az provider register -n Microsoft.Insights -c
- Run the workflow a second time, this time overriding the environment parameter with
test
instead ofdev
. When your workflow completes successfully, go to the Azure portal to see the newtest
App Service. - Run the workflow a third time, this time overriding the environment parameter with
prod
instead ofdev
. When your workflow completes successfully, go to the Azure portal to see the newprod
App Service. - Ensure that Application Insights is enabled for each of the three App Services.
Success Criteria
- Your
deploy.yaml
workflow completes without any errors and overrides theenvironment
parameter when calling the Bicep template. - Your resource group contains 7 resources: 3 App Services (dev, test, prod), 1 Application Insights, 1 App Service plan, 1 Container registry, and 1 Log Analytics Workspace.
Learning Resources
- Azure Login GitHub Action
- What is Infrastructure as Code?
- Secrets in GitHub Actions
- Use the Azure login action with OpenID Connect
- Configuring OpenID Connect in Azure
- Configuration Variables in GitHub Actions
- Deploy Bicep and Azure Resource Manager templates by using GitHub Actions
- Overriding ARM template parameters
If you are interested in learning more about Infrastructure as Code, there are multiple What the Hacks that cover it in greater depth:
- Infrastructure As Code: Bicep
- Infrastructure As Code: ARM Templates & PowerShell DSC
- Infrastructure As Code: Terraform
- Infrastructure As Code: Ansible
Solution
Expand this section to view the solution
- The final Bicep script is in the solution folder.
- The following az cli commands will create an application registration and then service principal. This uses a sample app registration name of
TechExcelUser
.az ad app create --display-name TechExcelUser
. Copy theappId
output to use in the next command. Save theappId
as a GitHub secret called AZURE_CLIENT_ID. Copy the “id” output to use for the {app registration ID} when creating the federated credential.az ad sp create --id {appId}
. Copy the “id” of this newly created service principal to use in the next command for the {service principal ID}. Save theappOwnerOrganizationId
as a GitHub secret calledAZURE_TENANT_ID
az role assignment create --role contributor --scope /subscriptions/{subscription_id}/resourceGroups/{resource_group_name} --subscription {subscription_id} --assignee-object-id {service principal ID} --assignee-principal-type ServicePrincipal
.- Create a file called credentials.json and fill in the contents with your account name:
{ "name": "GitHubDevOpsCredential", "issuer": "https://token.actions.githubusercontent.com", "subject": "repo:{your_account}/TechExcel-Implementing-DevOps-practices-to-accelerate-developer-productivity-code:ref:refs/heads/main", "description": "Deploy Azure resources from the TechExcel DevOps practices GitHub repo", "audiences": [ "api://AzureADTokenExchange" ] }
az ad app federated-credential create --id {app registration ID} --parameters credentials.json
-
The GitHub Actions workflow for this task is a YAML file in the solutions folder. The following code block is the YAML file with additional comments and explanation.
# We only want to run this script manually. on: workflow_dispatch # Environment variables are defined in an "env" section. # We set the target environment to dev. # Open the deploy-advanced.yml file to see how we can accept user input # instead of needing to change this file to switch environments. env: targetEnv: dev # The overall workflow name will be Azure Bicep. This will show up in the # GitHub Action page. name: Azure Bicep jobs: # This script has one job: build and deploy the IaC resources build-and-deploy: # We run this on an Ubuntu-based GitHub hosted runner. This hosted runner # has certain software already installed, including az cli runs-on: ubuntu-latest # In order to use federated credentials you will need to give your job, permissions to write your id-token # If you need to access any of the content of the repository(which in most of the cases you need), you will have to give read permissions for the contents. # If you don't specify any permissions contents: read is set by default, however when you start modifying the permissions, this default value is removed. permissions: contents: read pages: write id-token: write steps: # Check out the code. This grabs code from the repository and # makes it available to the GitHub hosted runner. It will usually be the # first task for any workflow - uses: actions/checkout@main # Log into Azure using a federated credential. We have already set up the # federation process in a prior step, so we need to pass in the following: # Client ID = Application registration ID `appId` # Tenant ID = Application owner organization ID (previously called Tenant ID in Azure) `appOwnerOrganizationId` # Subscription ID # https://github.com/azure/login - uses: azure/login@v1 with: client-id: $ tenant-id: $ subscription-id: $ # We also need to ensure that enable-AzPSSession is true. This is important for # using OIDC in Azure. If we were to pass in a client secret instead, we would not need # this setting enabled enable-AzPSSession: true # Deploy ARM template - name: Run ARM deploy # https://github.com/azure/arm-deploy uses: azure/arm-deploy@v1 with: subscriptionId: $ resourceGroupName: $ template: ./InfrastructureAsCode/main.bicep # Use the environment variable called targetEnv parameters: environment=$
- To enable Application Insights via the Azure portal, navigate to an App Service. Then, choose Application Insights from the Settings menu on the left-hand side. Next, select the Turn on Application Insights button to enable Application Insights. Finally, select Apply and then Yes to complete the process. Repeat this for the two remaining environments.
Advanced Challenges (optional)
Instead of changing the environment variable for each environment that we want to create in deploy.yml
, you can configure the workflow to prompt the user to enter the environment name before the workflow runs. This eliminates the need to hardcode the environment name.
- Configure your workflow to collect the environment name as a workflow input and use that value to override the environment parameter when calling the Bicep template.
Advanced Challenge Solution
Expand this section to view the advanced challenge's solution
- The solution to this advanced challenge is in the deploy-advanced.yml workflow file.