Skip to content

MassTransit on Azure Functions with Azure Service Bus - Isolated, local dev environment

License

Notifications You must be signed in to change notification settings

matei-tm/sample-local-development-with-azure-service-bus-and-azure-functions

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

54 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Description

The current repo serves as a playground to demonstrate how to switch an Azure Service Bus to RabbitMq in the context of an Azure Function and local development environment. It is trying to solve the lack of a local emulator for Azure Service Bus. The issue is mentioned by Jimmy Bogard in this article. It provides a local, completly isolated dev environment for Azure Functions inside local Kubernetes infrastructure.

The proposed solution is using:

  • a MassTransit wrapper for Azure Function RabbitMq trigger
  • a Kubernetes in Docker infrastructure
  • KEDA for event driven scaling of Azure Functions instances
  • Azure Application Insights (optional) for monitoring

The switching procedure is based on a custom configuration (named LocalDev) that is conditionally processed in the Azure Functions csproj files.

Note: Because RabbitMq does not have an equivalent for the ASB topic, the topics will be simulated as queues (work in progress to switch to exchange and keys)

Solution diagram

diagram

Solution structure

├───docker-files
├───docs
├───k8s
└───src
    ├───AzureFunction.MassTransit.Demo.Core
    │   └───Consumers
    ├───AzureFunction.MassTransit.Dual.DemoQueue
    │   └───Properties
    ├───AzureFunction.MassTransit.Dual.DemoTopic
    │   └───Properties
    ├───EndpointCreator.MassTransit.Rmq.Demo
    │   └───Properties
    ├───HeroDomain.Contracts
    ├───Publisher.MassTransit.Demo.Core
    └───Publisher.MassTransit.Dual.Demo
        └───Properties

Required nuget feed

The demo is using https://www.myget.org/F/matei-tm/api/v2 feed for the prerelease nugets of MassTransit.WebJobs.Extensions.RabbitMQ.
See the Nuget.Config file.

The dual behavior of the AzureFunction.MassTransit.Dual.Demo* csproj files

The Azure functions project files are containing package references for both Asb and RabbitMq, but depending on the configuration value, at build time, they will generate a clean assembly.

fig

The dual behavior of the AzureFunction.MassTransit.Dual.DemoQueue SubmitOrderFunction class

The conditional compilation is done by using #if/#else/#endif preprocessor directives

fig

The dual behavior of the AzureFunction.MassTransit.Dual.DemoTopic SubmitOrderFunction class

The conditional compilation is done by using #if/#else/#endif preprocessor directives

fig

Prerequisites

For the sake of simplicity, the installation tasks will be performed with:

Mandatory: An Azure subscription

The current demo can be completed with a minimal subscription. It can be:

  • a student version with 100$ credit
  • a dev essentials version with 200$ credit
  • a pay-as-you-go (depending on the speed of progress, it will charge less than 20$)

Mandatory: Azure CLI (az)

Check here for installation instructions.

Windows setup

# open an administrative Power Shell console
choco install azure-cli

MacOS/Linux setup

brew install azure-cli

Notes: Azure CLI

If you are using the local installation of the Azure CLI and you are managing several tenants and subscriptions, run the az login command first and add your subscription. See here different authentication methods.

# accessing from localhost
az login

Mandatory: Kubernetes CLI (kubectl)

Check here for installation instructions.

Windows setup

# open an administrative Power Shell console
choco install kubernetes-cli

MacOS/Linux setup

brew install kubernetes-cli

Helm

Check here for installation instructions.

Windows setup

# open an administrative Power Shell console
choco install kubernetes-helm

MacOS/Linux setup

brew install kubernetes-helm

Mandatory: Docker CLI

In order to be able to build the custom images containing disks with Iso files, the docker CLI is needed. Install Docker Desktop on your localbox. Depending on your OS use the proper installation guide:

Azure Setup

