A colleague of mine has several times asked me about Dapr and what I thought. Dapr finally released a 1.0 in February this year and I figured it was time to give it a look.

Dapr stands for Distributed APplication Runtime and is meant to take care of some of the common problems one encounters with microservices, namely pub/sub and the stateless nature of containerized services.

From docs.dapr.io

Installing

First, let’s follow the steps to get Dapr installed: https://docs.dapr.io/getting-started/install-dapr-cli/

$ wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash
Your system is linux_amd64
Installing Dapr CLI...

Getting the latest Dapr CLI...
Installing v1.0.1 Dapr CLI...
Downloading https://github.com/dapr/cli/releases/download/v1.0.1/dapr_linux_amd64.tar.gz ...
[sudo] password for builder:
dapr installed into /usr/local/bin successfully.
CLI version: 1.0.1
Runtime version: n/a

Verification

$ dapr

         __
    ____/ /___ _____  _____
   / __  / __ '/ __ \/ ___/
  / /_/ / /_/ / /_/ / /
  \__,_/\__,_/ .___/_/
              /_/

===============================
Distributed Application Runtime

Usage:
  dapr [command]

Available Commands:
  completion     Generates shell completion scripts
  components     List all Dapr components. Supported platforms: Kubernetes
  configurations List all Dapr configurations. Supported platforms: Kubernetes
  dashboard      Start Dapr dashboard. Supported platforms: Kubernetes and self-hosted
  help           Help about any command
  init           Install Dapr on supported hosting platforms. Supported platforms: Kubernetes and self-hosted
  invoke         Invoke a method on a given Dapr application. Supported platforms: Self-hosted
  list           List all Dapr instances. Supported platforms: Kubernetes and self-hosted
  logs           Get Dapr sidecar logs for an application. Supported platforms: Kubernetes
  mtls           Check if mTLS is enabled. Supported platforms: Kubernetes
  publish        Publish a pub-sub event. Supported platforms: Self-hosted
  run            Run Dapr and (optionally) your application side by side. Supported pbuilder@DESKTOP-JBA79RT:~/Workspaces/dapr$
  status         Show the health status of Dapr services. Supported platforms: Kubernetes
  stop           Stop Dapr instances and their associated apps. . Supported platforms: Self-hosted
  uninstall      Uninstall Dapr runtime. Supported platforms: Kubernetes and self-hosted
  upgrade        Upgrades a Dapr control plane installation in a cluster. Supported platforms: Kubernetes

Flags:
  -h, --help      help for dapr
  -v, --version   version for dapr

Use "dapr [command] --help" for more information about a command.

Initializing Dapr

This should install redis, zipkin and a few other required components

$ dapr init
⌛  Making the jump to hyperspace...
↖  Downloading binaries and setting up components...
Dapr runtime installed to /home/builder/.dapr/bin, you may run the following to add it to your path if you want to run daprd directly:
    export PATH=$PATH:/home/builder/.dapr/bin
✅  Downloaded binaries and completed components set up.
ℹ️  daprd binary has been installed to /home/builder/.dapr/bin.
ℹ️  dapr_placement container is running.
ℹ️  dapr_redis container is running.
ℹ️  dapr_zipkin container is running.
ℹ️  Use `docker ps` to check running containers.
✅  Success! Dapr is up and running. To get started, go here: https://aka.ms/dapr-getting-started

Verification

$ dapr --version
CLI version: 1.0.1
Runtime version: 1.0.1

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS                        PORTS                              NAMES
19c53c5ab322        daprio/dapr         "./placement"            About a minute ago   Up About a minute             0.0.0.0:50005->50005/tcp           dapr_placement
90e7af85b8d5        openzipkin/zipkin   "start-zipkin"           About a minute ago   Up About a minute (healthy)   9410/tcp, 0.0.0.0:9411->9411/tcp   dapr_zipkin
aa1343a2ac94        redis               "docker-entrypoint.s…"   About a minute ago   Up About a minute             0.0.0.0:6379->6379/tcp             dapr_redis


$ ls $HOME/.dapr
bin  components  config.yaml

First test, a sidecar with no app

