Exploring Dapr: Running your first Dapr app in Kubernetes!

In this “Exploring Dapr” series, I’m exploring some of its capabilities and sharing my thoughts on it.  Dapr is an event-driven portable runtime for building micro-services, for more information I refer to the documentation.  Be aware that Dapr is currently under constant evolution, so some of the described behavior or findings might not be accurate anymore in the future.

This blog post is based on Dapr version 0.3 and it explains how you can run your Dapr application in Kubernetes.  We’ll start from the publish / subscribe sample application, that I’ve described over here.  I’m using the local Kubernetes cluster that ships with Docker Desktop and Azure Container Registry as my private container store.

dapr-kubernetes-01

Install Dapr in your Kubernetes cluster

First things first, let’s enable Dapr in our cluster.  The installation procedure is clearly described over here, so no need to repeat it.

After installation, you should see three Dapr pods running:

  • Dapr operator
  • Dapr sidecar injector
  • Dapr placement

overview_kubernetes

$ kubectl get pods
NAME                                     READY   STATUS    RESTARTS   AGE
dapr-operator-76888fdcb9-n4pkm           1/1     Running   4          33d
dapr-placement-666b996945-whzv5          1/1     Running   4          33d
dapr-sidecar-injector-744d97578f-x5lm7   1/1     Running   4          33d

Create an Azure Container Registry

  • Create an Azure Container Registry

dapr-kubernetes-02

  • Provide the necessary details  Enable administrative access, which is acceptable for dev purposes.  For enterprise grade security, it’s better to use a service principal.

dapr-kubernetes-03

  • Go to the Access keys tab and copy the username and password, you’ll need it in the next steps.

dapr-kubernetes-04

Publish your image to an Azure container registry

  • Create Docker image, starting from the sample described in this blog.  This is a standard ASP.NET Core 3.0 app, with Docker support enabled.
docker build . -t pubsubapp:0.0.1
  • Run the docker image locally, to validate if it works
docker run -p 5000:80 pubsubapp:0.0.1
  • Link Docker to your private container registry and log in
docker login tvhdaprregistry.azurecr.io -u tvhdaprregistry
  • In order to push to a private registry, tag the image with the registry name as prefix
docker tag pubsubapp:0.0.1 tvhdaprregistry.azurecr.io/pubsubapp:0.0.1
  • Now you can push your Docker image to the Azure Container Registry
docker push tvhdaprregistry.azurecr.io/pubsubapp:0.0.1
  • Validate if your image is uploaded to the Azure Container Registry:

dapr-kubernetes-05

Please, remember that you better perform these steps during a build pipeline, in real enterprise-grade projects.

Run your Dapr application in Kubernetes

  • Remember that we’ve created a Dapr publish / subscribe component description
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: pubsub-azure-service-bus
spec:
  type: pubsub.azure.servicebus
  metadata:
  - name: connectionString
    value: ###
  • Deploy now the Dapr publish / subscribe component inside the Kubernetes cluster
kubectl apply -f Components/pubsub_azureServiceBus.yaml
  • Create a Kubernetes secret that will be used to pull the images from the Azure Container Registry
kubectl create secret docker-registry tvh-dapr-acr-secret --docker-server tvhdaprregistry.azurecr.io --docker-username=tvhdaprregistry --docker-password dB=<Password>
  • Create a Kubernetes deployment file.  Remark two important things in the YAML description:
    • We use the previously created secret to pull the image from ACR
    • We add Dapr annotations to the deployment, to ensure a Dapr sidecar will be injected
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pubsub-app
  labels:
    app: pubsub-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: pubsub-app
  template:
    metadata:
      labels:
        app: pubsub-app
      annotations:
        dapr.io/enabled: "true"
        dapr.io/id: "pubsubapp"
        dapr.io/port: "80"
        dapr.io/log-level: "debug"
    spec:
      containers:
      - name: pubsubapp
        image: tvhdaprregistry.azurecr.io/pubsubapp:0.0.1
        ports:
        - containerPort: 80
      imagePullSecrets:
      - name: tvh-dapr-acr-secret
  • Apply the deployment to your Kubernetes cluster
$ kubectl apply -f Kubernetes/pubsubapp_deployment.yaml
deployment.apps/pubsub-app created
  • You should see the deployment being ready pretty rapidly
$ kubectl get deployments pubsub-app
NAME         READY   UP-TO-DATE   AVAILABLE   AGE
pubsub-app   1/1     1            1           10s
  • Let’s have a look at the pod that has been created
$ kubectl get pods
NAME                                     READY   STATUS    RESTARTS   AGE
pubsub-app-5bb66bd4bd-ccrm5              2/2     Running   0          3m11s
  • When you look into the Pod, you’ll see that a dapr sidecar container got injected