If you want to play with Azure Service Bus, a preliminary setup is required. The following actions are using the Azure CLI. As well you can use the Azure portal to complete them. Finally we will need:

  • A service bus namespace
  • A queue named OrdersQueue
  • A topic named OrdersTopic
  • A subscription OrdersSubscription to the OrdersTopic
  • A connection string to the service bus

Create a resource group

All the resources will be created under a single resource group named k8s. Having everything in one basket will permit to purge all resources in a single step and cut all the subsequent costs. The following command is using westeurope as location. If it's the case, change it according to your own needs.

az group create --location westeurope -n k8s 

image

Create a service bus namespace

Run the following command to create a Service Bus messaging namespace. In order to avoid naming conflicts generated by another player of this demo, replace DemoSb04 with your own name of the Service Bus namespace.

az servicebus namespace create --resource-group k8s --name DemoSb04 --location westeurope

image

Create a queue OrdersQueue

Run the following command to create a queue in the namespace you created in the previous step.
Replace DemoSb04 with your own name of the Service Bus namespace.

az servicebus queue create --resource-group k8s --namespace-name DemoSb04 --name OrdersQueue

image

Create a topic OrdersTopic

Run the following command to create a topic in the namespace you created in the previous step.
Replace DemoSb04 with your own name of the Service Bus namespace.

az servicebus topic create --resource-group k8s --namespace-name DemoSb04 --name OrdersTopic

image

Create OrdersSubscription subscription for OrdersTopic

Run the following command to create a subscription to the topic you created in the previous step.
Replace DemoSb04 with your own name of the Service Bus namespace.

az servicebus topic subscription create --resource-group k8s --namespace-name DemoSb04 --topic-name OrdersTopic --name OrdersSubscription

image

Get the Azure Service Bus connection string

Run the following command to get the primary connection string for the namespace. You will use this connection string to connect to the queue/topic and send and receive messages. Replace DemoSb04 with your own name of the Service Bus namespace.

az servicebus namespace authorization-rule keys list --resource-group k8s --namespace-name DemoSb04 --name RootManageSharedAccessKey --query primaryConnectionString --output tsv

Note down the connection string and the queue name. You use them to send and receive messages.

image

Create an App Insights resource

If you want to monitor the applications create an App Insights resource and provide the connection information to the configuration files.

az extension add -n application-insights
az monitor app-insights component create --app demoApp --location westeurope --kind web --resource-group k8s --application-type web

image

Get the App Insights connection

# bash/zsh
az monitor app-insights component show --app demoApp --resource-group k8s | grep connection

image

Get the App Insights instrumentation key

# bash/zsh
az monitor app-insights component show --app demoApp --resource-group k8s | grep instrumentationKey

Local machine setup

Clone the repo

Clone the repo and switch to that folder

git clone https://github.com/matei-tm/AzureFunction.Demo
cd AzureFunction.Demo

Switch to Docker context for Kubernetes

Assure that current kubectl context is pointing to docker desktop

kubectl config use-context docker-desktop

Install KEDA

Setting Kubernetes-based Event Driven Autoscaler

helm repo add kedacore https://kedacore.github.io/charts
helm repo update

kubectl create namespace keda
helm install keda kedacore/keda --namespace keda

Install Dashboard

kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.4.0/aio/deploy/recommended.yaml

Troubleshoot. Check the process that is owner on port 8001

If the port 8001 is captured by another app, get the app name, verify it and close/kill it.

# Powershell
$theCulpritPort="8001"
Get-NetTCPConnection -LocalPort $theCulpritPort `
| Select-Object -Property "OwningProcess", @{'Name' = 'ProcessName';'Expression'={(Get-Process -Id $_.OwningProcess).Name}} `
| Get-Unique

Start the dashboard

kubectl proxy

Setup access

kubectl apply -f k8s/adminuser.yaml
kubectl apply -f k8s/clusterrolebinding.yaml

Refresh the token

http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/

Access to the dashboard is expiring after a period of inactivity. In order to generate a new token, execute the following command in bash terminal