$ dapr run --app-id myapp --dapr-http-port 3500
WARNING: no application command found.
ℹ️  Starting Dapr with id myapp. HTTP Port: 3500. gRPC Port: 44935
ℹ️  Checking if Dapr sidecar is listening on HTTP port 3500
INFO[0000] starting Dapr Runtime -- version 1.0.1 -- commit 45bc40d  app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] log level set to: info                        app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] metrics server started on :41371/             app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.metrics type=log ver=1.0.1
INFO[0000] standalone mode configured                    app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] app id: myapp                                 app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] mTLS is disabled. Skipping certificate request and tls validation  app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] local service entry announced: myapp -> 172.20.135.184:33997  app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.contrib type=log ver=1.0.1
INFO[0000] Initialized name resolution to standalone     app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] component loaded. name: pubsub, type: pubsub.redis/  app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] waiting for all outstanding components to be processed  app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] component loaded. name: statestore, type: state.redis/  app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] all outstanding components processed          app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] enabled gRPC tracing middleware               app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime.grpc.api type=log ver=1.0.1
INFO[0000] enabled gRPC metrics middleware               app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime.grpc.api type=log ver=1.0.1
INFO[0000] API gRPC server is running on port 44935      app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] enabled metrics http middleware               app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime.http type=log ver=1.0.1
INFO[0000] enabled tracing http middleware               app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime.http type=log ver=1.0.1
INFO[0000] http server is running on port 3500           app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] The request body size parameter is: 4         app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] enabled gRPC tracing middleware               app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime.grpc.internal type=log ver=1.0.1
INFO[0000] enabled gRPC metrics middleware               app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime.grpc.internal type=log ver=1.0.1
INFO[0000] internal gRPC server is running on port 33997  app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] actor runtime started. actor idle timeout: 1h0m0s. actor scan interval: 30s  app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime.actor type=log ver=1.0.1
WARN[0000] failed to read from bindings: app channel not initialized   app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] dapr initialized. Status: Running. Init Elapsed 6.5374ms  app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] placement tables updated, version: 0          app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime.actor.internal.placement type=log ver=1.0.1
ℹ️  Checking if Dapr sidecar is listening on GRPC port 44935
ℹ️  Dapr sidecar is up and running.
✅  You're up and running! Dapr logs will appear here.

We should be able to set and retrieve the state (which is stored in redis) using  a bash prompt in a separate terminal


builder@DESKTOP-JBA79RT:~$ curl -X POST -H "Content-Type: application/json" -d '[{ "key": "name", "value": "Bruce Wayne"}]' http://localhost:3500/v1.0/state/statestore
builder@DESKTOP-JBA79RT:~$ curl http://localhost:3500/v1.0/state/statestore/name
"Bruce Wayne"builder@DESKTOP-JBA79RT:~$

We can also use the redis cli on the redis container to view the data:

builder@DESKTOP-JBA79RT:~$ docker exec -it dapr_redis redis-cli
127.0.0.1:6379> keys *
1) "myapp||name"
127.0.0.1:6379> hgetall "myapp||name"
1) "data"
2) "\"Bruce Wayne\""
3) "version"
4) "2"

Back in our first terminal, let’s ctrl-C to kill it and come back to our bash prompt:

terminated signal received: shutting down
INFO[0185] stop command issued. Shutting down all operations  app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
✅  Exited Dapr successfully

Create secret in a JSON:

builder@DESKTOP-JBA79RT:~/Workspaces/dapr$ mkdir dapr-components
$ cat dapr-components/somesecrets.json
{
   "MySecret": "Some people just want to watch the world burn"
}

We can go ahead and create a localSecretStore.yaml file:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: my-secret-store
  namespace: default
spec:
  type: secretstores.local.file
  version: v1
  metadata:
  - name: secretsFile
    value: ./somesecrets.json
  - name: nestedSeparator
    value: ":"

showing file:

$ cat dapr-components/localSecretStore.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: my-secret-store
  namespace: default
spec:
  type: secretstores.local.file
  version: v1
  metadata:
  - name: secretsFile
    value: ./dapr-components/somesecrets.json
  - name: nestedSeparator
    value: ":"

and then testing

$ curl http://localhost:3500/v1.0/secrets/my-secret-store/MySecret
{"MySecret":"Some people just want to watch the world burn"}

We can update it just to see it working:

builder@DESKTOP-JBA79RT:~/Workspaces/dapr$ cat dapr-components/localSecretStore.yaml | grep somesecrets.json
    value: ./dapr-components/somesecrets.json
