In this post I’ll show you how to deploy the .NET 5 “Out of process” azure functions using Pulumi. We’ll be using a GitHub action to build the code, which will also create the infrastructure too, then deploy the function to that infrastructure. In this example, we’ll be using a Azure Blob Storage to store the state of our Pulumi stack.
If you’d just like to view the solution, you can find the code here: martinjt/pulumi-dotnet5 (github.com)
What is Pulumi?
Pulumi is an Infrastructure as Code framework that allows you to declare your infrastructure in the same language you do your code. In this case, we’ll be writing it in C# to match the code of our function.
What are Github Actions?
Github Actions are free Build runners provided by GitHub that run against your Github repo. You currently get 2000 free build minutes for each of your personal accounts. You can use them to do anything, and we’ll be using them to build and deploy our code to Azure for free!
Pre-requisites
For this tutorial, you’ll need:
- An Azure Subscription to deploy to (You’ll need Contributor rights for Storage Accounts, Blobs, AppService/Function apps)
- An Azure Storage blob for storing the state
- Access to (or the ability to create) a service principal.
- Fork of the repository at https://github.com/martinjt/pulumi-dotnet5
Step 1 – Creating a Service Principal
You’ll need to have a service principal that Pulumi can use to create the resources in Azure. Behind the scenes, Pulumi is hitting the Azure REST API, and for that it needs credentials
If you have a service principal, then you can skip this part.
The easiest way I’ve found to do this is using the Azure CLI. Note that currently, the CLI is creating a Service Principal with Contributor permissions, however, there is a message saying this will not always be the case. You’ll need Contributor permissions for this.
az login
az account set -s <subscriptionId>
az ad sp create-for-rbac --name pulumi-tests
In the above commands, you’ll notice that I’m setting the subscription as default. This is something that I would recommend, but you could equally just pass the subscriptionId
on the command to create the principal.
This should return you a JSON blob that looks like this:
{
"appId": "",
"displayName": "pulumi-tests",
"name": "http://pulumi-tests",
"password": "",
"tenant": ""
}
Keep hold of this as we’ll need it in a subsequent step.
Step 2 – Hosted State file setup
We’ll be storing our Pulumi state file in Azure Blob Storage, so that will need creating manually.
Pulumi maintains an “internal” dictionary of all the resources it’s created as part of the stack, and how those map to the things it needs to create. As GitHub actions don’t have a shared place for these things, we need a persistent store for them. We’ll be using Azure Blob for this.
You’ll need a storage account (using an existing one is completely fine). I’d recommend storing this in the same subscription as the infrastructure it’s managing (i.e. if you have a subscription for Dev, test and prod, put the state files for each environment in those).
The only recommendation around creating this I would have is disabling the public access.
Once you have a Storage account, you’ll need a blob creating within there. Note down the names of both the storage account, and the blob as these will be need in the subsequent steps.
Step 3 – Github Secrets
For the pipeline to run, you’ll need to add a couple of secrets that will grant access to the Azure Blob storage, the Infrastructure, and also encrypt the state created by Pulumi. You’ll also need the variables for the Storage account name, and the blob name.
Within your fork, setup the following secrets in => Settings => Secrets => New Repository Secret.
Note: This works equally well with Organisation or Environment secrets if you’re using them.
Service Principal
The Github Action workflow is setup to use a secret called AZURE_PRINCIPAL
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_PRINCIPAL }}
This comes from the Action provided by azure, and expects the following json format:
{
"clientId": "<appId>",
"clientSecret": "<password>",
"tenantId": "<tenant>",
"subscriptionId": "<subscriptionId>"
}
These should all be available from the principal that was setup in Step 1.
Pulumi Secret
You’ll need to provide pulumi with a “key” for which it will use to encrypt the state of your workflow. This is set as PULUMI_PASSPHRASE
and can be any string
Storage Account
Next you’ll need to provide a valid storage account (that the principal supplied has access to). This is done with the STATE_STORAGE_ACCOUNT
secret
Storage Blob
This is the name of the blob used for the Pulumi State file, and is set as STATE_STORAGE_BLOB
. You need to ensure that the Service Principal supplied has Read, Write and List permissions to the blob.
Step 4 – Run the workflow
That’s it, you should now be able to run the workflow from the menus.
The important bits
There are a few important things to note when creating both Azure functions and .NET 5 Azure Functions with Pulumi.
Functions Runtime
When you’re deploying an Azure Function with .NET 5, you’ll need to make sure that you set the AppSettings FUNCTIONS_WORKER_RUNTIME
to dotnet-isolated
Changing Blobs on build
The solution I’ve provided uses the WEBSITE_RUN_FROM_PACKAGE
AppSetting that points to a blob in Blob storage. This provides a nice separation of the code and function. However, due to the way that pulumi works, the Blob
resource is not marked as updated, and therefore does not change the URLs. As the URL to the blob doesn’t change, the Function App will not pick up the new code.
To workaround this, I’ve added a DateTime to the blob’s name, so it’s updated on every build. I would recommend having this use the commit hash instead.
This does still have limitations in that old Function Apps could start errors as the old blob is being deleted.
Don’t publish your Infrastructure code
When you do a dotnet publish
in your application, make sure that you target the application’s project, and not the solution. If your solution file contains both the application and the infrastructure, publishing the solution will result in around 200MB of extra libraries that you don’t need. This quickly fills up your GitHub data allowance.
Conclusion
Deploying an Azure function with Pulumi is pretty easy, adding in the complexity of Github Actions/Workflows is actually pretty easy too. There are a fewhop incantations that you need in order for the Action to access azure, but other than that, it was fairly smooth sailing.
I hope that forking and deploying this repo makes it easier to understand what’s needed.
Top comments (0)