Posted on March 11, 2023
We already talked about the implementation of Azure Function App in a previous article
In this tutorial, we will be deploying our sample function app through a multi-stage pipeline. Let's walk through with very beginning by creating an Azure DevOps account.
Note: I will ensure that all resources in Azure are cleaned up by the time you read this article.
Let's see the steps we will be covering here.
- Setup your Azure DevOps environment
- Create the Azure function environment
- Use Cloud Shell through the Azure portal
- Create a Bash variable
- Create Azure resources through the Bash variable
- Create pipeline variable in Azure Pipeline
- Create an environment
- Create a service connection
- Use Cloud Shell through the Azure portal
- Deploy an Azure Functions app to Azure
Setup your Azure DevOps environment
- Create an Azure DevOps account: If you don't have an Azure DevOps account yet, you can create one by signing up on the Azure DevOps website.
- Create a project: Once you have created your Azure DevOps account, you can create a new project. This is where you will manage all of your code, work items, and build/release pipelines. Here are detailed steps
Create the Azure function environment
Create a Free Azure subscription to create an environment of Azure function
Use Cloud Shell through the Azure portal
Create a Bash variable
- Configure the default region to deploy
az configure --defaults location=eastus
- Generate a random number, which we will use to create globally unique names for certain services in the next step.
resourceSuffix=$RANDOM
- Globally unique names for your Azure Function, and storage accounts.
sampleFunctionName="sample-func-${resourceSuffix}"
storageName="samplefuncstorage${resourceSuffix}"
- Create 1 more Bash variable to store the names of your resource group and service plan.
rgName='codehack-rg'
Create Azure resources through Bash variable
- Create a storage account using the above global variables
az storage account create \
--name $storageName \
--resource-group $rgName \
--sku Standard_LRS
- Create a storage account using the above global variables
az functionapp create \
--name $sampleFunctionName \
--resource-group $rgName \
--storage-account $storageName \
--functions-version 4 \
--consumption-plan-location eastus
- See if the function app running
az functionapp list \
--resource-group $rgName \
--query "[].{hostName: defaultHostName, state: state}" \
--output table
Create pipeline variable in Azure pipeline
- Go to the Azure DevOps project and under the pipeline, select "Library"
- Select Variable Group
- Enter group name Release
- Click + to add a variable, and add two variables (You can find these values in the Azure portal )
ResourceGroupName | codehack-rg
sampleFunctionName | sample-func-16527
- Click "Save"
Create an environment
- Go to the "Environment"
- Type the Name of the Environment e.g. "spike"
- Click "Create" with default selection
Create a service connection
- Go to project settings
- Select Service Connection -> New Service Connection
- Select **"Azure Resource Manager" ** and select "Next"
- Select "Service Principle"
- Select your Azure Subscription -> Give the name of the subscription e.g. "Resource Manager - Function - Demo"
Deploy an Azure Functions app to Azure
- From Azure DevOps, navigate to Pipelines.
- New Pipeline -> Select Repos Git -
- Select the Starter pipeline and then modify it as per the below example.
Let's see Multi-Stage Pipeline
Sample below is a template but not actual implementation
Which will deploy Build Angular Application, Deploy Function App, and then Deploy Angular App
# Multi-stage pipeline to deploy Angular app and C# function app to Azure
trigger:
- main
stages:
- stage: BuildAngular
displayName: 'Build Angular App'
jobs:
- job: Build
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
inputs:
versionSpec: '14.x'
displayName: 'Install Node.js'
- script: |
npm install -g @angular/cli
npm install
displayName: 'Install Angular CLI and dependencies'
- script: |
ng build --prod
displayName: 'Build Angular app'
- stage: DeployFunctionApp
displayName: 'Deploy Function App'
dependsOn: BuildAngular
jobs:
- job: Deploy
pool:
vmImage: 'ubuntu-latest'
variables:
azureSubscription: '<Azure subscription name>'
resourceGroupName: '<resource group name>'
functionAppName: '<function app name>'
region: '<region name>'
steps:
- task: DotNetCoreCLI@2
inputs:
command: 'publish'
publishWebProjects: true
projects: '**/*.csproj'
arguments: '-o $(Build.ArtifactStagingDirectory)'
displayName: 'Publish Function App'
- task: AzureRmWebAppDeployment@4
inputs:
ConnectionType: 'AzureRM'
azureSubscription: $(azureSubscription)
appType: 'functionApp'
appName: $(functionAppName)
package: '$(Build.ArtifactStagingDirectory)/**/*.zip'
runtimeStack: DOCKER|microsoft/azure-functions-dotnet:4
ResourceGroupName: $(resourceGroupName)
SlotName: 'production'
region: $(region)
- stage: DeployAngularApp
displayName: 'Deploy Angular App'
dependsOn: BuildAngular
jobs:
- job: Deploy
pool:
vmImage: 'ubuntu-latest'
variables:
azureSubscription: '<Azure subscription name>'
resourceGroupName: '<resource group name>'
webAppName: '<web app name>'
region: '<region name>'
steps:
- task: AzureWebApp@1
inputs:
azureSubscription: $(azureSubscription)
appType: 'webAppLinux'
appName: $(webAppName)
package: '$(System.DefaultWorkingDirectory)/dist/<angular app name>'
runtimeStack: 'NODE|14-lts'
region: $(region)
resourceGroupName: $(resourceGroupName)
You'll need to replace the placeholder values (
<Azure subscription name>
,<resource group name>
,<function app name>
,<web app name>
,<region name>
, and<angular app name>
) with your actual values.
Following tweaks you might need to do it here.
- Also, you replace the variable section like the following to read the variable from Azure DevOps (It's already defined above)
variables:
- group: Release
- You can define the environment from Azure DevOps (It's already defined above)
environment: spike
- and Azure subscription name from Azure DevOps (It's already defined above)
inputs:
azureSubscription: 'Resource Manager - Function - Demo'
- Make sure you
$(functionAppName)
and$(webAppName)
$(webAppName): sample-func-web-16527
$(functionAppName): sample-func-16527