builder@DESKTOP-JBA79RT:~/Workspaces/dapr$ dapr run --app-id myapp --dapr-http-port 3500 --components-path ./dapr-components/
WARNING: no application command found.
ℹ️  Starting Dapr with id myapp. HTTP Port: 3500. gRPC Port: 45005
ℹ️  Checking if Dapr sidecar is listening on HTTP port 3500
INFO[0000] starting Dapr Runtime -- version 1.0.1 -- commit 45bc40d  app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] log level set to: info                        app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] metrics server started on :45113/             app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.metrics type=log ver=1.0.1
INFO[0000] standalone mode configured                    app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] app id: myapp                                 app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] mTLS is disabled. Skipping certificate request and tls validation  app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] local service entry announced: myapp -> 172.20.135.184:41583  app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.contrib type=log ver=1.0.1
INFO[0000] Initialized name resolution to standalone     app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] waiting for all outstanding components to be processed  app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] component loaded. name: my-secret-store, type: secretstores.local.file/v1  app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] all outstanding components processed          app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] enabled gRPC tracing middleware               app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime.grpc.api type=log ver=1.0.1
INFO[0000] enabled gRPC metrics middleware               app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime.grpc.api type=log ver=1.0.1
INFO[0000] API gRPC server is running on port 45005      app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] enabled metrics http middleware               app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime.http type=log ver=1.0.1
INFO[0000] enabled tracing http middleware               app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime.http type=log ver=1.0.1
INFO[0000] http server is running on port 3500           app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] The request body size parameter is: 4         app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] enabled gRPC tracing middleware               app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime.grpc.internal type=log ver=1.0.1
INFO[0000] enabled gRPC metrics middleware               app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime.grpc.internal type=log ver=1.0.1
INFO[0000] internal gRPC server is running on port 41583  app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] actor runtime started. actor idle timeout: 1h0m0s. actor scan interval: 30s  app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime.actor type=log ver=1.0.1
WARN[0000] failed to read from bindings: app channel not initialized   app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] dapr initialized. Status: Running. Init Elapsed 2.4697999999999998ms  app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
INFO[0000] placement tables updated, version: 0          app_id=myapp instance=DESKTOP-JBA79RT scope=dapr.runtime.actor.internal.placement type=log ver=1.0.1
ℹ️  Checking if Dapr sidecar is listening on GRPC port 45005
ℹ️  Dapr sidecar is up and running.
✅  You're up and running! Dapr logs will appear here.

And in another shell

$ curl http://localhost:3500/v1.0/secrets/my-secret-store/MySecret
{"MySecret":"Superman is better"}

Dapr Pub/Sub quick start

Let’s do one of the simpler quick starts with node and python

builder@DESKTOP-JBA79RT:~/Workspaces$ git clone  https://github.com/dapr/quickstarts.git
Cloning into 'quickstarts'...
remote: Enumerating objects: 43, done.
remote: Counting objects: 100% (43/43), done.
remote: Compressing objects: 100% (36/36), done.
remote: Total 2341 (delta 17), reused 12 (delta 6), pack-reused 2298
Receiving objects: 100% (2341/2341), 10.05 MiB | 23.81 MiB/s, done.
Resolving deltas: 100% (1381/1381), done.
builder@DESKTOP-JBA79RT:~/Workspaces$ cd quickstarts/
builder@DESKTOP-JBA79RT:~/Workspaces/quickstarts$ cd pub-sub/builder@DESKTOP-JBA79RT:~/Workspaces/quickstarts/pub-sub$ cd node-subscriber/
builder@DESKTOP-JBA79RT:~/Workspaces/quickstarts/pub-sub/node-subscriber$

NPM install

builder@DESKTOP-JBA79RT:~/Workspaces/quickstarts/pub-sub/node-subscriber$ nvm use 10.22.1
Now using node v10.22.1 (npm v6.14.6)
builder@DESKTOP-JBA79RT:~/Workspaces/quickstarts/pub-sub/node-subscriber$ npm install
added 50 packages from 37 contributors and audited 50 packages in 0.678s
found 0 vulnerabilities

Verify it runs

$ dapr run --app-id node-subscriber --app-port 3000 node app.js
ℹ️  Starting Dapr with id node-subscriber. HTTP Port: 39115. gRPC Port: 43107
INFO[0000] starting Dapr Runtime -- version 1.0.1 -- commit 45bc40d  app_id=node-subscriber instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
….

Now setup the python3 subscriber

