This week I worked with Azure Bicep and YAML to deploy Azure infrastructure. This blog post shows how to utilize Azure Bicep and YAML ("build blocks") to deploy Weather Forecast application and infrastructure to the Azure. This sample utilizes Weather Forecast application which was created in the earlier blog post.
What is Azure Bicep?
Shortly Azure Bicep is a declarative domain-specific language (DSL) to deploy Azure resources. You can use Bicep instead of JSON to develop your Azure Resource Manager templates (ARM templates). Bicep is a transparent abstraction over ARM template JSON and doesn't lose any of the JSON template capabilities. During deployment, the Bicep CLI converts a Bicep file into ARM template JSON. Source: What is Bicep?
Image source: Project Bicep introduction (Image originally extracted from “Project Bicep Demo at Ignite 2020 by Mark Russinovich”)
More materials to check:
Install Bicep Tools
Bicep on Microsoft Learn
Understand the structure and syntax of Bicep filesData type in Bicep
Parameters in Bicep
Variables in Bicep
Define resources with Bicep and ARM templates
Weather Forecast Azure infrastructure
Weather Forecast service has two App Services (UI and API) and Application Insights. This Azure infrastructure is deployed using Bicep.
Before starting
Download and install Azure CLI tool from here. After that install Azure Bicep CLI with the following command:
az bicep install
Weather Forecast Bicep configuration
Final infrastructure configuration looks like this (created with Visual Studio Code Bicep file visualizer)
This samples uses two Bicep modules: App Service Module and Application Insights Module. Bicep modules are a powerful way to organize and encapsulate complex details to the multiple bicep files. A module is just a Bicep file that is deployed from another Bicep file. Source: Bicep Modules
Modules improves readability and reusability when you're building and designing especially complex infrastructure.
Application Insights Bicep Module
This module creates Application Insights resource for logging purposes. Weather Forecast UI and API components have own application insight resources. Modules can return output values when resource is created. This module returns InstrumentationKey of the Application Insight which enables communication between App Service and Application Insights.
@description('Name of the Application Insights')
param applicationInsightsName string
@description('Application Insight Location.')
param applicationInsightsLocation string = 'West Europe'
@description('Tags.')
param tags object
resource appInsights 'Microsoft.Insights/components@2020-02-02-preview' = {
name: applicationInsightsName
location: applicationInsightsLocation
kind: 'web'
properties: {
Application_Type: 'web'
publicNetworkAccessForIngestion: 'Enabled'
publicNetworkAccessForQuery: 'Enabled'
}
tags:tags
}
output instrumentationKey string = appInsights.properties.InstrumentationKey
App Service Bicep Module
This module is reusable App Service component which also creates an Application Insights and connects Application Insight and App Service together. Basically Application Insights Module is utilized inside this App Service Module. Application Insights Module returns InstrumentationKey as a output parameter and this value is set to the application settings of the App Service.
Because Weather Forecast UI and API utilizes .NET 6 Framework remember to set 'netFrameworkVersion' to 6.
@description('Name of the App Service Plan')
param appServiceName string
@description('ID of App Service Plan.')
param appServicePlanId string
@description('Location.')
param location string = 'West Europe'
@description('Tags.')
param tags object
module appInsights './appinsights.bicep' = {
name: 'appinsights-${appServiceName}'
params: {
applicationInsightsName: 'appi-${appServiceName}'
applicationInsightsLocation: location
tags: tags
}
}
resource appService 'Microsoft.Web/sites@2021-02-01' = {
name: appServiceName
location: location
properties:{
serverFarmId: appServicePlanId
siteConfig:{
alwaysOn: true
ftpsState: 'Disabled'
netFrameworkVersion: 'v6.0'
appSettings: [
{
'name': 'APPINSIGHTS_INSTRUMENTATIONKEY'
'value': appInsights.outputs.instrumentationKey
}
]
}
httpsOnly: true
}
tags:tags
}
output appServiceId string = appService.id
Main Bicep File
Main Bicep file orchestrates the building of the Azure infrastructure. It first creates shared App Service Plan and then App Services for Weather Forecast UI and API. Parameters like names and App Service Plan SKU are retrieved from the separate parameter file.
@description('Name of the App Service Plan')
param appServicePlanName string
@description('The SKU of App Service Plan.')
param appServicePlanSku string = 'S1'
@allowed([
'Win'
'Linux'
])
@description('Select the OS type to deploy.')
param appServicePlanPlatform string
@description('Name of the UI App Service')
param uiAppServiceName string
@description('Name of the API App Service')
param apiAppServiceName string
var defaultTags = {
Team: 'DevTeam1'
}
resource appServicePlan 'Microsoft.Web/serverfarms@2021-02-01' = {
name: appServicePlanName
location: resourceGroup().location
sku: {
name: appServicePlanSku
}
kind: ((appServicePlanPlatform == 'Linux') ? 'linux' : 'windows')
tags:defaultTags
}
module uiAppService './webapp.bicep' = {
name: 'uiAppService'
params: {
appServiceName: uiAppServiceName
appServicePlanId: appServicePlan.id
location: resourceGroup().location
tags: defaultTags
}
}
module apiAppService './webapp.bicep' = {
name: 'apiAppService'
params: {
appServiceName: apiAppServiceName
appServicePlanId: appServicePlan.id
location: resourceGroup().location
tags: defaultTags
}
}
Parameters file
Parameter file contains all parameters which are needed for creating the Azure infrastructure.
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"appServicePlanName": {
"value": "plan-weatherforecast"
},
"appServicePlanSku": {
"value": "S1"
},
"appServicePlanPlatform": {
"value": "Win"
},
"uiAppServiceName": {
"value": "app-weatherforecastui"
},
"apiAppServiceName": {
"value": "app-weatherforecastapi"
}
}
}
Deploy infrastructure to Azure locally using Bicep CLI
Login first to Azure
az login --tenant 00000000-0000-0000-0000-000000000000
Select Azure subscription
az account set --subscription 00000000-0000-0000-0000-000000000000
Deploy infrastructure to Azure with Bicep
az deployment group create --resource-group rg-weather-forecast --template-file main.bicep --parameters parameters/dev.parameters.json
Other Bicep CLI commands which you should know
Bicep to ARM:
az bicep decompile --file main.json
ARM to Bicep:
az bicep build --file main.bicep
How to configure application & infrastructure CI/CD pipeline as a code with YAML?
YAML enables you to configure pipelines as a code. I simplified pipeline presentation in this blog so below you find examples of most important tasks which are utilized in Infrastructure and .NET 6 API application pipelines.
Infrastructure YAML pipeline in Azure DevOps
Infrastructure pipeline deploys Weather Forecast Azure infrastructure.
The following variables are used while deploying infrastructure
variables:
resourceGroupName: 'rg-weather-forecast'
location: 'westeurope'
azureSubscription: 'azure-service-connection'
Build stage contains the job with the following tasks:
1. Copy Files creates Azure infrastructure artifact
Infrastructure artifact contains main.bicep and parameter files.
- task: CopyFiles@2
displayName: 'Create infrastructure artifact'
inputs:
SourceFolder: 'infrastructure'
Contents: |
main.bicep
parameters/dev.parameters.json
TargetFolder: '$(Build.ArtifactStagingDirectory)'
2. Publish Build Artifacts publishes infrastructure artifact to the own container
- task: PublishBuildArtifacts@1
displayName: 'Publish infrastructure artifact'
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'infrastructure'
publishLocation: 'Container'
Deploy stage contains the job with the following tasks:
1. Download infrastructure artifact
- task: DownloadBuildArtifacts@0
displayName: 'Download infrastructure artifact'
inputs:
buildType: 'current'
downloadType: 'single'
artifactName: 'infrastructure'
downloadPath: '$(System.ArtifactsDirectory)'
2. Deploy infrastructure
Azure CLI task enables you to call Bicep CLI commands so you can utilize the same commands which where tested earlier locally.
- task: AzureCLI@2
displayName: 'Deploy infrastructure'
inputs:
azureSubscription: $(azureSubscription)
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az group create \
--name $(resourceGroupName) \
--location $(location)
az deployment group create \
--name $(Build.BuildNumber) \
--resource-group $(resourceGroupName) \
--template-file $(System.ArtifactsDirectory)/infrastructure/main.bicep \
--parameters @$(System.ArtifactsDirectory)/infrastructure/parameters/dev.parameters.json \
After these phases Infrastructure is deployed.
.NET 6 API application YAML pipeline in Azure DevOps
.NET 6 API application pipeline deploys Weather Forecast API application to Azure.
The following variables are used while deploying .NET 6 API application
variables:
buildConfiguration: "Release"
dotNetVersion: '6.0.x'
resourceGroupName: 'rg-weather-forecast'
location: 'westeurope'
azureSubscription: 'azure-service-connection'
dotNetFramework: 'net6.0'
Build stage contains the job with the following tasks:
1. Install .NET 6 framework with Use Dot Net @ 2 task
Remember to add this task to install .NET 6 version. If you're using preview version of .NET 6 set 'includePreviewVersions' to true.
- task: UseDotNet@2
displayName: Install .NET Framework version
inputs:
packageType: 'sdk'
version: $(dotNetVersion)
includePreviewVersions: true
installationPath: $(Agent.ToolsDirectory)/dotnet
2. Build API application project
- task: DotNetCoreCLI@2
displayName: Build API application
inputs:
command: 'build'
projects: '**/WeatherForecastApi.csproj'
arguments: --output $(System.DefaultWorkingDirectory)/publish_output --configuration $(buildConfiguration)
3. Create API application artifact
- task: DotNetCoreCLI@2
displayName: Create API application artifact
inputs:
command: publish
publishWebProjects: True
arguments: '--configuration $(BuildConfiguration) --framework $(dotNetFramework) --output $(Build.ArtifactStagingDirectory)'
zipAfterPublish: True
4. Publish API application artifact
- task: PublishBuildArtifacts@1
displayName: Publish API application artifact
inputs:
PathtoPublish: "$(Build.ArtifactStagingDirectory)"
ArtifactName: "api"
publishLocation: Container
Deploy stage contains the job with the following tasks:
1. Download API application artifact
- task: DownloadBuildArtifacts@0
displayName: Download API application artifact
inputs:
buildType: 'current'
downloadType: 'single'
artifactName: 'api'
downloadPath: '$(System.ArtifactsDirectory)'
2. Deploy API application
- task: AzureWebApp@1
displayName: Deploy API application
inputs:
appType: webApp
azureSubscription: $(azureSubscription)
appName: 'app-weatherforecastapi'
package: "$(System.ArtifactsDirectory)/api/*.zip"
You can check full samples of YAML pipelines from here.
Summary
This was my first touch with Bicep and YAML. I have earlier worked with ARM templates and graphical editor of Azure DevOps to build pipelines. I'm very impressed how easy it was to generate infrastructure templates with Bicep and utilize Bicep in YAML based pipelines.. I would say that Bicep syntax is very developer friendly. Bicep Modules are also a nice way to structure templates and increase reusability. I'm definitely using Bicep in the future.