Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add .NET Core container debugging support #4699

Merged
merged 12 commits into from
Aug 25, 2020
48 changes: 48 additions & 0 deletions docs/content/en/docs/workflows/debug.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Debugging is currently supported for:
- NodeJS (runtime ID: `nodejs`)
- Java and JVM languages (runtime ID: `jvm`)
- Python (runtime ID: `python`)
- .NET Core (runtime ID: `netcore`)

Note that many debuggers may require additional information for the location of source files.
We are looking for ways to identify this information and to pass it back if found.
Expand Down Expand Up @@ -101,6 +102,53 @@ The DAP is supported by Visual Studio Code, [Eclipse LSP4e](https://projects.ecl
[and other editors and IDEs](https://microsoft.github.io/debug-adapter-protocol/implementors/tools/).
DAP is not yet supported by JetBrains IDEs like PyCharm.

#### .NET Core

.NET Core applications are configured to be deployed along with `vsdbg`.

In order to configure your application for debugging, your app must be:

- Identified as being dotnet-based by having an entrypoint using [dotnet](https://github.com/dotnet/sdk) cli
or one of the following environment variables `ASPNETCORE_URLS`, `DOTNET_RUNNING_IN_CONTAINER`,
`DOTNET_SYSTEM_GLOBALIZATION_INVARIANT`.
- Built with the `--configuration Debug` options to disable optimizations.

**Note for users of [VS Code's debug adapter for C#](https://github.com/OmniSharp/omnisharp-vscode):**
the following configuration can be used to debug a container. It assumes that your code is deployed
in `/app` or `/src` folder in the container. If that is not the case, the `sourceFileMap` property
should be changed to match the correct folder. `processId` is usually 1 but might be different if you
have an unusual entrypoint. You can also use `"${command:pickRemoteProcess}"` instead if supported by
your base image. (`//` comments must be stripped.)
```json
{
"name": "Skaffold Debug",
"type": "coreclr",
"request": "attach",
"processId" : 1,
"justMyCode": true, // set to `true` in debug configuration and `false` in release configuration
"pipeTransport": {
"pipeProgram": "kubectl",
"pipeArgs": [
"exec",
"-i",
"<NAME OF YOUR POD>", // name of the pod you debug.
"--"
],
"pipeCwd": "${workspaceFolder}",
"debuggerPath": "/dbg/netcore/vsdbg", // location where vsdbg binary installed.
"quoteArgs": false
},
"sourceFileMap": {
// Change this mapping if your app in not deployed in /src or /app in your docker image
"/src": "${workspaceFolder}",
"/app": "${workspaceFolder}"
// May also be like this, depending of your repository layout
// "/src": "${workspaceFolder}/src",
// "/app": "${workspaceFolder}/src/<YOUR PROJECT TO DEBUG>"
}
}
```

## IDE Support via Events and Metadata

`debug` provides additional support for IDEs to detect the debuggable containers and to determine
Expand Down
6 changes: 3 additions & 3 deletions integration/debug_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,19 @@ func TestDebug(t *testing.T) {
{
description: "kubectl",
deployments: []string{"java"},
pods: []string{"nodejs", "npm", "python3", "go"},
pods: []string{"nodejs", "npm", "python3", "go", "netcore"},
},
{
description: "kustomize",
args: []string{"--profile", "kustomize"},
deployments: []string{"java"},
pods: []string{"nodejs", "npm", "python3", "go"},
pods: []string{"nodejs", "npm", "python3", "go", "netcore"},
},
{
description: "buildpacks",
args: []string{"--profile", "buildpacks"},
deployments: []string{"java"},
pods: []string{"nodejs", "npm", "python3", "go"},
pods: []string{"nodejs", "npm", "python3", "go", "netcore"},
},
}
for _, test := range tests {
Expand Down
2 changes: 2 additions & 0 deletions integration/testdata/debug/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
node_modules
*.swp
netcore/**/obj
netcore/**/bin
1 change: 1 addition & 0 deletions integration/testdata/debug/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ resources:
- npm/k8s/pod.yaml
- python3/k8s/pod.yaml
- go/k8s/pod.yaml
- netcore/k8s/pod.yaml
23 changes: 23 additions & 0 deletions integration/testdata/debug/netcore/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile
# to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
COPY ["src/HelloWorld/HelloWorld.csproj", "src/HelloWorld/"]
RUN dotnet restore "src/HelloWorld/HelloWorld.csproj"
COPY . .
WORKDIR "/src/HelloWorld"
RUN ls -al
RUN dotnet build "HelloWorld.csproj" --configuration Debug -o /app/build

FROM build AS publish
RUN dotnet publish "HelloWorld.csproj" --configuration Debug -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "HelloWorld.dll"]
26 changes: 26 additions & 0 deletions integration/testdata/debug/netcore/k8s/pod.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
apiVersion: v1
kind: Pod
metadata:
name: netcore
spec:
containers:
- name: dotnet-web
image: skaffold-debug-netcore
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 5
failureThreshold: 4
timeoutSeconds: 5
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 5
failureThreshold: 30
timeoutSeconds: 5
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Mvc;

namespace HelloWorld.Controllers
{
[ApiController]
[Route("/")]
public class HomeController : ControllerBase
{
[HttpGet]
public string Get()
{
return "Ok";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

</Project>
26 changes: 26 additions & 0 deletions integration/testdata/debug/netcore/src/HelloWorld/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace HelloWorld
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
39 changes: 39 additions & 0 deletions integration/testdata/debug/netcore/src/HelloWorld/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace HelloWorld
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
10 changes: 10 additions & 0 deletions integration/testdata/debug/netcore/src/HelloWorld/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
7 changes: 7 additions & 0 deletions integration/testdata/debug/skaffold.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ build:
context: python3
- image: skaffold-debug-go
context: go
- image: skaffold-debug-netcore
context: netcore

deploy:
kubectl:
Expand All @@ -24,6 +26,7 @@ deploy:
- npm/k8s/pod.yaml
- python3/k8s/pod.yaml
- go/k8s/pod.yaml
- netcore/k8s/pod.yaml

profiles:
- name: kustomize
Expand Down Expand Up @@ -54,3 +57,7 @@ profiles:
context: go
buildpacks:
builder: "gcr.io/buildpacks/builder:v1"
- image: skaffold-debug-netcore
context: netcore
buildpacks:
builder: "gcr.io/buildpacks/builder:v1"
2 changes: 1 addition & 1 deletion pkg/skaffold/debug/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ import (
type ContainerDebugConfiguration struct {
// Artifact is the corresponding artifact's image name used in the skaffold.yaml
Artifact string `json:"artifact,omitempty"`
// Runtime represents the underlying language runtime (`go`, `jvm`, `nodejs`, `python`)
// Runtime represents the underlying language runtime (`go`, `jvm`, `nodejs`, `python`, `netcore`)
Runtime string `json:"runtime,omitempty"`
// WorkingDir is the working directory in the image configuration; may be empty
WorkingDir string `json:"workingDir,omitempty"`
Expand Down
76 changes: 76 additions & 0 deletions pkg/skaffold/debug/transform_netcore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
Copyright 2020 The Skaffold Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package debug

import (
"strings"

"github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
)

type netcoreTransformer struct{}

func init() {
containerTransforms = append(containerTransforms, netcoreTransformer{})
}

// isLaunchingNetcore determines if the arguments seems to be invoking dotnet
func isLaunchingNetcore(args []string) bool {
if len(args) < 2 {
return false
}

if args[0] == "dotnet" || strings.HasSuffix(args[0], "/dotnet") {
return true
}

if args[0] == "exec" && (args[1] == "dotnet" || strings.HasSuffix(args[1], "/dotnet")) {
return true
}

return false
}

func (t netcoreTransformer) IsApplicable(config imageConfiguration) bool {
// Some official base images (eg: dotnet/core/runtime-deps) contain the following env vars
for _, v := range []string{"ASPNETCORE_URLS", "DOTNET_RUNNING_IN_CONTAINER", "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"} {
if _, found := config.env[v]; found {
return true
}
}

if len(config.entrypoint) > 0 && !isEntrypointLauncher(config.entrypoint) {
return isLaunchingNetcore(config.entrypoint)
}

if len(config.arguments) > 0 {
return isLaunchingNetcore(config.arguments)
}

return false
}

// Apply configures a container definition for vsdbg.
// Returns a simple map describing the debug configuration details.
func (t netcoreTransformer) Apply(container *v1.Container, config imageConfiguration, portAlloc portAllocator) (ContainerDebugConfiguration, string, error) {
logrus.Infof("Configuring %q for netcore debugging", container.Name)

return ContainerDebugConfiguration{
Runtime: "netcore",
}, "netcore", nil
}
Loading