TL;DR
One way of executing multiple Dapr applications locally is to use Docker compose or to use project Tye with its Dapr specific features. Both approaches base on packaging the apps into containers and start these containers. Additionally a network boundary is created to avoid or tune down "port dance" mentioned below. Debugging then is achieved by attaching to the container executing the Dapr application.
To avoid container build times and attaching the debugger - given that the setup is homogenous and/or simple enough - in this post I want to show a simple approach to start Dapr sidecars and (selectively) application instances with PowerShell (Core) background jobs.
DISCLAIMER: the approach shown here is not intended for production use; it is intended to help with local debugging and testing of .NET based Dapr applications
startDaprAsJob script
This repository contains a simple 2 app sample - app1 invoking app2 - to show the basic workings of this approach. Script ./startDaprAsJob.ps1
controls which projects / applications are to be started. Again for simplicity a static definition is used which could be replaced with some generic function to detect all relevant projects and settings.
pre-requisite: Dapr client is initialized with
dapr init --slim
so that the script can control lifecycle of placement service, which is required for actor scenarios
challenge - the "port dance"
When running multiple Dapr applications / services locally in parallel the challenge is, that each needs a dedicated set of ports for the application itself, for Dapr HTTP, for Dapr gRPC and also for Dapr metrics. The script solves this by generating port numbers dynamically and assigning these to the particular applications through modifying the launchSettings.json
.
Now this adjustment makes the script somewhat specific to .NET and Visual Studio. But this approach can be used for adapting the script to any other environment.
configuration
$configProjects = @(
@{
appId = "app1"
folder = "./app1"
projectFile = "app1.csproj"
settingName = "app1"
debug = $false
}
@{
appId = "app2"
folder = "./app2"
projectFile = "app2.csproj"
settingName = "app2"
debug = $true
}
)
setting | purpose |
---|---|
appId |
needs to be Dapr id commonly used to address service |
folder |
relative folder of .NET project (containing components folder and tracing.yaml ) |
projectFile |
name of service project file |
settingName |
name of launch setting (Launch:Project; not IIS Express) which is modified for startup |
debug |
$true : no background instance is started; waiting for (debugging) instance started from VS$false : background instance of service is started with dotnet run |
Setting debug
determines which projects are directly executed as background jobs (when $false
) or which ones are started by yourself from a development environment (when $true
). For those apps Dapr sidecar(s) wait(s) until application(s) become available.
usage
With the settings above app2
can be opened e.g. in VS, marked as startup project, desired breakpoints (for example in line 21 of app2
HealthController
) can be set and be started.
Now the script is used to start the other Dapr applications / services / ...
❯ .\startDaprAsJob.ps1
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
41 placement BackgroundJob Running True localhost …
--------------------------------------------------------------------------------
updated C:\Users\kai\src\dapr-experimental\app2app\app1\Properties\launchSettings.json
start Daprd in background app1 5000
43 app1-daprd BackgroundJob Running True localhost …
45 app1-app BackgroundJob Running True localhost …
--------------------------------------------------------------------------------
updated C:\Users\kai\src\dapr-experimental\app2app\app2\Properties\launchSettings.json
start Daprd in background app2 5010
47 app2-daprd BackgroundJob Running True localhost …
expecting C:\Users\kai\src\dapr-experimental\app2app\app2\app2.csproj to be started from development environment
--------------------------------------------------------------------------------
t: test call health endpoint
s: job status
e: check all logs for errors
q: stop jobs and quit
0: show log of placement
1: show log of app1-daprd
2: show log of app1-app
3: show log of app2-daprd
Enter option:
Checking job status shows that app1
is running with its sidecar and for app2
only the sidecar is running.
Enter option: s
Name State
---- -----
placement Running
app1-daprd Running
app1-app Running
app2-daprd Running
Invoking the test should bring up app2
in debugger:
Enter option: t
--------------------------------------------------------------------------------
App1 health
status : OK
App2 health (through App1)
...trip to debugger...
status : OK - App2
Breakpoint hit:
That's it.
other options
e
does a basic check for errors on all logs produced to see whether Dapr sidecars and components started or whether errors occurred in the applications.
As a convenience numbers can be used to retrieve logs of the corresponding job.
1
would display the Dapr log of app1
:
time="2020-08-21T13:54:58.6704251Z" level=info msg="starting Dapr Runtime -- version 0.9.0 -- commit 6babe85-dirty" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:54:58.6704251Z" level=info msg="log level set to: debug" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:54:58.671422Z" level=info msg="metrics server started on :9091/" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.metrics type=log ver=0.9.0
time="2020-08-21T13:54:58.6774253Z" level=info msg="standalone mode configured" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:54:58.6774253Z" level=info msg="app id: app1" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:54:58.6774253Z" level=info msg="mTLS is disabled. Skipping certificate request and tls validation" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:54:58.6794256Z" level=info msg="found component statestore (state.redis)" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:54:58.6794256Z" level=info msg="found component messagebus (pubsub.redis)" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:54:58.6794256Z" level=info msg="application protocol: http. waiting on port 5000. This will block until the app is listening on that port." app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:55:00.9266468Z" level=info msg="application discovered on port 5000" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:55:00.9925923Z" level=info msg="application configuration loaded" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:55:01.0135957Z" level=info msg="local service entry announced: app1 -> 10.242.0.4:63838" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.contrib type=log ver=0.9.0
...
2
would display app2
console log:
info: Microsoft.Hosting.Lifetime[0]
Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\Users\kai\src\dapr-experimental\app2app\app1
Option q
stops and removes all related jobs. In case the script breaks, jobs can be cleaned up by starting the script again with immediately ending it with q
.
supported OS
The only Windows OS specific part in this script should be the invocation of the placement
service:
C:\Dapr\placement.exe --port $port
How to run Dapr self hosted without containers indicates what needs to be adapted for Linux / MacOS.
Conclusion
Whether it is easier and more stable to have Docker containers (see above mentioned Docker compose and project Tye) or PowerShell jobs floating around on your machine is a matter of taste. I personally like this approach as it allows me to spin up a multi application scenario on my local machine quickly.
Top comments (6)
Hi,
I'm interested in launching a few projects. But I did not find any scripts in the repo.
I have to run about 8 projects and every time I run one by one, it is very time-consuming.
Should you show the content of the ps1 script?
How I can achieve my goal?
Thank you in advance.
Hello @kep11
I restored the repo/script. Please see whether you can make sense out of it.
Kai
I am sorry @kep11 for breaking the link. I will update the post in the next hours.
Could you include here as well the way to work with State Stores and Pub/Sub with this approach? as i was running into a couple of issues this way
Sure. Gimme a few days as I'm still in vacation mode. What is the background of your request? If you operate state store with Redis in a docker container my sample can be extended easily - it is still plain vanilla Dapr. Or are you especially interested for the "w/o docker" part where some state and some pub/sub components live somewhere outside docker?
Hi, already fixed the issues i was running into, however i would gladly read some post regarding development using dapr + docker (loacally), like having an image of a an api/mvc app (aka app1) that was build with DAPR, starting this image inside a docker container locally and then creating a fresh api (akk app2). And calling app1 from app2 using DAPR. (would be best in debug mode), and maybe some interaction between these apps with the pub/sub (redis or any other doesn't matter). If you would have some nice posts you can advice or do one yourself that would be great.