Github Actions and Bicep is fantastic and here is why…
Krzysztof Lach
Posted on March 16, 2023
From last few years I was mainly working with Azure Pipelines and Ansible as infrastructure as code. From that time I was thinking that Azure Pipelines with Ansible is a very good duo. Especially that project that I worked on invested a tons of money and time to build a robust, innovative, easy to use automation tools. With few easy steps you could import predefined roles, define parameters and deploy Azure building blocks. All of that made me just fall in love with „the framework”.
Whole automation that was build around it was amazing. Of course „the framework” has pros and cons but for me switching from CLI, ARM templates and json that many companies are still using was a game changer - even thought it took me a while to understand mechanisms hidden behind a nice developer friendly layer of abstraction.
Few weeks ago I decided to use different tools that I can use when building infrastructure as code. I wanted to go outside my comfort zone and get better understanding what is going on with actual industry trends. After small reaserch I decided that I will go with Github Actions and Microsoft Bicep.
I simply decided to use Github Actions because I’m keeping all my side projects on Github - probably like most of IT folks. I started going trough documentation and I was amazed how well it is structured and how easy I can find useful stuff. There is also a strong community behind Github Actions that is actively supporting idea behind moving Azure Pipelines to Github. There are many free tools for workflows and actions yaml syntax validation that are extremely useful.
To make Github Actions complementary for Azure I used Bicep language. I liked the idea that Bicep is domain specific language for deploying Azure resources. To simply combine Actions with Bicep you need only one action role azure/arm-deploy. But why Bicep is so cool? Bicep files are often one third of the JSON ARM template, that most of the time is unreadable for humans.
I would say that Bicep templates syntax is very similar to common programming languages like C# or TypeScript - so for me prototyping deployments was very easy to jump in.
Ok but enough of cheap talking… so without further it ado let’s jump straight to the code.
For sake of simplicity we will use Python + FastAPI (To be honest because I'm learning Python lately ^^). Here is my project structure:
The source of our pipeline is in main.yml file where pipeline has it's starting point. We sliced our workflow in three stages [build, deploy infrastructure, deploy].
- Infrastructure step simply is deploying App Service infrastructure to our Azure Subscription.
infrastructure_deploy:
if: github.event.inputs.deploy_infrastructure == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: Example.Python.Api
steps:
- uses: actions/checkout@v3
- name: Az Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Bicep deploy infrastructure
id: infrastructure-deployment
uses: azure/arm-deploy@v1
with:
scope: subscription
subscriptionId: ${{ secrets.AZURE_SUBSCRIPTION }}
region: westeurope
template: ./.github/workflows/infrastructure/main.bicep
parameters: project=python-example-api-aps location=westeurope appName=${{github.event.inputs.appName}}
deploymentName: aps-python-bicep-demo
- Build step build and save our build output as artefact that in the final deployment step we will use to publish our app to cloud.
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Az Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Set up Python 3.11
uses: actions/setup-python@v3
with:
python-version: "3.11"
- name: Create and start virtual environment
run: |
python -m venv venv
source venv/bin/activate
- name: Set up dependency caching for faster installs
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Install dependencies
run: pip install -r requirements.txt
- name: Upload artifact for deployment jobs
uses: actions/upload-artifact@v3
with:
name: python-app
path: |
.
!venv/
- Azure deployment
deploy:
runs-on: ubuntu-latest
needs: build
environment:
name: 'production'
steps:
- name: Az Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Download artifact from build job
uses: actions/download-artifact@v3
with:
name: python-app
path: .
- name: Retrieve publish profile for deployment
id: publishProfileRetrieval
run: |
publishProfiles=$(az webapp deployment list-publishing-profiles \
--name app-api-${{github.event.inputs.appName}} \
--resource-group "ziqq-test-rg2" \
--subscription "${{ secrets.AZURE_SUBSCRIPTION }}" --xml)
echo "::add-mask::$publishProfiles"
echo "::set-output name=publishProfiles::$publishProfiles"
- name: Print pwd
run: pwd
- name: 'Deploy to Azure Web App'
id: deploy-to-webapp
uses: azure/webapps-deploy@v2
with:
images: ''
app-name: app-api-${{github.event.inputs.appName}}
publish-profile: ${{ steps.publishProfileRetrieval.outputs.publishProfiles }}
- name: logout
run: |
az logout
Now lets take a look at the fantastic Bicep files structure:
- As entry point we have main.bicep file where like before all our infrastructure deployments steps are described.
- params.bicep file describes what type of parameters our pipeline can take and where to pass them.
- Folder modules contains all templates needed to run App Service infrastructure.
main.bicep
targetScope = 'subscription'
param location string
param project string
param appName string
resource rg 'Microsoft.Resources/resourceGroups@2022-09-01' = {
name: 'ziqq-test-rg2'
location: location
}
module resources 'params.bicep' = {
name: 'deploy-${project}-resources'
scope: rg
params: {
location: location
project: project
appName: appName
}
}
output rgName string = rg.name
params.bicep
param location string
param project string
param appName string
module appServicePlan 'modules/app-service-plan.bicep' = {
name: 'deploy-plan'
params: {
location: location
project: project
}
}
module appService 'modules/app-service.bicep' = {
name: 'deploy-app-service'
params: {
appName: appName
location: location
planId: appServicePlan.outputs.planId
}
}
app-service-plan.bicep
param location string
param project string
resource plan 'Microsoft.Web/serverfarms@2022-03-01' = {
name: 'asp-${project}'
location: location
kind: 'app,linux'
properties: {
reserved: true
}
sku: {
name: 'S1'
}
}
output planId string = plan.id
app-service.bicep
param location string
param appName string
param planId string
resource app 'Microsoft.Web/sites@2022-03-01' = {
name: 'app-api-${appName}'
location: location
properties: {
serverFarmId: planId
reserved: true
siteConfig: {
linuxFxVersion: 'PYTHON|3.11'
}
}
}
output publishProfile string = ''
Azure Bicep team did a great job developing modern and developer friendly way to deploy infrastructure to Azure. I found using Bicep and Github Actions very easy and satisfying. Debugging and maintaining JSON ARM Templates in early stages of infrastructure as code way of dealing with cloud was a nightmare for me so I appreciate even more frameworks/languages like this. I will definitely continue to work with both technologies in my personal projects.
Posted on March 16, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.