builder@DESKTOP-JBA79RT:~/Workspaces/quickstarts/pub-sub/node-subscriber$ cd ../python-subscriber/
builder@DESKTOP-JBA79RT:~/Workspaces/quickstarts/pub-sub/python-subscriber$ pip3 install -r requirements.txt
Collecting Click==7.0 (from -r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/fa/37/45185cb5abbc30d7257104c434fe0b07e5a195a6847506c074527aa599ec/Click-7.0-py2.py3-none-any.whl (81kB)
    100% |████████████████████████████████| 81kB 2.3MB/s
Collecting Flask==1.1.1 (from -r requirements.txt (line 2))
  Downloading https://files.pythonhosted.org/packages/9b/93/628509b8d5dc749656a9641f4caf13540e2cdec85276964ff8f43bbb1d3b/Flask-1.1.1-py2.py3-none-any.whl (94kB)
    100% |████████████████████████████████| 102kB 5.7MB/s
Collecting Flask-Cors==3.0.8 (from -r requirements.txt (line 3))
  Downloading https://files.pythonhosted.org/packages/78/38/e68b11daa5d613e3a91e4bf3da76c94ac9ee0d9cd515af9c1ab80d36f709/Flask_Cors-3.0.8-py2.py3-none-any.whl
Collecting itsdangerous==1.1.0 (from -r requirements.txt (line 4))
  Using cached https://files.pythonhosted.org/packages/76/ae/44b03b253d6fade317f32c24d100b3b35c2239807046a4c953c7b89fa49e/itsdangerous-1.1.0-py2.py3-none-any.whl
Collecting Jinja2==2.10.1 (from -r requirements.txt (line 5))
  Downloading https://files.pythonhosted.org/packages/1d/e7/fd8b501e7a6dfe492a433deb7b9d833d39ca74916fa8bc63dd1a4947a671/Jinja2-2.10.1-py2.py3-none-any.whl (124kB)
    100% |████████████████████████████████| 133kB 8.0MB/s
Collecting MarkupSafe==1.1.1 (from -r requirements.txt (line 6))
  Downloading https://files.pythonhosted.org/packages/b2/5f/23e0023be6bb885d00ffbefad2942bc51a620328ee910f64abe5a8d18dd1/MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl
Collecting six==1.12.0 (from -r requirements.txt (line 7))
  Downloading https://files.pythonhosted.org/packages/73/fb/00a976f728d0d1fecfe898238ce23f502a721c0ac0ecfedb80e0d88c64e9/six-1.12.0-py2.py3-none-any.whl
Collecting Werkzeug==0.15.6 (from -r requirements.txt (line 8))
  Downloading https://files.pythonhosted.org/packages/b7/61/c0a1adf9ad80db012ed7191af98fa05faa95fa09eceb71bb6fa8b66e6a43/Werkzeug-0.15.6-py2.py3-none-any.whl (328kB)
    100% |████████████████████████████████| 337kB 2.0MB/s
Installing collected packages: Click, Werkzeug, itsdangerous, MarkupSafe, Jinja2, Flask, six, Flask-Cors
Successfully installed Click-7.0 Flask-1.1.1 Flask-Cors-3.0.8 Jinja2-2.10.1 MarkupSafe-1.1.1 Werkzeug-0.15.6 itsdangerous-1.1.0 six-1.12.0

And verify that

$ dapr run --app-id python-subscriber --app-port 5000 python3 app.py
ℹ️  Starting Dapr with id python-subscriber. HTTP Port: 39213. gRPC Port: 41375
INFO[0000] starting Dapr Runtime -- version 1.0.1 -- commit 45bc40d  app_id=python-subscriber instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
...

Lastly, let’s build and test the react app

builder@DESKTOP-JBA79RT:~/Workspaces/quickstarts/pub-sub/python-subscriber$ cd ..
builder@DESKTOP-JBA79RT:~/Workspaces/quickstarts/pub-sub$ cd react-form/
builder@DESKTOP-JBA79RT:~/Workspaces/quickstarts/pub-sub/react-form$ dapr run --app-id react-form --app-port 8080 npm run buildandstart
ℹ️  Starting Dapr with id react-form. HTTP Port: 44995. gRPC Port: 33951
…

It failed… seems it has an out of date package:

We can ctrl-c to get back


== APP == npm ERR!     /home/builder/.npm/_logs/2021-03-26T13_16_26_258Z-debug.log

^Cℹ️
terminated signal received: shutting down
✅  Exited Dapr successfully
✅  Exited App successfully
builder@DESKTOP-JBA79RT:~/Workspaces/quickstarts/pub-sub/react-form$

To fix, i just added an update for browserslist in the package.json:

builder@DESKTOP-JBA79RT:~/Workspaces/quickstarts/pub-sub/react-form$ git diff package.json
diff --git a/pub-sub/react-form/package.json b/pub-sub/react-form/package.json
index 44a5515..71cf0d2 100644
--- a/pub-sub/react-form/package.json
+++ b/pub-sub/react-form/package.json
@@ -9,7 +9,7 @@
     "server": "nodemon server.js",
     "dev": "concurrently --kill-others-on-fail \"yarn server\" \"yarn client\"",
     "start": "node server.js",
-    "buildclient": "cd client && npm install && npm run build",
+    "buildclient": "cd client && npm install && npx browserslist@latest --update-db && npm run build",
     "buildandstart": "npm run buildclient && npm install && npm run start"
   },
   "author": "Ryan Volum",

