The aim of this project is to provide a simple reference on how to perform local development and 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.
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.
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.
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 or Powershell deployment
# 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: "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
imagePullPolicy: Always
ports:
- containerPort: 5000 # Match your internal app port
env:
- name: ASPNETCORE_HTTP_PORTS
value: "5000"
- name: ASPNETCORE_ENVIRONMENT
value: Development
---
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: "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
---
# 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: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: dapr-test-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: dapr-test-privatestatestore
namespace: dapr-test
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: redis:6379
---
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:6379
- name: actorStateStore
value: "true"
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 -
- 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
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.
Contributions to this project are welcome! Please fork the repository and submit a pull request with your changes. If you encounter any issues or have feature requests, feel free to open an issue on GitHub.
If you have any questions or need further assistance, feel free to reach out:
- Email: maksym.sadovnychyy@gmail.com
This project is licensed under the MIT License. See the full license text below.
MIT License
Copyright (c) 2024 Maksym Sadovnychyy (MAKS-IT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.