Contact Us

Monitor Containerized ASP.NET Core Applications With Datadog APM | Datadog

Mobile App | May 2, 2022

ASP.NET Core is an open source web development framework that enables you to develop .NET applications on macOS, Linux, and Windows machines. The introduction of .NET Core in 2016 dramatically increased the number of ways to build and deploy .NET applications. This means that you need the ability to easily monitor application performance across a wide variety of platforms, such as Docker containers.

Being able to track requests across all of an application’s service and process boundaries helps you identify issues with services and their dependencies, such as slow database calls or overloaded servers. In this post, we’ll walk through how to instrument a sample containerized ASP.NET Core application to send traces to Datadog for monitoring by:

Datadog’s .NET tracer uses the .NET profiling APIs to add out-of-the-box instrumentation for many common libraries and programming languages used for both .NET Core and .NET frameworks, including VB.NET, C#, and F#. Once instrumented, your application will automatically send traces to the Datadog Agent, which aggregates and enhances them with additional metadata from the host before forwarding them to Datadog. For full details on the .NET tracer, check out our documentation.

To get started, make sure you have at least version 5 of the .NET Core SDK, the .NET CLI, and Docker Desktop installed. This will let you generate the sample ASP.NET Core application we’ll use throughout this guide.

You can use the .NET CLI to create a new web application project with all of the files needed to run a sample application via the following commands:

 
dotnet new sln -n DatadogContainerExample
dotnet new webapp -o DatadogContainerExample -n DatadogContainerExample 
dotnet sln add DatadogContainerExample

These commands create a new solution file (i.e., DatadogContainerExample.sln) and add a new Razor Pages web application project and associated DatadogContainerExample directory to the file.

Monitoring any application starts with deploying the Datadog Agent. There are many ways to do this, but here we’ll use Docker Compose to automatically configure the Datadog Docker Agent, which enables you to easily package and deploy Datadog with your containerized applications. Docker Compose provides a simple declarative format for orchestrating Docker containers when you don’t need the resilience or complexity of a solution like Kubernetes. Docker Compose is particularly useful for local development, but it can also be used in production environments.

To use Docker Compose to deploy the Datadog Agent, first create a new docker-compose.yml file in the same root directory as your DatadogContainerExample.sln file and add the following configuration:

./docker-compose.yml

 
version: "3.9"
services:
   datadog-agent:
    image: "gcr.io/datadoghq/agent:latest"
    environment:
      DD_APM_ENABLED: "true"
      DD_APM_NON_LOCAL_TRAFFIC: "true"
      DD_DOGSTATSD_NON_LOCAL_TRAFFIC: "true"
      DD_API_KEY: ${DD_API_KEY}

This file defines a single service called datadog-agent to build and run the latest version of the Datadog Agent container image. We’ll use this service to link the Agent to your containerized web application, which we’ll look at later when we instrument the application. The file also automatically configures the Agent to enable APM and allows it to listen for non-local traffic, which is required when the Agent and application are running in different containers. Finally, it passes in a DD_API_KEY environment variable for connecting the Agent to your Datadog account—your unique API key can be found in your account’s settings.

To launch the Datadog Agent, you can run the docker-compose up command in a terminal window. The Agent will automatically capture metrics from your host, enabling you to monitor host performance as soon as the Agent spins up.

The Datadog Agent runs alongside your containerized application and .NET tracer instrumentation, enabling you to capture traces as soon as you deploy the application. Next, we’ll show how you can use Datadog’s .NET tracer to automatically instrument your application running on a Linux container.

Linux containers provide a lightweight, cheap, and fast option for building .NET Core applications, and Datadog’s .NET tracer supports several Linux distributions. To deploy and instrument your application in a Linux container, create a Dockerfile in your project’s DatadogContainerExample directory:

./DatadogContainerExample/Dockerfile

FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
 
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
 
# Download the latest version of the tracer but don't install yet
RUN TRACER_VERSION=$(curl -s https://api.github.com/repos/DataDog/dd-trace-dotnet/releases/latest | grep tag_name | cut -d '"' -f 4 | cut -c2-) 
    && curl -Lo /tmp/datadog-dotnet-apm.deb https://github.com/DataDog/dd-trace-dotnet/releases/download/v${TRACER_VERSION}/datadog-dotnet-apm_${TRACER_VERSION}_amd64.deb
 
WORKDIR /src
COPY ["DatadogContainerExample/DatadogContainerExample.csproj", "DatadogContainerExample/"]
RUN dotnet restore "DatadogContainerExample/DatadogContainerExample.csproj"
COPY . .
WORKDIR "/src/DatadogContainerExample"
RUN dotnet build "DatadogContainerExample.csproj" -c Release -o /app/build
 
FROM build AS publish
RUN dotnet publish "DatadogContainerExample.csproj" -c Release -o /app/publish
 
FROM base AS final
 
# Copy the tracer from build target
COPY --from=build /tmp/datadog-dotnet-apm.deb /tmp/datadog-dotnet-apm.deb
# Install the tracer
RUN mkdir -p /opt/datadog 
    && mkdir -p /var/log/datadog 
    && dpkg -i /tmp/datadog-dotnet-apm.deb 
    && rm /tmp/datadog-dotnet-apm.deb
 
# Enable the tracer
ENV CORECLR_ENABLE_PROFILING=1
ENV CORECLR_PROFILER={846F5F1C-F9AE-4B07-969E-05C26BC060D8}
ENV CORECLR_PROFILER_PATH=/opt/datadog/Datadog.Trace.ClrProfiler.Native.so
ENV DD_DOTNET_TRACER_HOME=/opt/datadog
ENV DD_INTEGRATIONS=/opt/datadog/integrations.json
 
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "DatadogContainerExample.dll"]