Now when we run:

$ dapr run --app-id react-form --app-port 8080 npm run buildandstart
ℹ️  Starting Dapr with id react-form. HTTP Port: 36867. gRPC Port: 46197
INFO[0000] starting Dapr Runtime -- version 1.0.1 -- commit 45bc40d  app_id=react-form instance=DESKTOP-JBA79RT scope=dapr.runtime type=log ver=1.0.1
…

Now let’s put it all together…

Launch the Python one in a window, the Node subscriber in a window and then another window can use the dapr publish command to publish to a topic:

dapr publish --publish-app-id react-form --pubsub pubsub --topic A --data '{ "message": "This is a test" }'

And if we launch the react app, we can use that to also publish to topics:

This even worked for very large payloads

Testing in Kubernetes (Try #1, with issues)

First, like always, let's create a cluster for our work:

$ cat ~/create_cluster.sh
#!/bin/bash
set +x

sudo apt-get install -y jq

echo "az aks account set --subscription \"Pay-As-You-Go\""
#az account set --subscription "Visual Studio Enterprise Subscription"
az account set --subscription "Pay-As-You-Go"

echo "az group create --name idjaks$1rg --location centralus"
az group create --name idjaks$1rg --location centralus

echo "az ad sp create-for-rbac -n idjaks$1sp --skip-assignment --output json > my_sp.json"
az ad sp create-for-rbac -n idjaks$1sp --skip-assignment --output json > my_sp.json

echo "cat my_sp.json | jq -r .appId"
cat my_sp.json | jq -r .appId

export SP_PASS=`cat my_sp.json | jq -r .password`
export SP_ID=`cat my_sp.json | jq -r .appId`
sleep 10

echo "az aks create --resource-group idjaks$1rg --name idjaks$1 --location centralus --node-count 3 --enable-cluster-autoscaler --min-count 2 --max-count 4 --generate-ssh-keys --network-plugin azure --network-policy azure --service-principal $SP_ID --client-secret $SP_PASS"
az aks create --resource-group idjaks$1rg --name idjaks$1 --location centralus --node-count 3 --enable-cluster-autoscaler --min-count 2 --max-count 4 --generate-ssh-keys --network-plugin azure --network-policy azure --service-principal $SP_ID --client-secret $SP_PASS

az acr create -n idjacr$1cr -g idjaks$1rg --sku Basic --admin-enabled true

echo "=============== login ================"
set -x

sudo az aks install-cli

(rm -f ~/.kube/config || true) && az aks get-credentials -n idjaks$1 -g idjaks$1rg --admin

kubectl get nodes

kubectl get ns

When run, we can see the nodes up and running:

$ kubectl get nodes
NAME                                STATUS   ROLES   AGE     VERSION
aks-nodepool1-15452094-vmss000001   Ready    agent   3h39m   v1.18.14
aks-nodepool1-15452094-vmss000002   Ready    agent   3h39m   v1.18.14

We can now add the chart and see the active versions

builder@DESKTOP-JBA79RT:~/Workspaces/dapr$ helm repo add dapr https://dapr.github.io/helm-charts/
"dapr" has been added to your repositories
builder@DESKTOP-JBA79RT:~/Workspaces/dapr$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "hashicorp" chart repository
...Successfully got an update from the "dapr" chart repository
...Successfully got an update from the "kedacore" chart repository
...Successfully got an update from the "datadog" chart repository
...Successfully got an update from the "azure-samples" chart repository
...Successfully got an update from the "nginx-stable" chart repository
...Successfully got an update from the "openfaas" chart repository
...Successfully got an update from the "datawire" chart repository
...Successfully got an update from the "jetstack" chart repository
...Successfully got an update from the "bitnami" chart repository
...Successfully got an update from the "stable" chart repository
Update Complete. ⎈Happy Helming!⎈
builder@DESKTOP-JBA79RT:~/Workspaces/dapr$ helm search repo dapr --devel --versions
NAME            CHART VERSION   APP VERSION     DESCRIPTION
dapr/dapr       1.0.1           1.0.1           A Helm chart for Dapr on Kubernetes
dapr/dapr       1.0.0           1.0.0           A Helm chart for Dapr on Kubernetes
dapr/dapr       1.0.0-rc.4      1.0.0-rc.4      A Helm chart for Dapr on Kubernetes
dapr/dapr       1.0.0-rc.3      1.0.0-rc.3      A Helm chart for Dapr on Kubernetes
dapr/dapr       1.0.0-rc.2      1.0.0-rc.2      A Helm chart for Dapr on Kubernetes
dapr/dapr       1.0.0-rc.1      1.0.0-rc.1      A Helm chart for Dapr on Kubernetes
dapr/dapr       0.11.3          0.11.3          A Helm chart for Dapr on Kubernetes
dapr/dapr       0.11.2          0.11.2          A Helm chart for Dapr on Kubernetes
dapr/dapr       0.11.1          0.11.1          A Helm chart for Dapr on Kubernetes
dapr/dapr       0.11.0          0.11.0          A Helm chart for Dapr on Kubernetes
dapr/dapr       0.10.0          0.10.0          A Helm chart for Dapr on Kubernetes
dapr/dapr       0.4.3           0.9.0           A Helm chart for Dapr on Kubernetes
dapr/dapr       0.4.2           0.8.0           A Helm chart for Dapr on Kubernetes

Create a values file:

builder@DESKTOP-JBA79RT:~/Workspaces/dapr$ cat << EOF > values.yaml
> global.ha.enabled: true
> EOF
builder@DESKTOP-JBA79RT:~/Workspaces/dapr$ cat values.yaml
global.ha.enabled: true

Now we can just launch it:

builder@DESKTOP-JBA79RT:~/Workspaces/dapr$ helm install dapr dapr/dapr --version 1.0.1 --namespace dapr-system --create-namespace --values values.yaml --wait
NAME: dapr
LAST DEPLOYED: Wed Mar 24 16:26:12 2021
NAMESPACE: dapr-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Thank you for installing Dapr: High-performance, lightweight serverless runtime for cloud and edge

Your release is named dapr.

To get started with Dapr, we recommend using our quickstarts:
https://github.com/dapr/quickstarts

For more information on running Dapr, visit:
https://dapr.io

We can launch the Dapr dashboard with “dashboard -k”.. In fact, most of the commands for local vs kubernetes are just the difference of adding “-k”.

Sample app

Deploying a sample node app is pretty easy.  Here we skip most of the steps of the Hello Kubernetes guide (https://github.com/dapr/quickstarts/tree/v1.0.0/hello-kubernetes) and just create a deployment yaml:


$ cat dapr-node.yaml
kind: Service
apiVersion: v1
metadata:
  name: nodeapp
  labels:
    app: node
spec:
  selector:
    app: node
  ports:
  - protocol: TCP
    port: 80
    targetPort: 3000
  type: LoadBalancer

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nodeapp
  labels:
    app: node
spec:
  replicas: 1
  selector:
    matchLabels:
      app: node
  template:
    metadata:
      labels:
        app: node
      annotations:
        dapr.io/enabled: "true"
        dapr.io/app-id: "nodeapp"
        dapr.io/app-port: "3000"
    spec:
      containers:
      - name: node
        image: dapriosamples/hello-k8s-node:latest
        ports:
        - containerPort: 3000
        imagePullPolicy: Always

The Annotation takes care of the side car..

$ kubectl apply -f dapr-node.yaml -n dapr-system
service/nodeapp created
deployment.apps/nodeapp created

$ kubectl get svc -n dapr-system
NAME                    TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)                               AGE
dapr-api                ClusterIP      10.0.221.116   <none>        80/TCP                                6m45s
dapr-dashboard          ClusterIP      10.0.113.208   <none>        8080/TCP                              6m45s
dapr-placement-server   ClusterIP      None           <none>        50005/TCP,8201/TCP                    6m45s
dapr-sentry             ClusterIP      10.0.200.26    <none>        80/TCP                                6m45s
dapr-sidecar-injector   ClusterIP      10.0.9.59      <none>        443/TCP                               6m45s
nodeapp                 LoadBalancer   10.0.4.23      13.86.7.154   80:31836/TCP                          28s
nodeapp-dapr            ClusterIP      None           <none>        80/TCP,50001/TCP,50002/TCP,9090/TCP   27s

We can also expose the Dapr dashboard here as well:

If you update the service.

$ kubectl get svc dapr-dashboard -n dapr-system -o yaml > dashboard.yaml

Change type: ClusterIP to Loadbalancer
$ cat dashboard.yaml | grep "type: Lo"
  type: LoadBalancer

$ kubectl apply -f dashboard.yaml
Warning: resource services/dapr-dashboard is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply. kubectl apply should only be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically.
service/dapr-dashboard configured

Then we can get the IP

$ kubectl get svc -n dapr-system
NAME                    TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)                               AGE
dapr-api                ClusterIP      10.0.221.116   <none>          80/TCP                                12m
dapr-dashboard          LoadBalancer   10.0.113.208   52.154.153.91   8080:30056/TCP                        12m
dapr-placement-server   ClusterIP      None           <none>          50005/TCP,8201/TCP                    12m
dapr-sentry             ClusterIP      10.0.200.26    <none>          80/TCP                                12m
dapr-sidecar-injector   ClusterIP      10.0.9.59      <none>          443/TCP                               12m
nodeapp                 LoadBalancer   10.0.4.23      13.86.7.154     80:31836/TCP                          6m34s
nodeapp-dapr            ClusterIP      None           <none>          80/TCP,50001/TCP,50002/TCP,9090/TCP   6m33s