$ kubectl describe pod pubsub-app-5bb66bd4bd-ccrm5
...
  Type    Reason     Age    From                     Message
  ----    ------     ----   ----                     -------
  Normal  Scheduled  4m52s  default-scheduler        Successfully assigned default/pubsub-app-5bb66bd4bd-ccrm5 to docker-desktop
  Normal  Pulled     4m51s  kubelet, docker-desktop  Container image "tvhdaprregistry.azurecr.io/pubsubapp:0.0.1" already present on machine
  Normal  Created    4m50s  kubelet, docker-desktop  Created container pubsubapp
  Normal  Started    4m50s  kubelet, docker-desktop  Started container pubsubapp
  Normal  Pulling    4m50s  kubelet, docker-desktop  Pulling image "docker.io/daprio/dapr:latest"
  Normal  Pulled     4m49s  kubelet, docker-desktop  Successfully pulled image "docker.io/daprio/dapr:latest"
  Normal  Created    4m48s  kubelet, docker-desktop  Created container daprd
  Normal  Started    4m48s  kubelet, docker-desktop  Started container daprd

  • When I look into the Dapr container logs, I see that:
    • The Azure Service Bus pub/sub component was loaded
    • The application was discovered at port 80
    • Dapr is running by default on HTTP port 3500 in Kubernetes
kubectl logs pubsub-app-5bb66bd4bd-ccrm5 daprd
time="2019-12-290T13:56:59Z" level=info msg="starting Dapr Runtime -- version 0.3.0 -- commit v0.3.0-rc.0-1-gfe6c306-dirty"
time="2019-12-290T13:56:59Z" level=info msg="log level set to: debug"
time="2019-12-290T13:56:59Z" level=info msg="kubernetes mode configured"
time="2019-12-290T13:56:59Z" level=info msg="dapr id: pubsubapp"
time="2019-12-290T13:56:59Z" level=info msg="loaded component pubsub-azure-service-bus (pubsub.azure.servicebus)"
time="2019-12-290T13:56:59Z" level=info msg="application protocol: http. waiting on port 80"
time="2019-12-290T13:56:59Z" level=info msg="application discovered on port 80"
time="2019-12-290T13:57:01Z" level=info msg="Initialized service discovery to kubernetes"
time="2019-12-290T13:57:01Z" level=warning msg="failed to init actors: actors: state store must be present to initialize the actor runtime"
time="2019-12-290T13:57:01Z" level=info msg="http server is running on port 3500"
time="2019-12-290T13:57:01Z" level=info msg="gRPC server is running on port 50001"
time="2019-12-290T13:57:01Z" level=info msg="dapr initialized. Status: Running. Init Elapsed 1662.3049999999998ms"
  • Create a Kubernetes Service description, which will be used to expose our Order API on the cluster nodes.  Remark that this is just for testing, no security is applied.
apiVersion: v1
kind: Service
metadata:
  name: pubsub-app-service
  labels:
    app: pubsub-app-service
spec:
  type: NodePort
  ports:
  - port: 8080
    targetPort: 80
    nodePort: 30501
    protocol: TCP
    name: http
  selector:
    app: pubsub-app
  • Apply the service to your Kubernetes cluster
kubectl apply -f Kubernetes/pubsubapp_service.yaml
  • Test the application on localhost:30501.  Submit an order
  • Check the application logs and see that the order got processed correctly.
$ kubectl logs pubsub-app-5bb66bd4bd-ccrm5 pubsubapp
Microsoft.Hosting.Lifetime[0]
      http://[::]:80
Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
Microsoft.Hosting.Lifetime[0]
      Content root path: /app
Tvh.Dapr.PubSub.Orders.Controllers.OrderController[0]
      Order with id 123 received!
Tvh.Dapr.PubSub.Orders.Controllers.OrderController[0]
      Order with id 123 published with status OK!
Tvh.Dapr.PubSub.Orders.Controllers.OrderController[0]
      Order with id 123 processed!

Conclusion

You see that all the plumbing was related to Kubernetes and is not really Dapr-specific.  If you set your annotations correctly, Dapr gets successfully injected into your Pods.  I had some issues with connecting my application to the Dapr sidecar, until I noticed in the logs that it listens by default on HTTP port 3500.

One important remark is about security.  My little sample app contains both an API and the background worker in a single application.  Although I completely understand that it makes sense to split these into two applications, there are also arguments to keep them together (e.g. simplicity).  Keeping both the API and worker in one application imposes some security risks, as also your local inbound HTTP endpoints – that allow Dapr to submit messages into your container – might get (publicly) exposed.

Cheers
Toon

ABOUT

MEET THE YOUR AZURE COACH TEAM

Your Azure Coach is specialized in organizing Azure trainings that are infused with real-life experience. All our coaches are active consultants, who are very passionate and who love to share their Azure expertise with you.