We’ll look at the Dockerfile in more detail below, but it is designed to optimize the build process with multi-stage builds and ensure compatibility with Visual Studio, so you can debug issues locally via Visual Studio’s container tools. You can check out Microsoft’s documentation for details.

There are four build stages in the Dockerfile above: base, build, publish, and final. The mcr.microsoft.com/dotnet/aspnet:5.0 Docker image in the base stage is based on the Debian operating system and serves as the main image to run the application in the final stage.

In the build stage, the file uses the mcr.microsoft.com/dotnet/sdk:5.0 Docker image to first download the latest version of the Datadog .NET tracer—this image has the curl utility needed to download the tracer—then build the DatadogContainerExample project. To use a specific version of the tracer, you can set the TRACER_VERSION environment variable via a build argument instead. We’ll show how you can do that for Windows containers later.

The publish stage publishes the project and its dependencies to the /app/publish directory for deployment, and the final stage installs and enables the tracer with the necessary file configurations and environment variables.

To run your instrumented application and the Agent together, update the “docker-compose.yml” file with the following configuration:

./docker-compose.yml

version: "3.9"
services:
  web:
    build: 
      context: ./
      dockerfile: ./DatadogContainerExample/Dockerfile
    ports:
      - "8000:80"
    depends_on:
      - datadog-agent
    environment:
      DD_ENV: "core-local"
      DD_SERVICE: “DatadogContainerExample”
      DD_VERSION: "1.0.0"
      DD_AGENT_HOST: "datadog-agent"
      DD_TRACE_ROUTE_TEMPLATE_RESOURCE_NAMES_ENABLED: "true"
      DD_RUNTIME_METRICS_ENABLED: "true"
   datadog-agent:
    image: "gcr.io/datadoghq/agent:latest"
    environment:
      DD_APM_ENABLED: "true"
      DD_APM_NON_LOCAL_TRAFFIC: "true"
      DD_DOGSTATSD_NON_LOCAL_TRAFFIC: "true"
      DD_API_KEY: ${DD_API_KEY}

This update configures a new web service, which points to the ASP.NET Core application’s Dockerfile you created and exposes port 8000 on the localhost—mapping it to port 80 inside the container. It also links the web service’s container to the datadog-agent container at runtime by using the depends_on configuration option. Finally, the updated configuration sets several environment variables for the Datadog Agent to collect data from your application:

You can run the docker-compose up command in your project’s root directory again to launch the Datadog Agent and build your instrumented application. (You may need to run docker-compose down to stop previously launched containers.) Then you can navigate to http://localhost:8000 to view the application.

Since the Datadog Agent is deployed alongside the application, you will start seeing trace data in Datadog APM as you generate traffic.

In this section, we looked at how you can configure monitoring for a sample .NET Core application running in a Linux container with the containerized Agent and Datadog’s .NET tracer. Next, we’ll walk through the same process for instrumenting a .NET Core application running in a Windows container.

In most cases, you would run .NET Core applications on Linux containers because they typically consume fewer resources and are cheaper to develop on than Windows containers. However, your application may rely on services such as Microsoft’s Internet Information Services (IIS), or you may need to install Windows packages (e.g., .msi files) in Windows-only environments. There are two Windows containers available for these types of use cases:

To launch your instrumented application on these containers, we will make a few modifications to the Dockerfile and walk through the key changes.

The Windows Server Core container is useful if you need to support legacy applications or run utilities such as Powershell in your environment. To use this container, you can replace the contents of your Dockerfile with the following configuration:

./DatadogContainerExample/Dockerfile

FROM mcr.microsoft.com/dotnet/aspnet:5.0-windowsservercore-ltsc2019 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
 
FROM mcr.microsoft.com/dotnet/sdk:5.0-windowsservercore-ltsc2019 AS build
WORKDIR /src
COPY ["DatadogContainerExample/DatadogContainerExample.csproj", "DatadogContainerExample/"]
RUN dotnet restore "DatadogContainerExample/DatadogContainerExample.csproj"
COPY . .
WORKDIR "/src/DatadogContainerExample"
RUN dotnet build "DatadogContainerExample.csproj" -c Release -o /app/build
 
FROM build AS publish
RUN dotnet publish "DatadogContainerExample.csproj" -c Release -o /app/publish
 
FROM base AS final
 
# Download and install the tracer
# We recommend always using the latest release and regularly updating: https://github.com/DataDog/dd-trace-dotnet/releases/latest
ARG DD_TRACER_VERSION=1.27.0
ENV DD_TRACER_VERSION=$DD_TRACER_VERSION
ENV ASPNETCORE_URLS=http://*.80
 
