Featured image for: Debug .NET applications running on Kubernetes with VS Code.

Visual Studio Code (VS Code) is the integrated development environment (IDE) used by most .NET programmers. Programmers generally run VS Code on their local systems, but in the cloud era, you'll sometimes want to debug applications in the Kubernetes or Red Hat OpenShift environment where you're deploying them. With a few plugins and configurations, VS Code can work very smoothly in these cloud environments. This article sets you up to debug your .NET applications in the cloud.

Set up your environment

Install the Kubernetes command-line interface (CLI), kubectl, which VS Code uses to interact with Kubernetes. On Fedora, you can install kubectl with the following command:

$ sudo dnf install kubernetes-client

If you are using OpenShift, also install the OpenShift oc CLI:

$ sudo dnf install origin-clients

If you haven't already installed Visual Studio Code, do so using the instructions at the VS Code website.

As your final step in setting up the environment, start the IDE and install the C# extension and Kubernetes extension.

Connect to your cluster

Next, you will connect to the cluster and select its namespace. The commands in this section indicate where you should enter the following:

  • Your hostname as $NAME
  • Your cluster namespace as $NAMESPACE
  • An authentication token as $TOKEN
  • Your cluster's URL as $URL

If you have installed the oc client, you can connect to your OpenShift cluster using the login command, then select the namespace using the project command:

$ oc login --token=$TOKEN --server=$URL
$ oc project $NAMESPACE

Alternatively, using kubectl, you can log in as follows:

$ kubectl config set-credentials kubeuser/$NAME --token=$TOKEN
$ kubectl config set-cluster $NAME --server=$URL
$ kubectl config set-context default/$NAME/kubeuser --user=kubeuser/$NAME --namespace=$NAMESPACE --cluster=$NAME
$ kubectl config use-context default/$NAME/kubeuser

Deploy your application

In this section, you'll use the OpenShift Source-to-Image (S2I) strategy to build and deploy an image on your cluster.

If you are not using OpenShift, you should build a container image from a .NET application and deploy it on the cluster. Be sure to build the application in the Debug configuration. Also, make sure your image contains the ps utility, which the Kubernetes plugin uses to find the .NET application. When you build the application, note the path where the source code resides. You'll need this information later to configure the debugger.

For the S2I build, we'll use the latest .NET version. Your cluster probably doesn't know about it yet, so import the version into your namespace using the -f option in the following command (for more information, see Installing .NET image streams):

$ oc create -f https://raw.githubusercontent.com/redhat-developer/s2i-dotnetcore/master/dotnet_imagestreams.json

Now, build and deploy the application from the following .NET source code:

$ oc new-app --name=demo-app 'dotnet:6.0~https://github.com/redhat-developer/s2i-dotnetcore-ex#dotnet-6.0' --build-env DOTNET_STARTUP_PROJECT=app DOTNET_CONFIGURATION=Debug

Note that we've used the DOTNET_STARTUP_PROJECT variable to indicate that the .NET project is under the app folder. We've also set DOTNET_CONFIGURATION to use the Debug configuration.

You can track the build and deployment in the OpenShift web console.

After a successful deployment, make the service publicly accessible:

$ oc expose svc/demo-app

To connect with the application, you need its URL, which you can get through the following command:

$ oc get routes demo-app
NAME          HOST/PORT                                                       
example-app   demo-app-mynamespace.myserver.com

Note that you will need the following information for the next step. It appears in the build log after the application is built:

$ oc logs bc/demo-app | head
Adding cluster TLS certificate authority to trust store
Cloning "https://github.com/redhat-developer/s2i-dotnetcore-ex" ...
    Commit:    2703bfe7303d1528d0ddb44b419e3114e5cb9f43 (6.0: replace app with 'dotnet new mvc --no-restore' (#62))
...

Debug the application

Start by getting the application sources. The following commands draw on the information shown in the build log from the previous section:

$ git clone https://github.com/redhat-developer/s2i-dotnetcore-ex
$ cd s2i-dotnetcore-ex
$ git checkout 2703bfe7303d1528d0ddb44b419e3114e5cb9f43

Now open VS Code with the working directory argument (.) to make the workspace folder match the source repository:

$ code .

On the left side of the user interface, navigate to the Kubernetes tab and expand cluster–>Workloads–>Pods until you see the pod that is running the application. Right-click the entry and choose Terminal, as shown in Figure 1.

The Pods menu lets you select Terminal and open a terminal in a tab.
Figure 1. The Pods menu lets you select Terminal and open a terminal in a tab.

A terminal window will open in the VS Code TERMINAL tab. From the terminal, install Microsoft's .NET Core debugger (vsdbg):

$ curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l ~/vsdbg

You can see the installation's progress in the terminal window, as shown in Figure 2. The installation ends by printing the directory where the executable was installed. Make a note of this directory because you'll need the information during configuration.

The command downloading vsdbg shows progress in the terminal.
Figure 2. The command downloading vsdbg shows progress in the terminal.

Next, configure the Kubernetes settings for debugging your application. Open the Command Palette by pressing Ctrl+Shift+P and choose Preferences: Open Workspace Settings (JSON), as shown in Figure 3.

You can search for the option that opens workspace settings.
Figure 3. You can search for the option that opens workspace settings.

An empty JSON document will open. Change the contents to the following and save the file:

{
  "vs-kubernetes": {
    "vs-kubernetes.dotnet-vsdbg-path": "/opt/app-root/vsdbg/vsdbg",
    "vs-kubernetes.dotnet-source-file-map": "/opt/app-root/src"
  }
}

Note that you are setting two paths. The first path (dotnet-vsdbg-path) is the location of vsdbg in the container. You can find the directory containing vsdbg in the output from the installation script, as mentioned earlier. Append /vsdbg to the directory to create the value for this setting.

The second path (dotnet-source-file-map) is the directory where the repository was checked out when the application was built. The debugger uses this setting to map the files referenced in the debug information to the files in the VS Code workspace. When you use the OpenShift S2I strategy, this folder is /opt/app-root/src.

You're ready to start your debugger. Right-click the pod once more and select Debug (Attach), as shown in Figure 4.

The Pods menu lets you select Debug options, one remote (Attach) and one local.
Figure 4. The Pods menu lets you select Debug options, one remote (Attach) and one local.

A drop-down menu will appear. Choose dotnet as the environment, as shown in Figure 5.

The environment in which you are debugging is "dotnet."
Figure 5. The environment in which you are debugging is "dotnet."

The VS Code debug console (Figure 6) will show the debugger starting.

The debug console starts showing output after the debugger attaches to the application.
Figure 6. The debug console starts showing output after the debugger attaches to the application.

Now you can add a breakpoint to the app and trigger it from the web application, as shown in Figure 7. You are successfully debugging an application running in your cluster.

The application stops where you set a breakpoint.
Figure 7. The application stops where you set a breakpoint.

Define a launch configuration

In the previous section, the Kubernetes plugin was responsible for starting the .NET debug session. You can also define a launch configuration that starts the debugger from VS Code's Debug tab. Within this configuration, you can also control additional debugger settings, such as justMyCode and requireExactSource.

To start, create a file named launch.json in the hidden .vscode directory with the following contents. If the file already exists, you can copy in the .NET Pod Attach configuration following the configurations that are already present:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": ".NET Pod Attach",
      "type": "coreclr",
      "request": "attach",
      "processId": 1,
      "pipeTransport": {
        "pipeProgram": "kubectl",
        "pipeArgs": [ "exec", "-i", "demo-app-2-6gkd4", "--" ],
        "debuggerPath": "/opt/app-root/vsdbg/vsdbg",
        "pipeCwd": "${workspaceFolder}",
        "quoteArgs": false
      },
      "sourceFileMap": {
        "/opt/app-root/src": "${workspaceFolder}"
      }
    }
  ]
}

If you look closely at the configuration, you'll see the name of the pod, the location of the debugger, and the mapping for the source code. These are the settings you provided to the Kubernetes plugin. The configuration expects the .NET process to run as process ID (pid) 1. This is true for the S2I containers, but you may need to adjust the value when using a different image.

Now, you can launch the debugger directly from VS Code's RUN AND DEBUG tab, as shown in Figure 8.

You can launch the debugger from the RUN AND DEBUG tab.
Figure 8. You can launch the debugger from the RUN AND DEBUG tab.

In this example, you've compiled the application with the Debug configuration, which yields the best experience in the debugger. You can debug an application in the Release configuration by adding "justMyCode ": false to the configuration. Note that release optimizations cause a mismatch between the source code and the CPU instructions, so the debugger could have issues setting breakpoints in this configuration. You can improve the match by disabling JIT optimizations. To do this, set COMPlus_JITMinOpts to 1 in the application deployment configuration.

Conclusion

Although programmers have traditionally used VS Code on their local system, the debugger works just as well for applications in a remote environment such as a Kubernetes cluster. This article has guided you through the steps to make debugging convenient for .NET applications on Kubernetes and OpenShift, and will hopefully encourage you to use those environments.

Last updated: November 8, 2023