Serverless Go with Azure Functions and GitHub Actions
Tags: azure, go, tutorial
Published: 2022-09-11 14:56:36

---
title: Serverless Go with Azure Functions and GitHub Actions
published: true
date: 2022-09-11 14:55:00 UTC
tags: azure, go, tutorial
canonical_url: https://sahansera.dev/serverless-go-with-azure-functions-github-actions/
cover_image: https://sahansera.dev/static/90dd133bceddc4fcae6833ea6619fd47/26d86/serverless-go-with-azure-functions-github-actions.png
---

In this article, we are going to learn how to host a serverless Go web service with Azure Functions leveraging custom handlers in Azure functions. We will also automate the deployments by using GitHub Actions 🚀 You can even apply the same concepts to other languages Rust as long as you can self-contained binary.

## Walkthrough video

If you prefer to watch a video, I have created a walkthrough video on my YouTube channel 😊

 <iframe src="https://www.youtube.com/embed/0FqD8LTjHbg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen style=" position: absolute; top: 0; left: 0; width: 100%; height: 100%; "></iframe> 

{% embed https://www.youtube.com/watch?v=0FqD8LTjHbg %}

## Introduction

Azure Functions is Microsoft’s offering for serverless computing that enables you to run code on-demand without having to explicitly provision or manage infrastructure. Azure Functions is a great way to run your code in response to events, such as HTTP requests, timers, or messages from Azure services.

As of today, they support multiple [runtimes](https://docs.microsoft.com/en-us/azure/azure-functions/supported-languages) such as .NET, Java, JavaScript, Python, TypeScript. But what if we want to write our app in Go? Well, now you can do that too - by using Custom Handlers.

Before we move on, so what really is a custom handler and how does it work? Custom handlers let your Function app to accept events (eg. HTTP requests) from the Global host (aka [Function host](https://github.com/Azure/azure-functions-host) - that powers your Function apps) - as long as your chosen language supports HTTP primitives.

Here’s a great overview from Microsoft on how this is achieved.

![serverless-go-with-azure-functions-github-actions-1](https://sahansera.dev/static/ea7a2923719d4c72709b548b69167ecf/5a190/serverless-go-with-azure-functions-github-actions-1.png "serverless-go-with-azure-functions-github-actions-1")

_Source: [Microsoft Docs](https://docs.microsoft.com/en-us/azure/azure-functions/functions-custom-handlers)_

So, in our case, we are going to wrap a Go binary as a Function app and deploy it to Azure. Sounds good? Let’s jump right into it.

## Prerequisites

Make sure you have the following setup locally.

- [Go SDK](https://go.dev/dl/)
- [Azure Functions Core Tools](https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=v4%2Cmacos%2Ccsharp%2Cportal%2Cbash#install-the-azure-functions-core-tools)
- Prefererrably, Azure Functions [VS Code extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions)

## The plan

1. Clone the repo or create one
2. Create the Azure function resources
3. Test out the app
4. Deploy to Azure

## 1. Clone the repo or create one

Our code is going to be pretty simple. All it does is, whenever we make a request, it will return a random quote on programming.

> 💡 You can clone the repo I have created from [here](https://github.com/sahansera/go-azure-functions).

```go
package main

// Removed for brevity

var quotes = []string{
	"Talk is cheap. Show me the code.",
	"First, solve the problem. Then, write the code.",
	"Experience is the name everyone gives to their mistakes.",
	"Any fool can write code that a computer can understand. Good programmers write code that humans can understand.",
}

func quotesHandler(w http.ResponseWriter, r *http.Request) {
	// Get a random quote
	message := quotes[rand.Intn(len(quotes))]

	// Write the response
	fmt.Fprint(w, message)
}

func main() {
	listenAddr := ":8080"
	if val, ok := os.LookupEnv("FUNCTIONS_CUSTOMHANDLER_PORT"); ok {
		listenAddr = ":" + val
	}
	http.HandleFunc("/api/GetQuotes", quotesHandler)
	log.Printf("About to listen on %s. Go to https://127.0.0.1%s/", listenAddr, listenAddr)
	log.Fatal(http.ListenAndServe(listenAddr, nil))
}
```

**Explanation**

- We setting port `8080` as the default port for our app. But if we are running this in Azure Functions, we will be using a different port. So, we are checking if the `FUNCTIONS_CUSTOMHANDLER_PORT` environment variable is set. If it is, we will use that port instead.
- We are registering a handler for the `/api/GetQuotes` endpoint. This is the endpoint that we will be using to make requests to our app.
- `quotesHandler` is a simple function that returns a random quote from the `quotes` array.

## 2. Create the Azure function resources

You can create your own Azure function from [portal.azure.com](http://portal.azure.com) or using ARM templates. Below are the configuration I chose.

- Publish: Code
- Runtime Stack: Custom Handler
- Operating System: Linux
- Plan type: Consumption (Serverless)

## 3. Test out the app

If you have cloned the project you should see a folder structure similar to what’s shown below.

![serverless-go-with-azure-functions-github-actions-2.png](https://sahansera.dev/static/e359e32c161b4ba7a9ff08f5e10a60e1/9f21b/serverless-go-with-azure-functions-github-actions-2.png "serverless-go-with-azure-functions-github-actions-2.png")

At the root of the project folder let’s run the following commands.

```sh
go build handler.go # To build a binary
func start # Start the Function app service
```

You should see the output like so:

![serverless-go-with-azure-functions-github-actions-3.png](https://sahansera.dev/static/ff84465da4d2aa34c75ab1f599764e54/5a190/serverless-go-with-azure-functions-github-actions-3.png "serverless-go-with-azure-functions-github-actions-3.png")

Let’s run through each file now.

- [GetQuotes/function.json](https://github.com/sahansera/go-azure-functions/blob/main/GetQuotes/function.json): This file defines what happens when a request comes in and what should go out from the function. These are known as **_bindings._** Our function is triggered by HTTP requests and we will return a response
- [handler.go](https://github.com/sahansera/go-azure-functions/blob/main/handler.go): This is our Go web service where our main logic lives in. We listen on port 8080 and expose an HTTP endpoint called `/api/GetQuotes`
- [host.json](https://github.com/sahansera/go-azure-functions/blob/main/host.json): Take note under `customHandler.description.defaultExecutablePath` is set to handler which says where to find the compiled binary of our Go app and `enableForwardingHttpRequest` where we tell the function host to forward our traffic

## 4. Deploy to Azure with GitHub Actions

Now that we have everything ready to go let’s deploy this to Azure! 🚀  To get this done, we are going to use the [Azure Functions Action](https://github.com/marketplace/actions/azure-functions-action) from the GH Actions Marketplace.

From a high-level this is what we need to do in order to deploy this.

1. Authenticate with Azure
2. Build the project
3. Deploy

**Authenticate with Azure**

Since our app is written in “Go” which is not really supported out of the box, we won’t be able to use the Publish Profile method for this. So we are going to focus on using an Azure Service Principal for RBAC.

> 💡 Remember to follow the steps according to [this guide](https://github.com/marketplace/actions/azure-functions-action#using-azure-service-principal-for-rbac-as-deployment-credential) to create an SP.

1. Once you have created the service principal, we need to add that as a secret in our repo so that it can be used for authenticating with Azure Resource Manager during the deployment step. Head over to your repo → Settings → Secrets → Actions
2. Create a secret a called `AZURE_RBAC_CREDENTIALS` and update its content with what you got when you created the service principal.

Now the that the credentials are in place we can refer to that from the GitHub Action workflow file.

**Build the project**

This step is pretty straightforward as we are using the Go SDK to build the project with `GOOS=linux GOARCH=amd64` config.

This is what the final workflow file should look like:

[deploy.yml](https://github.com/sahansera/go-azure-functions/blob/main/.github/workflows/deploy.yml)

```yaml
name: CI/CD

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]
    
env:
  AZURE_FUNCTIONAPP_NAME: azgofuncapp # set this to your application's name

jobs:

  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: 'Login via Azure CLI'
      uses: azure/login@v1
      with:
        creds: ${{ secrets.AZURE_RBAC_CREDENTIALS }}

    - name: 'Set up Go'
      uses: actions/setup-go@v3
      with:
        go-version: 1.18

    - name: Build
      run: GOOS=linux GOARCH=amd64 go build handler.go
      
    - name: 'Deploy to Azure'
      uses: Azure/functions-action@v1
      with:
        app-name: ${{ env.AZURE_FUNCTIONAPP_NAME }}
```

Once everything is done, head over to the following URL and refresh a couple of times to see our programming quotes! 😀

```sh
https://<your_app_URL>.azurewebsites.net/api/GetQuotes
```

Here’s the an example.

![serverless-go-with-azure-functions-github-actions-4.png](https://sahansera.dev/static/3319e08116dcac501fe741acd7da9027/5a190/serverless-go-with-azure-functions-github-actions-4.png "serverless-go-with-azure-functions-github-actions-4.png")

## Conclusion

Well, that’s it folks! Today we built a small Go web service, wrapped it in an Azure Function and deployed it to Azure by using GitHub Actions!

## Troubleshooting

I ran into couple of issues when creating this project.

1. Are you getting the `Value cannot be null. (Parameter 'provider')` error? 

I was able to resolve it by following the exact config as below.

```json
{
  "version": "2.0",
  "logging": {...},
  "customHandler": {
    "description": {
      "defaultExecutablePath": "handler",
      "workingDirectory": "",
      "arguments": []
    },
    "enableForwardingHttpRequest": true
  }
}
```

1. Are you getting the `Azure Functions Runtime is unreachable` error?

For me, this went away when I did the first deployment. If it still doesn’t go away, check out this [link](https://docs.microsoft.com/en-us/azure/azure-functions/functions-recover-storage-account) for more info.

## References

- [https://docs.microsoft.com/en-us/azure/azure-functions/create-first-function-vs-code-other?tabs=go%2Cmacos](https://docs.microsoft.com/en-us/azure/azure-functions/create-first-function-vs-code-other?tabs=go%2Cmacos)
- [https://docs.microsoft.com/en-us/azure/azure-functions/event-driven-scaling](https://docs.microsoft.com/en-us/azure/azure-functions/event-driven-scaling)
- [https://github.com/Azure/azure-functions-dotnet-worker/issues/810](https://github.com/Azure/azure-functions-dotnet-worker/issues/810)
- [https://github.com/marketplace/actions/azure-functions-action#using-azure-service-principal-for-rbac-as-deployment-credential](https://github.com/marketplace/actions/azure-functions-action#using-azure-service-principal-for-rbac-as-deployment-credential)