Diagnostic .NET Apps in Kubernetes with dotnet-monitor

Somebody who likes to code
Search for a command to run...

Somebody who likes to code
No comments yet. Be the first to comment.
Google processes tens of thousands of code changes every day across a codebase of hundreds of millions of lines. The secret isn't magic tooling — it's a documented, principled approach to code review

Amazon Simple Notification Service (SNS) looks straightforward on the surface — publish a message, deliver it to subscribers — but the gap between a topic that "works in dev" and one that is productio

Amazon Simple Queue Service (SQS) is deceptively simple to get started with, but surprisingly easy to misconfigure in ways that cause silent message loss, runaway costs, duplicate processing storms, o

HTTP API is the right default choice for the majority of .NET serverless workloads. At 70% less cost than REST API, lower latency, and native JWT authorization, it covers most production requirements

AWS Lambda has become a cornerstone of modern serverless architectures, but writing a function that "just works" is very different from writing one that is performant, cost-effective, secure, and read

Today, we will review one of the most common dotnet-monitor use cases: running as a side card in a Kubernetes cluster. We recommend having some background in Kubernetes and reviewing our previous articles if you haven't already done so:

In the image above, we are displaying a pod with two containers: one is the application we want to monitor, and the other is the dotnet-monitor. Both are using a shared volume for communication. To get diagnostic data, a user can access the dotnet-monitor API container through port forwarding or set up collection rules to upload the artifact to external storage.
Install Docker Desktop
Enable Kubernetes (the standalone version included in Docker Desktop)
Ensure you have an Azure Account
Install Azure CLI
Download the example code.
Log into Azure by running az login, and create a storage account and container using the following commands:
az group create -l eastus -n MyResourceGroup
az storage account create --name 24f12674a14143d --resource-group MyResourceGroup --location eastus --sku Standard_ZRS --encryption-services blob
$assignee = az ad signed-in-user show --query objectId -o tsv
az role assignment create --role "Storage Blob Data Contributor" --assignee $assignee --scope "/subscriptions/e759b3f9-6ac3-4f9d-b479-1ba4471235cd/resourceGroups/MyResourceGroup/providers/Microsoft.Storage/storageAccounts/24f12674a14143d"
az storage container create --account-name 24f12674a14143d --name mycontainer --auth-mode login
Obtain the account keys by executing the following command:
az storage account keys list -g MyResourceGroup -n 24f12674a14143d
At the project level, we need a Dockerfile to containerize our application. It should include the following content:
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
COPY ["DotNetMonitorSandBox/DotNetMonitorSandBox.csproj", "DotNetMonitorSandBox/"]
RUN dotnet restore "DotNetMonitorSandBox/DotNetMonitorSandBox.csproj"
COPY . .
WORKDIR "/DotNetMonitorSandBox"
RUN dotnet build "DotNetMonitorSandBox.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "DotNetMonitorSandBox.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "DotNetMonitorSandBox.dll"]
At the solution level, execute the following command to create the container image:
docker build -t raulnq/dotnetmonitorsandbox:1.0 -f .\DotNetMonitorSandBox\Dockerfile .
We will configure dotnet-monitor using a JSON file, which will be mounted in the pod from a config map. The configmap.yaml file will contain the following content:
apiVersion: v1
kind: ConfigMap
metadata:
name: dotnet-monitor-configmap
data:
settings.json: |
{
"Egress": {
"AzureBlobStorage": {
"monitorBlob": {
"accountUri": "https://24f12674a14143d.blob.core.windows.net/",
"containerName": "mycontainer",
"blobPrefix": "artifacts",
"accountKey": "{account-key}"
}
}
},
"DiagnosticPort": {
"ConnectionMode": "Listen",
"EndpointName": "/diag/dotnet-monitor.sock"
},
"CollectionRules": {
"LargeGCHeapSize": {
"Trigger": {
"Type": "EventCounter",
"Settings": {
"ProviderName": "System.Runtime",
"CounterName": "gc-heap-size",
"GreaterThan": 10
}
},
"Actions": [
{
"Type": "CollectGCDump",
"Settings": {
"Egress": "monitorBlob"
}
}
],
"Limits": {
"ActionCount": 2,
"ActionCountSlidingWindowDuration": "1:00:00"
}
}
}
}
In the JSON content, there are three sections of interest (refer to previous articles for details):
Egress: In this section, we define the target Azure Blob Storage to which the diagnostic artifacts will be sent.
DiagnosticPort: We use the listen mode and define the Unix Domain Socket used by the dotnet-monitor.
CollectionRules: In this section, a rule is defined to collect a GCDump when the size of the heap exceeds 10MB.
The next step involves defining a deployment.yaml file as follows:
apiVersion: apps/v1
kind: Deployment
metadata:
name: dotnet-monitor-deployment
spec:
replicas: 1
selector:
matchLabels:
app: dotnet-monitor-app
template:
metadata:
labels:
app: dotnet-monitor-app
spec:
restartPolicy: Always
containers:
- name: app
image: raulnq/dotnetmonitorsandbox:1.0
imagePullPolicy: IfNotPresent
env:
- name: DOTNET_DiagnosticPorts
value: /diag/dotnet-monitor.sock,suspend
volumeMounts:
- mountPath: /diag
name: diagvol
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: 250m
memory: 512Mi
- name: monitor
image: mcr.microsoft.com/dotnet/monitor:8
args: [ "collect", "--no-auth" ]
imagePullPolicy: IfNotPresent
env:
- name: DOTNETMONITOR_Urls
value: http://localhost:52323
volumeMounts:
- mountPath: /diag
name: diagvol
- mountPath: /etc/dotnet-monitor/settings.json
name: configvol
subPath: settings.json
resources:
requests:
cpu: 50m
memory: 32Mi
limits:
cpu: 250m
memory: 256Mi
volumes:
- name: diagvol
emptyDir: {}
- name: configvol
configMap:
name: dotnet-monitor-configmap
The deployment above consists of a couple of volumes and two containers:
volumes
diagvol: An emptyDir volume provides an empty directory that containers in the pod can read from and write to.
configvol: We can add files to a config map and mount them into a container.
containers
app: Our primary application, in which we establish an environment variable that corresponds to the diagnostic port utilized by dotnet-monitor. In addition to this, we are mounting the emptyDir volume that will be used for communication with the monitor container.
monitor: In this container, we are mounting the same emptyDir volume and the setting file.
Run the following commands to deploy our application to the Kubernetes cluster:
kubectl apply -f .\configmap.yaml
kubectl apply -f .\deployment.yaml
To test dotnet-monitor, we will perform port forwarding for the main application. First, execute the command kubectl get pods to obtain the pod name, and then proceed with the following command:
kubectl port-forward {pod-name} 8081:80
We will simulate a memory leak by repeatedly accessing the endpoint http://localhost:8081/memory-leak. Over time, we will begin to see GC dumps appear in our Azure Blob Storage.

In conclusion, using dotnet-monitor as a sidecar in Kubernetes enables efficient diagnostics and monitoring of .NET applications, simplifying the process of collecting diagnostic data in a containerized environment. Thank you, and happy coding.