kubectl -n kubernetes-dashboard get secret $(kubectl -n kubernetes-dashboard get sa/admin-user -o jsonpath="{.secrets[0].name}") -o go-template="{{.data.token | base64decode}}"

Docker local registry

We need a local docker registry to host the images. Create a local registry

docker run -d -p 5000:5000 --name registry registry:2

RabbitMq

For the RabbitMq service, a Docker container will be used

docker run -d --hostname my-rabbit --name some-rabbit -p 8080:15672 -p 5672:5672 rabbitmq:3-management

Connection string for Azure Functions will use host.docker.internal (amqp://guest:guest@host.docker.internal:5672)
Connection string for Publisher will use localhost 127.0.0.1 ip (amqp://guest:guest@127.0.0.1:5672)
To access the RabbitMq Dashboard open http://127.0.0.1:8080/ with username:guest and password:guest

Review and update the configuration files

Replace the connection strings for ASB/RabbitMq/AppInsights in the following files. Use the values provided in previous steps

.
├── docker-files
├── docs
├── k8s
└── src
    ├── AzureFunction.MassTransit.Demo.Core
    │   └── Consumers
    ├── AzureFunction.MassTransit.Dual.DemoQueue
    │   ├── Properties
    │   ├── host.json
    │   └── local.settings.json
    ├── AzureFunction.MassTransit.Dual.DemoTopic
    │   ├── Properties
    │   ├── host.json
    │   └── local.settings.json
    ├── EndpointCreator.MassTransit.Rmq.Demo
    │   ├── Properties
    │   ├── appsettings.Development.json
    │   ├── appsettings.json
    │   └── host.json
    ├── HeroDomain.Contracts
    ├── Publisher.MassTransit.Asb.Demo
    │   ├── Properties
    │   ├── appsettings.Development.json
    │   └── appsettings.json
    ├── Publisher.MassTransit.Demo.Core
    └── Publisher.MassTransit.Dual.Demo
        ├── Properties
        ├── appsettings.Development.json
        ├── appsettings.json
        └── host.json

Optional, the secrets for publishers can be provided through development safe storage feature.

Configuration keys explained

File local.settings.json

Key Description Retrieve section
APPINSIGHTS_INSTRUMENTATIONKEY App Insights instrumentation key view
APPLICATIONINSIGHTS_CONNECTION_STRING App Insights connection string view
AzureWebJobsServiceBus the ASB endpoint for the Azure Function trigger view
ServiceBus the ASB endpoint for the consumers configured to handle the message received by the trigger. The key name is the default value requested by MassTransit view

File appsettings*.json

Key Description Retrieve section
ApplicationInsights.InstrumentationKey App Insights instrumentation key view
AppConfig-ServiceBus.ServiceBusConnectionString the ASB endpoint to publish/send messages view

Other keys can stay with the current values

Azure functions deployment

Build Docker image

From repo root folder, execute the following commands

Azure function with ASB endpoint and topic trigger

# Azure function with ASB endpoint and topic trigger
docker build -t af-masstransit-asb-topic -f docker-files/af-masstransit-asb-topic.Dockerfile .

Azure function with ASB endpoint and queue trigger

# Azure function with ASB endpoint and queue trigger
docker build -t af-masstransit-asb-queue -f docker-files/af-masstransit-asb-queue.Dockerfile .

Azure function with RabbitMq endpoint and topic trigger

# Azure function with RabbitMq endpoint and topic trigger
docker build -t af-masstransit-rmq-topic -f docker-files/af-masstransit-rmq-topic.Dockerfile .

Azure function with RabbitMq endpoint and queue trigger

# Azure function with RabbitMq endpoint and queue trigger
docker build -t af-masstransit-rmq-queue -f docker-files/af-masstransit-rmq-queue.Dockerfile .

All in one

torq=( topic queue )
asborrmq=( asb rmq )
for i in {0..1}; do 
  for j in {0..1}; do 
    docker build -t localhost:5000/af-masstransit-dual-demo/af-masstransit-"${asborrmq[$j]}"-"${torq[$i]}" -f docker-files/af-masstransit-"${asborrmq[$j]}"-"${torq[$i]}".Dockerfile . ; 
  done; 
done;

Verify the build images

docker image ls -a | grep af-masstransit

Push docker images to local registry

The builded images will be pushed to the local registry. Assuming that you built all the images

torq=( topic queue )
asborrmq=( asb rmq )
for i in {0..1}; do 
  for j in {0..1}; do 
    docker push localhost:5000/af-masstransit-dual-demo/af-masstransit-"${asborrmq[$j]}"-"${torq[$i]}"  ; 
  done; 
done;

Registry test

docker pull localhost:5000/af-masstransit-dual-demo/af-masstransit-rmq-queue

Deploying azure functions images to k8s

Use one of the following commands to deploy

Deploying all

torq=( topic queue )
asborrmq=( asb rmq )
for i in {0..1}; do 
  for j in {0..1}; do 
    func kubernetes deploy --name af-masstransit-"${asborrmq[$j]}"-"${torq[$i]}"  --image-name host.docker.internal:5000/af-masstransit-dual-demo/af-masstransit-"${asborrmq[$j]}"-"${torq[$i]}" --namespace rabbit ; 
  done; 
done;

Depending on your needs, you can use only a single deployment. In that case use one of the following commands

# Use one command at a time
func kubernetes deploy --name af-masstransit-asb-topic --image-name host.docker.internal:5000/af-masstransit-dual-demo/af-masstransit-asb-topic --namespace rabbit
func kubernetes deploy --name af-masstransit-rmq-topic --image-name host.docker.internal:5000/af-masstransit-dual-demo/af-masstransit-rmq-topic --namespace rabbit
func kubernetes deploy --name af-masstransit-asb-queue --image-name host.docker.internal:5000/af-masstransit-dual-demo/af-masstransit-asb-queue --namespace rabbit
func kubernetes deploy --name af-masstransit-rmq-queue --image-name host.docker.internal:5000/af-masstransit-dual-demo/af-masstransit-rmq-queue --namespace rabbit

Troubleshooting: server gave HTTP response to HTTPS client

error

Being a local, private registry the host.docker.internal must be added to insecure-registries section in Docker config file.

fix

Verify the secrets

The keys contained by local.settings.json are deployed as secrets into k8s.

image

Check the stored values to be in sync with the collected values from az cli output. Check File local.settings.json section for more info

ASB Topic Trigger (Mandatory)

Go to http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/#/secret/rabbit/af-masstransit-asb-topic?namespace=rabbit and verify the following keys:

  • APPINSIGHTS_INSTRUMENTATIONKEY
  • APPLICATIONINSIGHTS_CONNECTION_STRING
  • AzureWebJobsServiceBus - the ASB endpoint for the Azure Function trigger
  • ServiceBus - the ASB endpoint for the consumers configured by MassTransit

ASB Queue Trigger (Mandatory)

Go to http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/#/secret/rabbit/af-masstransit-asb-queue?namespace=rabbit and verify the following keys:

  • APPINSIGHTS_INSTRUMENTATIONKEY
  • APPLICATIONINSIGHTS_CONNECTION_STRING
  • AzureWebJobsServiceBus - the ASB endpoint for the Azure Function trigger
  • ServiceBus - the ASB endpoint for the consumers configured by MassTransit

RabbitMq Queue Trigger (Optional)

Go to http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/#/secret/rabbit/af-masstransit-rmq-queue?namespace=rabbit and verify the following keys:

  • APPINSIGHTS_INSTRUMENTATIONKEY
  • APPLICATIONINSIGHTS_CONNECTION_STRING

RabbitMq Topic Trigger (Optional)

Go to http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/#/secret/rabbit/af-masstransit-rmq-topic?namespace=rabbit and verify the following keys:

  • APPINSIGHTS_INSTRUMENTATIONKEY
  • APPLICATIONINSIGHTS_CONNECTION_STRING

Playing and testing

On the publisher side an environment variable named RABBITMQ_ENABLED is controlling the behaviour

Setting the publisher environment

The RABBITMQ_ENABLED environment variable can take two values, true or false. If not set the bus will be configured with Azure Service Bus.

  • With Powershell

    # Powershell
    $env:RABBITMQ_ENABLED = "true"
  • With DOS Command line

    rem DOS Command line
    SET RABBITMQ_ENABLED=true
    
  • With Bash/Zsh

    # bash/zsh
    export RABBITMQ_ENABLED=true

Build the solution

You can build any of Debug/Release/LocalDev configuration

dotnet build src/AzureFunction.Demo.sln --configuration Debug

Create RabbitMq demo endpoints (Mandatory)

Run the following application in order to create the queues and exchanges needed by the demo.

./src/EndpointCreator.MassTransit.Rmq.Demo/bin/Debug/net6.0/EndpointCreator.MassTransit.Rmq.Demo.exe

Verifying the endpoints

Open http://127.0.0.1:8080/#/queues (user:guest, pwd: guest) and verify the queues to be as in the following figure

image

Play with Azure Service Bus

In a new terminal run the following commands

# bash/zsh (for other terminals change the following line with the proper command from Setting the publisher environment section )
export RABBITMQ_ENABLED=false
cd src/Publisher.MassTransit.Dual.Demo/bin/Debug/net6.0
./Publisher.MassTransit.Dual.Demo.exe

Send a message and verify

Sending a message will use the OrdersQueue queue

image

Check in k8s dashboard on Pods section if a replica af-masstransit-asb-queue-* is launched (the launcher can have a 30 sec delay). Open the pod and check the logs.

image

Publish a message and verify

Publishing a message will use the OrdersTopic topic

image

Check in k8s dashboard on Pods section if a replica af-masstransit-asb-topic-* is launched (the launcher can have a 30 sec delay). Open the pod and check the logs.

image

Play with RabbitMq

In a new dos_command/powershell terminal run the following commands

# bash/zsh (for other terminals change the following line with the proper command from Setting the publisher environment section )
export RABBITMQ_ENABLED=true
cd src/Publisher.MassTransit.Dual.Demo/bin/Debug/net6.0
./Publisher.MassTransit.Dual.Demo.exe

Send a message and verify

Sending a message will use the OrdersQueue

image

Check in k8s dashboard on Pods section if a replica af-masstransit-rmq-queue-* is launched (the launcher can have a 30 sec delay). Open the pod and check the logs.

image

Publish a message and verify

Publishing a message will use the OrdersTopic queue (no topic equivalent in RabbitMq)

image

Check in k8s dashboard on Pods section if a replica af-masstransit-rmq-topicp is launched (the launcher can have a 30 sec delay). Open the pod and check the logs.

image

Cleaning (Optional)

The demo is complete. Use the following hints to clean the local and cloud environments.

Removing k8s resources

If you need to clean the local kubernetes, use the following command

torq=( topic queue )
asborrmq=( asb rmq )
for i in {0..1}; do 
  for j in {0..1}; do 
    kubectl delete deploy af-masstransit-"${asborrmq[$j]}"-"${torq[$i]}" --namespace rabbit ; 
    kubectl delete ScaledObject af-masstransit-"${asborrmq[$j]}"-"${torq[$i]}" --namespace rabbit ; 
    kubectl delete secret af-masstransit-"${asborrmq[$j]}"-"${torq[$i]}" --namespace rabbit ; 
  done; 
done;

Removing the Azure resources

If you need to purge all Azure resources, delete the resource group

az group delete -n k8s

Credits

Inspired by Chris Patterson's (@phatboyg) Azure Function with MassTransit sample https://github.com/MassTransit/Sample-AzureFunction/tree/master/src/Sample.AzureFunction. Lots of the basic structure of the code is thanks to this. The current work is extending the sample with an implementation for RabbitMq.

References

Nice to read

Automation

About

MassTransit on Azure Functions with Azure Service Bus - Isolated, local dev environment

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published