Overview
In todays tutorial we will take a look at implementing CI/CD with GitHub by using GitHub Actions to automate Azure Function deployment.
Bringing and maintaining our Azure Functions into a Git repository also brings all the benefits of version control, source code management and automated build and deployment of our functions into Azure though CI/CD.
Pre-requisites
To get started you'll need a few things, firstly:
- An Azure Subscription
- A GitHub Account and Git repository
You will also need to install a few local pre-requirements on the machine you will be working on. In my case I will prepare my machine for developing C# Functions, but you can take a look at some of the other code stacks here: Run Local Requirements
- Azure Functions Core Tools
- VSCode
- Azure Functions for Visual Studio Code
- C# for Visual Studio Code
Create an Azure Function App
Lets start by creating a resource group and a windows dotnet function app in our Azure subscription. For this step I have written a PowerShell script using Azure CLI. You can also find ths script on my GitHub repository.
#Log into Azure
az login
# Setup Variables.
$randomInt = Get-Random -Maximum 9999
$resourceGroupName = "GitHub-Managed-Function-Demo"
$storageName = "demofuncsa$randomInt"
$functionAppName = "demofunc$randomInt"
$region = "uksouth"
# Create a resource resourceGroupName
az group create --name "$resourceGroupName" --location "$region"
# Create an azure storage account for function app
az storage account create `
--name "$storageName" `
--location "$region" `
--resource-group "$resourceGroupName" `
--sku "Standard_LRS" `
--kind "StorageV2" `
--https-only true `
--min-tls-version "TLS1_2"
# Create a Function App
az functionapp create `
--name "$functionAppName" `
--storage-account "$storageName" `
--consumption-plan-location "$region" `
--resource-group "$resourceGroupName" `
--os-type "Windows" `
--runtime "dotnet" `
--runtime-version "6" `
--functions-version "4" `
--assign-identity
The above script created a resource group containing the function app, function app storage and insights as well as the consumption app service plan.
NOTE: We have only created our Function App at this stage, we do not have any Functions yet. We will create our first function later on in this tutorial with GitHub.
Create a GitHub repository
Next up head over to your GitHub account and create a new repository. We will use this repository to link our function app/s source code to.
I have called my repository Demo-Azure-Functions
Prepare Local Requirements
As mentioned at the beginning of this post we will now install and run a few pre-requirements on the machine we will be working and developing our function code on.
Install the following tools:
- Install Azure Function Core Tools
- Install VSCode
- Install Azure Functions extension for Visual Studio Code
- Install C# extension for Visual Studio Code
Clone GitHub Function repository
With all our tools now installed we can now clone our GitHub function repository to our local machine:
Paste in the copied clone URL and select a folder you want to clone the repository to. (Note: The repo will be cloned to a sub folder in the folder you selected, the name of this sub folder will match the repo name and will contain all your repo files.)
Link Azure Function App with GitHub Repository
Next we will create an empty folder inside of our locally cloned repository. This folder will represent our Function App:
NOTE: I have called my folder in my repo the same name as the name I have given to my Azure Function App we created earlier; demofunc6144.
Now we will create our first function inside of the folder using the Azure Functions extension for Visual Studio Code we installed earlier.
In VSCode you will see the extension installed on the left side of the screen. Click on the extension and select Create New Project:
This will then open the Command Palette again, browse to and select the empty folder we created representing our Function App:
The Command Palette will now present you with some options, select the following:
Once the above process has completed, notice that now we have a C# function app template in our folder demofunc6144 we can start working on straight away. Because this code is also now in our local git repository we can ensure that our code is always managed through source control.
Save and commit the the changes, then push the new function to the remote GitHub repository:
Deploy Function App
Now we have a fully integrated workspace we can use to create and develop Functions for our Function App. But we have not set up any CI/CD yet.
This brings us to the last step, automating the deployment of our Functions with CI/CD using GitHub Actions
Navigate back to the Function App hosted in Azure that we created earlier in this tutorial and go to Deployment Center:
Select Source GitHub and set the Org, Repo and Branch we created and hit Save. (NOTE: You will be asked to link your GitHub account if you are performing this step for the very first time):
NOTE: You can also manage the Publish Profile from the above step.
When you save on the configuration above, you will notice that on the GitHub repository there is a new automation workflow that is automatically created as well as a new repository secret.
The workflow will be located in a special folder called .github/workflows that is automatically created by Azure:
In my case the workflow was called master_decomfunc6144.yml:
Let's take a closer look at this workflow:
name: Build and deploy dotnet core app to Azure Function App - demofunc6144
on:
push:
branches:
- master
paths:
- 'demofunc6144/**'
workflow_dispatch:
env:
AZURE_FUNCTIONAPP_PACKAGE_PATH: 'demofunc6144' # set this to the path to your web app project, defaults to the repository root
DOTNET_VERSION: '6.0.x' # set this to the dotnet version to use
jobs:
build-and-deploy:
runs-on: windows-latest
steps:
- name: 'Checkout GitHub Action'
uses: actions/checkout@v3.6.0
- name: Setup DotNet ${{ env.DOTNET_VERSION }} Environment
uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: 'Resolve Project Dependencies Using Dotnet'
shell: pwsh
run: |
pushd './${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}'
dotnet build --configuration Release --output ./output
popd
- name: 'Run Azure Functions Action'
uses: Azure/functions-action@v1
id: fa
with:
app-name: 'demofunc6144'
slot-name: 'Production'
package: '${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}/output'
publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_AD8BBBC377A040C480EB918BC04CE61C }}
NOTE: I have added the workflow trigger to only trigger on my master branch and for any changes made under the folder/repo path demofunc6144. The workflow_dispatch: trigger allows us to additionally trigger and run the automation workflow manually.
#Trigger
on:
push:
branches:
- master
paths:
- 'demofunc6144/**'
workflow_dispatch:
Also note that I have changed the environment variables for the function app package path to demofunc6144
#Environment variables
env:
AZURE_FUNCTIONAPP_PACKAGE_PATH: 'demofunc6144' # set this to the path to your web app project, defaults to the repository root
DOTNET_VERSION: '6.0.x' # set this to the dotnet version to use
Let's take a look at what this automation workflow will do when it is triggered:
jobs:
build-and-deploy:
runs-on: windows-latest
steps:
- name: 'Checkout GitHub Action'
uses: actions/checkout@v3.6.0
- name: Setup DotNet ${{ env.DOTNET_VERSION }} Environment
uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: 'Resolve Project Dependencies Using Dotnet'
shell: pwsh
run: |
pushd './${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}'
dotnet build --configuration Release --output ./output
popd
- name: 'Run Azure Functions Action'
uses: Azure/functions-action@v1
id: fa
with:
app-name: 'demofunc6144'
slot-name: 'Production'
package: '${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}/output'
publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_AD8BBBC377A040C480EB918BC04CE61C }}
The above job has 4 steps:
- The code is checked out onto the GitHub runner.
- The version of .NET we specified in the environment variables will be installed on the GitHub runner.
- The Azure Function is built and packaged.
- The Function is deployed to the Azure Function App, demofunc6144 using the publish-profile of the Function App:
publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_AD8BBBC377A040C480EB918BC04CE61C }}
Note that the Publish Profile is actually stored as a GitHub Action Secret, this was also automatically created by Azure as part of the workflow YAML file:
NOTE: This Actions Secret is basically the contents of the Function Apps Publish Profile File which can be downloaded and re-added if ever needed manually:
Let's trigger this workflow manually and deploy our function into the Azure Function App. In GitHub navigate to Actions, select the workflow and then Run Workflow:
After the workflow has run, we can now see our Function in the Function App on Azure.
Conclusion
That's all there is to it, now we have successfully integrated our function app development lifecycle using source control with Git and GitHub and have the ability to automatically deploy our Functions using CI/CD workflows with GitHub Actions.
We can simply create additional folders for any new Function Apps along with a corresponding YAML workflow linked to deploy functions created for the relevant Function Apps in Azure.
I hope you have enjoyed this post and have learned something new. You can also find the code samples used in this blog post on my published GitHub page. ❤️
Author
Like, share, follow me on: 🐙 GitHub | 🐧 X/Twitter | 👾 LinkedIn
Top comments (5)
Well done and very helpful, thank you for putting this together.
So I'm giving making a function app my first try, but apparently I'm not seeing what I should be. I can create the function app server either through power shell or the Azure portal. That's fine. My issues seem to be with VS Code and the Azure Extension. I'm attaching an image of what I see when I click the Azure icon. I'm on version v1.7.3, which I gather is the most current. I do not see the "Functions" category as in your proj01.png graphic. Instead, I see "Workspace". And while I can click the + icon there, my only option is to create a new Function, not a project. When I click that, I get an error saying my directory is not a Function App directory, and do I want to create a new one. If I say yes, it dumps all the new files into the root of the folder created in earlier steps without providing an option to select the subfolder created after cloning the repo. Any ideas? Or is this something that changed between versions?
Hey :)
When you create a new project using the vscode extension it should ask you to browse to the folder you wish to create the template in:
res.cloudinary.com/practicaldev/im...
The problem is, I'm never given an option to select a folder. When I click the Azure extension icon, I get three options: "Resources", "Workspace", and "Help and Feedback". Workspace does have a plus, which when clicked on allows you to "Create Function..." but when clicked, presents the error "The selected folder is not a function project. Create new project?" The options on this error window are Yes and Cancel. Clicking yes allows you to create the project but just dumps the newly created files into the root of the original folder (the one cloned from the earlier steps from GitHub.) At no point does it give me the option to select the subfolder we created.
I'm curious is this is a version difference, or if I have something set incorrectly. (I've tried attaching images to this and the previous messages showing what I am seeing...)
EDIT - Ok, did some testing with various versions of the Azure Extension. The feature used in this tutorial under the Azure Extension: Functions, disappears with the introduction of version 1.7.0. It seems "Functions" gets replaced with "Workspace" and a lot of icons go away, including the menu option for New Project. Bummer.
Is there any way to automate this process of setting up the CI/CD for a function app. I'm trying to create a function app as part of a larger solution. Requring this manual Portal step is basically a show-stopper for us. Everything must be configurable through IAC for us to be compliant with our DR plan.