And now access it:

We can click on the app to get details

Including logs

We glossed over it a bit, but you can see the dockerfile used for this:https://github.com/dapr/quickstarts/blob/v1.0.0/hello-kubernetes/node/app.js

I tested pushing an order and i see express parsed it, but failed to push into redis

Let’s add the redis yaml and try again

builder@DESKTOP-JBA79RT:~/Workspaces/dapr$ kubectl apply -f redis.yaml -n dapr-system
component.dapr.io/statestore created

builder@DESKTOP-JBA79RT:~/Workspaces/dapr$ cat redis.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestore
spec:
  type: state.redis
  version: v1
  metadata:
  # These settings will work out of the box if you use `helm install
  # bitnami/redis`.  If you have your own setup, replace
  # `redis-master:6379` with your own Redis master address, and the
  # Redis password with your own Secret's name. For more information,
  # see https://docs.dapr.io/operations/components/component-secrets .
  - name: redisHost
    value: redis-master:6379
  - name: redisPassword
    secretKeyRef:
      name: redis
      key: redis-password
auth:
  secretStore: kubernetes

And installing Redis

$ helm install  bitnami/redis -n dapr-system --generate-name
NAME: redis-1616622849
LAST DEPLOYED: Wed Mar 24 16:54:11 2021
NAMESPACE: dapr-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
** Please be patient while the chart is being deployed **
Redis(TM) can be accessed via port 6379 on the following DNS names from within your cluster:

redis-1616622849-master.dapr-system.svc.cluster.local for read/write operations
redis-1616622849-slave.dapr-system.svc.cluster.local for read-only operations

To get your password run:

    export REDIS_PASSWORD=$(kubectl get secret --namespace dapr-system redis-1616622849 -o jsonpath="{.data.redis-password}" | base64 --decode)

To connect to your Redis(TM) server:

1. Run a Redis(TM) pod that you can use as a client:
   kubectl run --namespace dapr-system redis-1616622849-client --rm --tty -i --restart='Never' \
    --env REDIS_PASSWORD=$REDIS_PASSWORD \
   --image docker.io/bitnami/redis:6.0.12-debian-10-r3 -- bash

2. Connect using the Redis(TM) CLI:
   redis-cli -h redis-1616622849-master -a $REDIS_PASSWORD
   redis-cli -h redis-1616622849-slave -a $REDIS_PASSWORD

To connect to your database from outside the cluster execute the following commands:

    kubectl port-forward --namespace dapr-system svc/redis-1616622849-master 6379:6379 &
    redis-cli -h 127.0.0.1 -p 6379 -a $REDIS_PASSWORD

In summary, I tried several combinations of helm values for redis and the statestore yaml, but the quickstart assumed a passwordless redis and it was not working out.

Kubernetes Try #2 (worked)

Creating a fresh cluster ($ ~/create_cluster.sh 17) I’ll first install Dapr.

To keep it simple, i’ll install Dapr into the default namespace

