Dapr and Azure Functions: Part 1 – Hello World
This series of articles will discuss how to build Functions based microservices running in Dapr.
The goal is to break down each and every step and help folks new to Docker and containers better understand just exactly what’s happening under the hood without the complexity of the eShopOnDapr reference project.
The eShopOnDapr reference project is awesome as a reference point, but really difficult to understand the details of the configuration needed to make this work. I have mostly skipped over container-oriented “DevOps” and went straight to “NoOps” (Functions as a Service such as AWS Lambda and Azure Functions) and my sense from the documentation I’ve read is that Microsoft assumes that the engineers and architects interested in Dapr are already proficient in Docker and Kubernetes.
If you are like me, I like to iteratively build on knowledge and understand how the parts interact and I found most of the articles on Dapr really lacking in breaking down the “Ops” part of Dapr; my mind kind of works like this:
And this is how I felt going through the eShopOnDapr reference project:
If you’ve seen my previous post on Containers + Dapr vs Functions as a Service, I am a big fan of Functions as a Service because of the ability to get closer to “NoOps”. As I worked through this series of articles to build a bottom-up foundational operational knowledge of Dapr, what I’ve come to appreciate even more is just how productive platforms like AWS Lambda, Google Cloud Functions, and Azure Functions actually are. Spending time configuring and troubleshooting networking seems so…unproductive. If you do not have any real benefit to building your solution using containers (see my earlier post), I think most teams will be more productive using FaaS. But follow along as I figure out how to operationalize Dapr from scratch!
This is a multi-part series of articles (at least 4 parts):
- Part 1 – Hello World (you are here!)
- Part 2 – Azure Functions Containerization in Docker
- Part 3 – Containerizing with Dapr
- Part 4 – Running in Kubernetes
- Part 5a – Deploying to AWS with ECR and EKS Fargate
- Part 5b – Deploying to Azure with ACR and AKS
- Part 5c – Deploying to Google Cloud with Artifact Registry and GKE
I won’t spend too much time with the components in these first parts, but rather focus on how to “put pieces together”. Microsoft’s official e-book and guidance can be found here in the Microsoft docs, but I did not find it practical for starting from scratch and working with Visual Studio Code.
The initial inspiration came from an article Why Running Azure Logic Apps Anywhere is a Game Changer. That led me to Self-Hosting Azure Functions in Kubernetes.
You might wonder “Why Functions?” When used exclusively with
HttpTrigger it is more or less ASP.NET Web API with a super pared down set of functionality. In Part 4, we’ll look at some basic performance characteristics of the Functions Runtime Host versus ASP.NET Web API.
To get started, follow the Dapr documentation to get your environment set up.
If you need to, grab the Functions Core Tools as well.
In this series, I will not be focusing on Dapr specific bindings and instead we’ll just use a simple
HttpTrigger binding; our main point of interest is to understand:
- How to build up a Dapr solution from scratch,
- How to work with Dapr and Docker on a day-to-day basis with VS Code,
- And how to deploy into AWS EKS + Fargate or Azure AKS.
Microsoft has published a set of extensions which provide Dapr bindings for Functions if you decide to go forward with Functions instead of .NET Web API.
We’ll dive right in with creating and running our Hello World Functions app with Dapr.
Step 1: Create Functions Project
Start by creating a folder. In my case, it is
Open it with Visual Studio Code and open a new terminal.
At the terminal, type:
func init --name HelloWorldFunc --worker-runtime dotnet
Step 2: Add HelloWorld Function
Add a simple Function from the terminal:
func function new --name HelloWorld --authlevel anonymous
HttpTrigger when prompted. There’s nothing special about this Function; it’s just the default HTTP triggered Function and we’re going to use it as-is.
func start to test
If we copy the URL
http://localhost:7071/api/HelloWorld into a browser window, we should see:
Step 3: Connect Dapr
Now we’ll run it with the Dapr sidecar.
dapr run --app-id helloworldfuncdapr --app-port 7071 --dapr-http-port 7070 func start
Let’s break this down:
--app-id helloworldfuncdapris an arbitrary app identifier which will become part of the service URL
--app-port 7071is the port where the app will communicate with Dapr
--dapr-http-port 7070is where Dapr exposes the application port (7071 in this case) via the sidecar
func startruns the application
You should see output like this:
We can still access the Function endpoint directly using
http://localhost:7071/api/HelloWorld, but now we can also access it via the Dapr sidecar using
Let’s break down the URL:
http://localhost:7070/v1.0/invokenote the port 7070 which we specified as
--app-idwe specified earlier
/method/api/HelloWorldis the route to the Functions endpoint
At this point, this is what we have:
func.exe is running our Functions app and Dapr is acting more or less like a simple HTTP forwarding proxy at this point.
This is perfectly suitable for development purposes.