ENV COR_ENABLE_PROFILING="1"
ENV COR_PROFILER="{846F5F1C-F9AE-4B07-969E-05C26BC060D8}"
 
ENV CORECLR_ENABLE_PROFILING="1"
ENV CORECLR_PROFILER=$COR_PROFILER
 
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
 
RUN Write-Host "Downloading Datadog .NET Tracer v$env:DD_TRACER_VERSION" ;
    (New-Object System.Net.WebClient).DownloadFile('https://github.com/DataDog/dd-trace-dotnet/releases/download/v' + $env:DD_TRACER_VERSION + '/datadog-dotnet-apm-' + $env:DD_TRACER_VERSION + '-x64.msi', 'datadog-apm.msi') ;
    Write-Host 'Installing Datadog .NET Tracer' ;
    Start-Process -Wait msiexec -ArgumentList '/i datadog-apm.msi /quiet /qn /norestart /log datadog-apm-msi-installer.log' ;
    Write-Host 'Datadog .NET Tracer installed, removing installer file' ;
    Remove-Item 'datadog-apm.msi'
 
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "DatadogContainerExample.dll"]

The updated file uses the mcr.microsoft.com/dotnet/aspnet:5.0-windowsservercore-ltsc2019 Docker image for the base and build stages and downloads and installs the tracer as part of the final stage. It also uses a build argument to specify which version of the tracer is installed. The other build stages remain the same.

Windows Nano Server is optimized for the cloud and includes the Kestrel web server, making it a lightweight option for .NET Core development in Windows environments. To run the .NET tracer and your application in this container, replace the contents of your Dockerfile with the following configuration:

./DatadogContainerExample/Dockerfile

FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
 
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
 
WORKDIR /src
COPY ["DatadogContainerExample/DatadogContainerExample.csproj", "DatadogContainerExample/"]
RUN dotnet restore "DatadogContainerExample/DatadogContainerExample.csproj"
COPY . .
WORKDIR "/src/DatadogContainerExample"
RUN dotnet build "DatadogContainerExample.csproj" -c Release -o /app/build
 
FROM build AS publish
RUN dotnet publish "DatadogContainerExample.csproj" -c Release -o /app/publish
 
FROM base AS final
 
WORKDIR /datadog
 
# Install the latest version of the tracer
ARG DD_TRACER_VERSION=1.27.0
ENV DD_TRACER_VERSION=$DD_TRACER_VERSION
RUN curl -Lo datadog-dotnet-apm.zip https://github.com/DataDog/dd-trace-dotnet/releases/download/v%DD_TRACER_VERSION%/windows-tracer-home.zip 
  && tar.exe -xf datadog-dotnet-apm.zip 
  && del datadog-dotnet-apm.zip
 
# Enable the tracer
ENV CORECLR_ENABLE_PROFILING=1
ENV CORECLR_PROFILER={846F5F1C-F9AE-4B07-969E-05C26BC060D8}
ENV CORECLR_PROFILER_PATH=/datadog/Datadog.Trace.ClrProfiler.Native.dll
ENV DD_DOTNET_TRACER_HOME=/datadog
ENV DD_INTEGRATIONS=/datadog/integrations.json
 
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "DatadogContainerExample.dll"]

This configuration uses the same mcr.microsoft.com/dotnet/aspnet:5.0 Docker image as the Linux deployment because the image supports both Linux and Windows Nano Server operating systems. The file also downloads and extracts a specific version of the tracer as a .zip file, which is required for installing the tracer on a Windows Nano Server container. The rest of the stages remain the same.

You can run your application for either Windows container using the same docker-compose up command, then navigate to http://localhost:8000 to view your application. You will not need to modify docker-compose.yml because the datadog-agent image will automatically recognize that your application is running in a Windows environment and launch the appropriate Windows-based container when you run the docker-compose command.

We’ve just looked at how you can set up Datadog’s Agent and .NET tracer for a sample .NET Core application running in two different Windows containers. Now that you have instrumented and deployed a sample .NET Core application on both Linux and Windows containers, you can monitor its performance in Datadog APM.

Datadog APM enables you to track distributed traces end to end and capture detailed performance metrics from your application, so you can get a better understanding of how your application services process requests.

To view your application’s traces, navigate to the service page in your Datadog account and locate the datadogcontainerexample service, which reflects the value you set for the DD_SERVICE tag in the docker-compose.yml file. You can select that service to see a high-level overview of application performance and visualizations for key metrics, such as the total number of requests, errors, and request latency.

You can then select an endpoint, which represent individual requests to an application page, and drill down to collected traces in order to view more details about each application call involved in processing the request.

You can also capture traces from any custom instrumentation to help you monitor the performance of your application’s business logic, such as methods that generate customer IDs or purchase order numbers.

For even greater visibility into your application, you can connect your .NET logs to your traces by automatically injecting trace and span IDs into your logs. This enables you to quickly pivot between your traces and .NET logs to get more context for an issue, so you can resolve it sooner.

This content was originally published here.