builder@DESKTOP-JBA79RT:~/Workspaces/dapr$ dapr init -k -n default
⌛  Making the jump to hyperspace...
ℹ️  Note: To install Dapr using Helm, see here: https://docs.dapr.io/getting-started/install-dapr-kubernetes/#install-with-helm-advanced

✅  Deploying the Dapr control plane to your cluster...
✅  Success! Dapr has been installed to namespace default. To verify, run `dapr status -k' in your terminal. To get started, go here: https://aka.ms/dapr-getting-started
builder@DESKTOP-JBA79RT:~/Workspaces/dapr$

Next we add Redis

builder@DESKTOP-JBA79RT:~/Workspaces/dapr$ helm repo add bitnami https://charts.bitnami.com/bitnami
"bitnami" already exists with the same configuration, skipping
builder@DESKTOP-JBA79RT:~/Workspaces/dapr$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "kedacore" chart repository
...Successfully got an update from the "hashicorp" chart repository
...Successfully got an update from the "azure-samples" chart repository
...Successfully got an update from the "dapr" chart repository
...Successfully got an update from the "datadog" chart repository
...Successfully got an update from the "nginx-stable" chart repository
...Successfully got an update from the "openfaas" chart repository
...Successfully got an update from the "datawire" chart repository
...Successfully got an update from the "jetstack" chart repository
...Successfully got an update from the "bitnami" chart repository
...Successfully got an update from the "stable" chart repository
Update Complete. ⎈Happy Helming!⎈
builder@DESKTOP-JBA79RT:~/Workspaces/dapr$ helm install redis bitnami/redis
NAME: redis
LAST DEPLOYED: Fri Mar 26 11:53:13 2021
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
** Please be patient while the chart is being deployed **
Redis(TM) can be accessed via port 6379 on the following DNS names from within your cluster:

redis-master.default.svc.cluster.local for read/write operations
redis-slave.default.svc.cluster.local for read-only operations


To get your password run:

    export REDIS_PASSWORD=$(kubectl get secret --namespace default redis -o jsonpath="{.data.redis-password}" | base64 --decode)

To connect to your Redis(TM) server:

1. Run a Redis(TM) pod that you can use as a client:
   kubectl run --namespace default redis-client --rm --tty -i --restart='Never' \
    --env REDIS_PASSWORD=$REDIS_PASSWORD \
   --image docker.io/bitnami/redis:6.0.12-debian-10-r3 -- bash

2. Connect using the Redis(TM) CLI:
   redis-cli -h redis-master -a $REDIS_PASSWORD
   redis-cli -h redis-slave -a $REDIS_PASSWORD

To connect to your database from outside the cluster execute the following commands:

    kubectl port-forward --namespace default svc/redis-master 6379:6379 &
    redis-cli -h 127.0.0.1 -p 6379 -a $REDIS_PASSWORD

We can see the password was set in a k8s secret by redis:

builder@DESKTOP-JBA79RT:~/Workspaces/dapr$ kubectl get secrets redis -o yaml | grep redis-password
  redis-password: eW9RRjdSUnd6ZQ==
        f:redis-password: {}


$ echo eW9RRjdSUnd6ZQ== | base64 --decode
yoQF7RRwze

We will need this in the redis step that comes next:

$ cat redis.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: pubsub
spec:
  type: pubsub.redis
  version: v1
  metadata:
  - name: "redisHost"
    value: "redis-master:6379"
  - name: "redisPassword"
    value: "yoQF7RRwze"

We can now apply the whole of pub-sub:


builder@DESKTOP-JBA79RT:~/Workspaces/quickstarts/pub-sub/deploy$ kubectl apply -f .
deployment.apps/node-subscriber created
deployment.apps/python-subscriber created
service/react-form created
deployment.apps/react-form created
component.dapr.io/pubsub created

We can see pods and services:

As well as access the react form through the public ingress

more details:

Summary

Dapr provides an interesting framework for common containerized problems.  I only scratched the surface with some demos, however it was enough to see the value it could provide.  I would certainly use it for the simple pub/sub problem many apps need to solve, however I’m not sure I would want to leverage its basic secrets management or observability (logs).  I find AKV and Vault better for secrets and either Azure Logs or Datadog for logging easier to use.

The parts that were most interesting was dapr in docker only - the ability to build and test a pub/sub app in Node or Python locally without a cluster would be very handy for development.

I likely will circle back on this, especially to see how Dapr could be partnered with something like KNative or OpenFaaS for functions with pub/sub.