Debugging with Docker Compose in Visual Studio 2017

The generated Docker Compose files are shown under the top-level solution object:

There's a basic docker-compose.yml file with the web application defined as a service, complete with build details for the Dockerfile:

version: '3.4'

services:
webapi.netfx:
image: ${DOCKER_REGISTRY-}webapinetfx
build:
context: .WebApi.NetFx
dockerfile: Dockerfile

There's also a docker-compose.override.yml file, which adds the port and network configuration so that it can run locally:

version: '3.4'

services:
webapi.netfx:
ports:
- "80"
networks:
default:
external:
name: nat

There's nothing here about building the application because the compilation is done in Visual Studio rather than in Docker. The built application binaries live on your development machine and are copied into the container. When you hit F5, the container is started and Visual Studio launches a browser at the container's IP address. You can add breakpoints to code in Visual Studio, and when you navigate to that code from the browser, you'll be taken to the debugger in Visual Studio:

It's a seamless experience, but it's not clear what's happening—how does the Visual Studio debugger on your machine connect to the binaries inside the container? Fortunately, Visual Studio logs all the Docker commands it issues to the output windows, so you can track down how this works.

In the build output window, you'll see something like this:

1>------ Build started: Project: WebApi.NetFx, Configuration: Debug Any CPU ------
1> WebApi.NetFx -> C:UsersAdministratorsource eposWebApi.NetFxWebApi.NetFxinWebApi.NetFx.dll
2>------ Build started: Project: docker-compose, Configuration: Debug Any CPU ------
2>docker-compose -f "C:UsersAdministratorsource eposWebApi.NetFxdocker-compose.yml" -f "C:UsersAdministratorsource eposWebApi.NetFxdocker-compose.override.yml" -f "C:UsersAdministratorsource eposWebApi.NetFxobjDockerdocker-compose.vs.debug.g.yml" -p dockercompose1902887664513455984 --no-ansi up -d
2>dockercompose1902887664513455984_webapi.netfx_1 is up-to-date
========== Build: 2 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

You can see that the build happens first, and then the container is launched with docker-compose up. The docker-compose.yml and docker-compose.override.yml files we've already seen are used, along with a file called docker-compose.vs.debug.g.yml. Visual Studio generates that file on the build, and you need to show all the files in the solution to see it. It contains additional Docker Compose settings:

services:
webapi.netfx:
image: webapinetfx:dev
build:
args:
source: obj/Docker/empty/
volumes:
- C:UsersAdministratorsource eposWebApi.NetFxWebApi.NetFx:C:inetpubwwwroot
- C:Program Files (x86)Microsoft Visual Studio2017ProfessionalCommon7IDERemote Debugger:C: emote_debugger:ro
entrypoint: cmd /c "start /B C:\ServiceMonitor.exe w3svc & C:\remote_debugger\x64\msvsmon.exe /noauth /anyuser /silent /nostatus /noclrwarn /nosecuritywarn /nofirewallwarn /nowowwarn /timeout:2147483646"

There's a lot going on here:

  • The Docker image uses the dev tag to distinguish it from the release build
  • The build argument for the source location specifies an empty directory
  • A volume is used to mount the web root in the container from the project folder on the host
  • A second volume is used to mount the Visual Studio remote debugger in the container from the host
  • The entrypoint launches ServiceMonitor to run IIS, and then launches msvsmon, which is the remote debugger

In debug mode, the argument for the source code environment variable is an empty directory. Visual Studio builds a Docker image with an empty wwwroot directory and then mounts the source code folder from the host into the web root in the container to populate that folder at runtime.

When the container is running, Visual Studio runs some commands inside the container to set permissions, which allows the remote debugger tool to work. In the output window for Docker, you'll see something like this:

========== Debugging ==========
docker ps --filter "status=running" --filter "name=dockercompose1902887664513455984_webapi.netfx_" --format {{.ID}} -n 1
3e2b6a7cb890
docker inspect --format="{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}" 3e2b6a7cb890
172.27.58.105
docker exec 3e2b6a7cb890 cmd /c "C:WindowsSystem32inetsrvappcmd.exe set config -section:system.applicationHost/applicationPools /[name='DefaultAppPool'].processModel.identityType:LocalSystem /commit:apphost & C:WindowsSystem32inetsrvappcmd.exe set config -section:system.webServer/security/authentication/anonymousAuthentication /userName: /commit:apphost"
Applied configuration changes to section "system.applicationHost/applicationPools" for "MACHINE/WEBROOT/APPHOST" at configuration commit path "MACHINE/WEBROOT/APPHOST"
Applied configuration changes to section "system.webServer/security/authentication/anonymousAuthentication" for "MACHINE/WEBROOT/APPHOST" at configuration commit path "MACHINE/WEBROOT/APPHOST"
Launching http://172.27.58.105/ ...

That's Visual Studio fetching the ID of the container it launched with Docker Compose, then running appcmd to set the IIS application pool to use an administrative account and to set the web server to allow anonymous authentication.

Visual Studio 2017 keeps the container running in the background when you stop debugging. If you make a change to the program and rebuild, the same container is used so that there's no startup lag. By mounting the project location into the container, any changes in content or binaries are reflected when you rebuild. By mounting the remote debugger from the host, your image doesn't have any development tools baked into it; they stay on the host.

This is the inner loop process, where you get fast feedback. Whenever you change and rebuild your app, you see these changes in the container. However, the Docker image from debug mode is not usable for the outer loop CI process; the app is not copied into the image; it works only if you mount the app from your local source into a container.

To support the outer loop, there's also a Docker compose override file for release mode in a second hidden override file, docker-compose.vs.release.g.yml:

services:
webapi.netfx:
build:
args:
source: obj/Docker/publish/
volumes:
- C:Program Files (x86)Microsoft Visual Studio2017ProfessionalCommon7IDERemote Debugger:C: emote_debugger:ro
entrypoint: cmd /c "start /B C:\ServiceMonitor.exe w3svc & C:\remote_debugger\x64\msvsmon.exe /noauth /anyuser /silent /nostatus /noclrwarn /nosecuritywarn /nofirewallwarn /nowowwarn /timeout:2147483646"
labels:
com.microsoft.visualstudio.debuggee.program: "C:\app\WebApi.NetFx.dll"
com.microsoft.visualstudio.debuggee.workingdirectory: "C:\app"

The difference here is that there's no volume mapping the local source location to the web root in the container. When you compile in release mode, the value of the source argument is a published location that contains the web app. Visual Studio builds the release image by packaging the published application into the container.

In release mode, you can still run the application in a Docker container and you can still debug the application. But you lose the fast feedback loop because to change the app, Visual Studio needs to rebuild the Docker image and start a new container.

This is a fair compromise, and the Docker tooling in Visual Studio 2017 gives you a seamless development experience, along with the basis for your CI build. One thing Visual Studio 2017 doesn't do is use multi-stage builds, so the project compilation still happens on the host rather than inside a container. This makes the generated Docker artifacts less portable, so you need more than just Docker to build this app on a server.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.166.255