From deec49f5df134fc6d5c1e5ca56fd656b6277bc67 Mon Sep 17 00:00:00 2001 From: Maksym Sadovnychyy Date: Thu, 19 Dec 2024 21:06:37 +0100 Subject: [PATCH] (feature): release --- README.md | 682 +++++++++++++++++- src/Publisher/Dockerfile | 2 +- src/Publisher/Properties/launchSettings.json | 2 +- src/Publisher/Publisher.csproj | 4 +- src/Subscriber/Dockerfile | 2 +- src/Subscriber/Properties/launchSettings.json | 2 +- src/Subscriber/Subscriber.csproj | 2 +- src/deployment.yaml | 181 +++++ 8 files changed, 869 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f4398ed..60ffeeb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,686 @@ # Dapr with ASP.NET Core in Docker Compose and Kubernetes deployment test -> Warning: This project is not yet complete to run in Kubernetes. It is still in development. +The aim of this project is to provide a simple reference on how to perform local development ana then deploy a C# Dapr powered microservices application in Kubernetes. This project demonstrates the deployment of a sample application using RabbitMQ for messaging and Redis for state management, leveraging Dapr to simplify the development of distributed applications. + +## Project Structure + +The project includes the following components: + +* RabbitMQ: A message broker used for pub/sub messaging. +* Redis: A key-value store used for state management. +* Publisher App: A C# microservice that publishes messages to RabbitMQ and set Redis shared state. +* Subscriber App: A C# microservice that subscribes to messages from RabbitMQ and read Redis shared state. + +## Requirements + +To deploy this C# microservices application in Kubernetes with Dapr support, you need the following: + +* Kubernetes: A running Kubernetes cluster (version 1.18 or later recommended). +kubectl command-line tool configured to interact with your Kubernetes cluster. +* Configured Storage Class: A configured storage class in your Kubernetes cluster to provision persistent storage for stateful components like Redis. +* Dapr: Dapr CLI installed (version 1.0 or later). Dapr initialized in your Kubernetes cluster. + +In case you decide to build images from source code on your own, you also need: + +* Container registry: A configured container registry, such as Harbor or Docker Hub. + +In case of local development (no Kubernetes required): + +* Visual Studio 2022 Community: An integrated development environment (IDE) for developing C# applications. +* .NET 8 SDK: The software development kit for building and running .NET applications. +* Rancher Desktop or Docker Desktop: Tools for running Docker containers on your local machine. + +## Deployment + +The deployment is done using Kubernetes manifests for each component, including Dapr components for pub/sub and state management. +Sample application can be installed via [Yaml](#yaml-deployment) or [Powershell](#powershell-deployment) deployment + +### Yaml deployment + +```yaml +# RabbitMQ + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: rabbitmq + namespace: dapr-test +spec: + replicas: 1 + selector: + matchLabels: + app: rabbitmq + template: + metadata: + labels: + app: rabbitmq + spec: + containers: + - name: rabbitmq + image: rabbitmq:3-management + ports: + - containerPort: 5672 # RabbitMQ port + - containerPort: 15672 # RabbitMQ management port + env: + - name: RABBITMQ_DEFAULT_USER + value: admin + - name: RABBITMQ_DEFAULT_PASS + value: password + - name: RABBITMQ_DEFAULT_VHOST + value: "/" + +--- + +apiVersion: v1 +kind: Service +metadata: + name: rabbitmq-service + namespace: dapr-test +spec: + selector: + app: rabbitmq + ports: + - name: amqp + protocol: TCP + port: 5672 + targetPort: 5672 + - name: management + protocol: TCP + port: 15672 + targetPort: 15672 + +--- + +# Redis + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + namespace: dapr-test +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + containers: + - name: redis + image: redis:6.2.6 + ports: + - containerPort: 6379 + +--- + +apiVersion: v1 +kind: Service +metadata: + name: redis-service + namespace: dapr-test +spec: + selector: + app: redis + ports: + - protocol: TCP + port: 6379 + targetPort: 6379 + +--- + +# Publisher App + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: publisher + namespace: dapr-test +spec: + replicas: 1 + selector: + matchLabels: + app: publisher + template: + metadata: + labels: + app: publisher + annotations: + dapr.io/enabled: "true" + dapr.io/app-id: "publisher" + dapr.io/app-port: "5000" # Adjust to your service port + spec: + containers: + - name: publisher + image: cr.maks-it.com/dapr-test/subscriber:latest + ports: + - containerPort: 5000 # Match your internal app port + env: + - name: ASPNETCORE_ENVIRONMENT + value: Development + - name: ASPNETCORE_HTTP_PORTS + value: 5000 + +--- + +apiVersion: v1 +kind: Service +metadata: + name: publisher-service + namespace: dapr-test +spec: + selector: + app: publisher + ports: + - protocol: TCP + port: 80 + targetPort: 5000 # Match the internal port of the publisher service + +--- + +# Subscriber App + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: subscriber + namespace: dapr-test +spec: + replicas: 1 + selector: + matchLabels: + app: subscriber + template: + metadata: + labels: + app: subscriber + annotations: + dapr.io/enabled: "true" + dapr.io/app-id: "subscriber" + dapr.io/app-port: "5000" # Adjust to match the subscriber service port + spec: + containers: + - name: subscriber + image: cr.maks-it.com/dapr-test/subscriber:latest + ports: + - containerPort: 5000 # Match the internal port of the subscriber service + env: + - name: ASPNETCORE_ENVIRONMENT + value: Development + - name: ASPNETCORE_HTTP_PORTS + value: 5000 + +--- + +# Dapr PubSub + +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: pubsub + namespace: dapr-test +spec: + type: pubsub.rabbitmq + version: v1 + metadata: + - name: connectionString + value: "amqp://admin:password@rabbitmq:5672" + - name: durable + value: "false" + - name: deletedWhenUnused + value: "false" + - name: autoAck + value: "true" + - name: reconnectWait + value: "0" + - name: concurrency + value: parallel + +--- + +# Dapr StateStore + +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: statestore + namespace: dapr-test +spec: + type: state.redis + version: v1 + metadata: + - name: redisHost + value: redis:6379 + - name: keyPrefix + value: none + +--- + +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: privatestatestore + namespace: dapr-test +spec: + type: state.redis + version: v1 + metadata: + - name: redisHost + value: redis:6379 + +--- + +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: actorsstatestore + namespace: dapr-test +spec: + type: state.redis + version: v1 + metadata: + - name: redisHost + value: redis:6379 + - name: actorStateStore + value: "true" + +``` + +### Powershell deployment + +```powershell +kubectl create namespace dapr-test ` + +# RabbitMQ +@{ + apiVersion = "apps/v1" + kind = "Deployment" + metadata = @{ + name = "rabbitmq" + namespace = "dapr-test" + } + spec = @{ + replicas = 1 + selector = @{ + matchLabels = @{ + app = "rabbitmq" + } + } + template = @{ + metadata = @{ + labels = @{ + app = "rabbitmq" + } + } + spec = @{ + containers = @( + @{ + name = "rabbitmq" + image = "rabbitmq:3-management" + ports = @( + @{ + containerPort = 5672 # RabbitMQ port + }, + @{ + containerPort = 15672 # RabbitMQ management port + } + ) + env = @( + @{ + name = "RABBITMQ_DEFAULT_USER" + value = "admin" + }, + @{ + name = "RABBITMQ_DEFAULT_PASS" + value = "password" + }, + @{ + name = "RABBITMQ_DEFAULT_VHOST" + value = "/" + } + ) + } + ) + } + } + } +} | ConvertTo-Json -Depth 10 | kubectl apply -f - ` + +@{ + apiVersion = "v1" + kind = "Service" + metadata = @{ + name = "rabbitmq-service" + namespace = "dapr-test" + } + spec = @{ + selector = @{ + app = "rabbitmq" + } + ports = @( + @{ + name = "amqp" + protocol = "TCP" + port = 5672 + targetPort = 5672 + }, + @{ + name = "management" + protocol = "TCP" + port = 15672 + targetPort = 15672 + } + ) + } +} | ConvertTo-Json -Depth 10 | kubectl apply -f - ` + +# Redis +@{ + apiVersion = "apps/v1" + kind = "Deployment" + metadata = @{ + name = "redis" + namespace = "dapr-test" + } + spec = @{ + replicas = 1 + selector = @{ + matchLabels = @{ + app = "redis" + } + } + template = @{ + metadata = @{ + labels = @{ + app = "redis" + } + } + spec = @{ + containers = @( + @{ + name = "redis" + image = "redis:6.2.6" + ports = @( + @{ + containerPort = 6379 # Redis port + } + ) + } + ) + } + } + } +} | ConvertTo-Json -Depth 10 | kubectl apply -f - ` + +@{ + apiVersion = "v1" + kind = "Service" + metadata = @{ + name = "redis-service" + namespace = "dapr-test" + } + spec = @{ + selector = @{ + app = "redis" + } + ports = @( + @{ + protocol = "TCP" + port = 6379 + targetPort = 6379 + } + ) + } +} | ConvertTo-Json -Depth 10 | kubectl apply -f - ` + +# Publisher App +@{ + apiVersion = "apps/v1" + kind = "Deployment" + metadata = @{ + name = "publisher" + namespace = "dapr-test" + } + spec = @{ + replicas = 1 + selector = @{ + matchLabels = @{ + app = "publisher" + } + } + template = @{ + metadata = @{ + labels = @{ + app = "publisher" + } + annotations = @{ + "dapr.io/enabled" = "true" + "dapr.io/app-id" = "dapr-test-publisher" + "dapr.io/app-port" = "5000" # Adjust to your service port + } + } + spec = @{ + containers = @( + @{ + name = "publisher" + image = "cr.maks-it.com/dapr-test/publisher:latest" # Corrected image name + imagePullPolicy = "Always" + ports = @( + @{ + containerPort = 5000 # Match your internal app port + } + ) + env = @( + @{ + name = "ASPNETCORE_HTTP_PORTS" + value = "5000" + }, + @{ + name = "ASPNETCORE_ENVIRONMENT" + value = "Development" + } + ) + } + ) + } + } + } +} | ConvertTo-Json -Depth 10 | kubectl apply -f - ` + +@{ + apiVersion = "v1" + kind = "Service" + metadata = @{ + name = "publisher-service" + namespace = "dapr-test" + } + spec = @{ + selector = @{ + app = "publisher" + } + ports = @( + @{ + protocol = "TCP" + port = 80 + targetPort = 5000 # Match the internal port of the publisher service + } + ) + } +} | ConvertTo-Json -Depth 10 | kubectl apply -f - ` + +# Subscriber App +@{ + apiVersion = "apps/v1" + kind = "Deployment" + metadata = @{ + name = "subscriber" + namespace = "dapr-test" + } + spec = @{ + replicas = 1 + selector = @{ + matchLabels = @{ + app = "subscriber" + } + } + template = @{ + metadata = @{ + labels = @{ + app = "subscriber" + } + annotations = @{ + "dapr.io/enabled" = "true" + "dapr.io/app-id" = "dapr-test-subscriber" + "dapr.io/app-port" = "5000" # Adjust to match the subscriber service port + } + } + spec = @{ + containers = @( + @{ + name = "subscriber" + image = "cr.maks-it.com/dapr-test/subscriber:latest" + imagePullPolicy = "Always" + ports = @( + @{ + containerPort = 5000 # Match the internal port of the subscriber service + } + ) + env = @( + @{ + name = "ASPNETCORE_HTTP_PORTS" + value = "5000" + }, + @{ + name = "ASPNETCORE_ENVIRONMENT" + value = "Development" + } + ) + } + ) + } + } + } +} | ConvertTo-Json -Depth 10 | kubectl apply -f - ` + +# Dapr PubSub +@{ + apiVersion = "dapr.io/v1alpha1" + kind = "Component" + metadata = @{ + name = "dapr-test-pubsub" + namespace = "dapr-test" + } + spec = @{ + type = "pubsub.rabbitmq" + version = "v1" + metadata = @( + @{ + name = "connectionString" + value = "amqp://admin:password@rabbitmq-service:5672" + }, + @{ + name = "durable" + value = "false" + }, + @{ + name = "deletedWhenUnused" + value = "false" + }, + @{ + name = "autoAck" + value = "true" + }, + @{ + name = "reconnectWait" + value = "0" + }, + @{ + name = "concurrency" + value = "parallel" + } + ) + } +} | ConvertTo-Json -Depth 10 | kubectl apply -f - ` + +# Dapr StateStore +@{ + apiVersion = "dapr.io/v1alpha1" + kind = "Component" + metadata = @{ + name = "dapr-test-statestore" + namespace = "dapr-test" + } + spec = @{ + type = "state.redis" + version = "v1" + metadata = @( + @{ + name = "redisHost" + value = "redis-service:6379" + }, + @{ + name = "keyPrefix" + value = "none" + } + ) + } +} | ConvertTo-Json -Depth 10 | kubectl apply -f - ` + +@{ + apiVersion = "dapr.io/v1alpha1" + kind = "Component" + metadata = @{ + name = "dapr-test-privatestatestore" + namespace = "dapr-test" + } + spec = @{ + type = "state.redis" + version = "v1" + metadata = @( + @{ + name = "redisHost" + value = "redis-service:6379" + } + ) + } +} | ConvertTo-Json -Depth 10 | kubectl apply -f - ` + + +@{ + apiVersion = "dapr.io/v1alpha1" + kind = "Component" + metadata = @{ + name = "dapr-test-actorsstatestore" + namespace = "dapr-test" + } + spec = @{ + type = "state.redis" + version = "v1" + metadata = @( + @{ + name = "redisHost" + value = "redis-service:6379" + }, + @{ + name = "actorStateStore" + value = "true" + } + ) + } +} | ConvertTo-Json -Depth 10 | kubectl apply -f - +``` + +## Test + +* You have to port forward `publisher-service` and go to `/swagger` path in your browser. +* Publish a sample mesessage +* If everything is ok, you will see logs with your published message in subscriber service + +## Summary + +This project provides a reference for deploying a C# microservices application in Kubernetes with Dapr support. It includes configurations for RabbitMQ and Redis, as well as the publisher and subscriber applications. The Dapr components for pub/sub and state management are also configured to demonstrate how to leverage Dapr for building distributed applications. ## Contribution diff --git a/src/Publisher/Dockerfile b/src/Publisher/Dockerfile index fc87e77..5ed2d72 100644 --- a/src/Publisher/Dockerfile +++ b/src/Publisher/Dockerfile @@ -4,7 +4,7 @@ FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base USER app WORKDIR /app -EXPOSE 8080 +EXPOSE 5000 # This stage is used to build the service project diff --git a/src/Publisher/Properties/launchSettings.json b/src/Publisher/Properties/launchSettings.json index dc1d7cd..61e3752 100644 --- a/src/Publisher/Properties/launchSettings.json +++ b/src/Publisher/Properties/launchSettings.json @@ -23,7 +23,7 @@ "launchBrowser": true, "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", "environmentVariables": { - "ASPNETCORE_HTTP_PORTS": "8080" + "ASPNETCORE_HTTP_PORTS": "5000" }, "publishAllPorts": true, "useSSL": false diff --git a/src/Publisher/Publisher.csproj b/src/Publisher/Publisher.csproj index ca00cd2..7fc660f 100644 --- a/src/Publisher/Publisher.csproj +++ b/src/Publisher/Publisher.csproj @@ -9,10 +9,10 @@ - + - + diff --git a/src/Subscriber/Dockerfile b/src/Subscriber/Dockerfile index b78e470..9010474 100644 --- a/src/Subscriber/Dockerfile +++ b/src/Subscriber/Dockerfile @@ -4,7 +4,7 @@ FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base USER app WORKDIR /app -EXPOSE 8080 +EXPOSE 5000 # This stage is used to build the service project diff --git a/src/Subscriber/Properties/launchSettings.json b/src/Subscriber/Properties/launchSettings.json index 000c480..a35bdf8 100644 --- a/src/Subscriber/Properties/launchSettings.json +++ b/src/Subscriber/Properties/launchSettings.json @@ -23,7 +23,7 @@ "launchBrowser": true, "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", "environmentVariables": { - "ASPNETCORE_HTTP_PORTS": "8080" + "ASPNETCORE_HTTP_PORTS": "5000" }, "publishAllPorts": true, "useSSL": false diff --git a/src/Subscriber/Subscriber.csproj b/src/Subscriber/Subscriber.csproj index 30bf233..e871b82 100644 --- a/src/Subscriber/Subscriber.csproj +++ b/src/Subscriber/Subscriber.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/deployment.yaml b/src/deployment.yaml index e93bbf6..d713b51 100644 --- a/src/deployment.yaml +++ b/src/deployment.yaml @@ -1,3 +1,98 @@ +# RabbitMQ + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: rabbitmq + namespace: dapr-test +spec: + replicas: 1 + selector: + matchLabels: + app: rabbitmq + template: + metadata: + labels: + app: rabbitmq + spec: + containers: + - name: rabbitmq + image: rabbitmq:3-management + ports: + - containerPort: 5672 # RabbitMQ port + - containerPort: 15672 # RabbitMQ management port + env: + - name: RABBITMQ_DEFAULT_USER + value: admin + - name: RABBITMQ_DEFAULT_PASS + value: password + - name: RABBITMQ_DEFAULT_VHOST + value: "/" + +--- + +apiVersion: v1 +kind: Service +metadata: + name: rabbitmq-service + namespace: dapr-test +spec: + selector: + app: rabbitmq + ports: + - name: amqp + protocol: TCP + port: 5672 + targetPort: 5672 + - name: management + protocol: TCP + port: 15672 + targetPort: 15672 + +--- + +# Redis + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + namespace: dapr-test +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + containers: + - name: redis + image: redis:6.2.6 + ports: + - containerPort: 6379 + +--- + +apiVersion: v1 +kind: Service +metadata: + name: redis-service + namespace: dapr-test +spec: + selector: + app: redis + ports: + - protocol: TCP + port: 6379 + targetPort: 6379 + +--- + +# Publisher App + apiVersion: apps/v1 kind: Deployment metadata: @@ -22,6 +117,11 @@ spec: image: cr.maks-it.com/dapr-test/subscriber:latest ports: - containerPort: 5000 # Match your internal app port + env: + - name: ASPNETCORE_ENVIRONMENT + value: Development + - name: ASPNETCORE_HTTP_PORTS + value: 5000 --- @@ -40,6 +140,8 @@ spec: --- +# Subscriber App + apiVersion: apps/v1 kind: Deployment metadata: @@ -64,3 +166,82 @@ spec: image: cr.maks-it.com/dapr-test/subscriber:latest ports: - containerPort: 5000 # Match the internal port of the subscriber service + env: + - name: ASPNETCORE_ENVIRONMENT + value: Development + - name: ASPNETCORE_HTTP_PORTS + value: 5000 + +--- + +# Dapr PubSub + +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: pubsub + namespace: dapr-test +spec: + type: pubsub.rabbitmq + version: v1 + metadata: + - name: connectionString + value: "amqp://admin:password@rabbitmq:5672" + - name: durable + value: "false" + - name: deletedWhenUnused + value: "false" + - name: autoAck + value: "true" + - name: reconnectWait + value: "0" + - name: concurrency + value: parallel + +--- + +# Dapr StateStore + +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: statestore + namespace: dapr-test +spec: + type: state.redis + version: v1 + metadata: + - name: redisHost + value: redis:6379 + - name: keyPrefix + value: none + +--- + +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: privatestatestore + namespace: dapr-test +spec: + type: state.redis + version: v1 + metadata: + - name: redisHost + value: redis:6379 + +--- + +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: actorsstatestore + namespace: dapr-test +spec: + type: state.redis + version: v1 + metadata: + - name: redisHost + value: redis:6379 + - name: actorStateStore + value: "true"