銀の光と碧い空

クラウドなインフラとC#なアプリ開発の狭間にいるエンジニアの日々

Visual Studio Tools for Docker が ASP.NET CoreのDockerデバッグ実行している仕組みを追ってみた

Docker for Windowsを使うと、Visual StudioからF5でコンテナ内にアプリをデプロイしてデバッグできるようになったという記事を見たのですが

kiyokura.hateblo.jp

気になるのは、どうやってデバッグしているかということです。ポイントとしてはDockerとホストで共有フォルダの設定をしないといけないということとと、もともとMicrosoftがクロスプラットフォームなデバッグ向けにMIEngineというものを開発しているという点です。

github.com

このあたりを手がかりに探ってみました*1

まず、Visual Studio側でDocker Suppotを設定したときに作成されるファイルを見てみます。Dockerfileは非常にシンプルです。

FROM microsoft/aspnetcore:1.0.1
ENTRYPOINT ["dotnet", "WebApplication1.dll"]
ARG source=.
WORKDIR /app
EXPOSE 80
COPY $source .

ビルドしたバイナリの転送とか、リモートデバッグの仕組みはほかにありそうです。docker-compose.dev.debug.ymlを見てみましょう。

version: '2'

services:
  webapplication1:
    build:
      args:
        source: obj/Docker/empty/
    labels:
      - "com.microsoft.visualstudio.targetoperatingsystem=linux"
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - DOTNET_USE_POLLING_FILE_WATCHER=1
    volumes:
      - .:/app
      - ~/.nuget/packages:/root/.nuget/packages:ro
      - ~/clrdbg:/clrdbg:ro
    entrypoint: tail -f /dev/null

環境変数の設定や、volumeのマウントが見えます。アプリケーションバイナリと、NuGetのバイナリは別にマウントしているんですね。clrdbgもマウントしていることから、やはりclrdbgを利用してリモートデバッグしているようです。

それでは、実際にリモートデバッグしたときの出力を見てみましょう。

1>------ ビルド開始: プロジェクト:WebApplication1, 構成:Debug Any CPU ------
1>  docker  ps --filter "name=webapplication1_webapplication1" --format {{.ID}} -n 1
1>  b72591744860
1>  docker  exec -i b72591744860 /bin/bash -c "if PID=$(pidof -x dotnet); then kill $PID; fi"
1>  C:\Program Files\dotnet\dotnet.exe build "d:\documents\visual studio 2015\Projects\WebApplication1" --configuration Debug --no-dependencies
1>  Project WebApplication1 (.NETCoreApp,Version=v1.0) was previously compiled. Skipping compilation.
1>  docker-compose -f "d:\documents\visual studio 2015\Projects\WebApplication1\docker-compose.yml" -f "d:\documents\visual studio 2015\Projects\WebApplication1\docker-compose.dev.debug.yml" -p "webapplication1" up -d
1>  webapplication1_webapplication1_1 is up-to-date
1>  docker  ps --filter "name=webapplication1_webapplication1" --format {{.ID}} -n 1
1>  b72591744860
1>  docker  exec  b72591744860 /bin/bash -c "files=(/app/*); if [ ${#files[@]} -gt 1 ]; then echo true; fi"
1>  true
1>  docker  exec  b72591744860 /bin/bash -c "files=(/root/.nuget/packages/*); if [ ${#files[@]} -gt 1 ]; then echo true; fi"
1>  true
1>  docker  exec  b72591744860 /bin/bash -c "files=(/clrdbg/*); if [ ${#files[@]} -gt 1 ]; then echo true; fi"
1>  true
1>  docker  exec -i b72591744860 /bin/bash -c "if PID=$(pidof -x dotnet); then kill $PID; fi"
========== ビルド: 1 正常終了、0 失敗、0 更新不要、0 スキップ ==========

実行中のコンテナを見つけて、内部のdotnetプロセスがあれば殺して、ビルドして、docker compose をupdateしています。実際に、~に対応する、Windowsのユーザーのホームディレクトリ%USERPROFILE%の下のclrdbgフォルダを見ると、.soファイルといったデバッグのためのバイナリが用意されています。

次に、Dockerの出力を見るとこのようになっています。ポートを見つけて、ブラウザで該当ページを開いています。

docker  ps --filter "name=webapplication1_webapplication1" --format {{.ID}} -n 1
b72591744860
docker  inspect --format="{{json .NetworkSettings.Ports}}" b72591744860
{"80/tcp":[{"HostIp":"0.0.0.0","HostPort":"32770"}]}
Waiting for response from http://localhost:32770/ ...
Launching http://localhost:32770/ ...

リモートデバッグするためのコマンドが表示されていないのですが、これはVisual Studio Tools for Docker がVisual Studio拡張内部で実行しているのではと推測しています。それを確認する方法ですが、タスクスケジューラーを開きながらリモートデバッグが開始すると、docker.exeプロセスが新規に実行されることがわかります。これの詳細を見てみましょう。

PS> Get-WmiObject Win32_Process -Filter "name='docker.exe'"

Caption                    : docker.exe
CommandLine                : "docker" exec -i b72591744860 /clrdbg/clrdbg --interpreter=mi

関係あるところだけ抜き出しましたが、docker exec でclrdbgを実行していました。この仕掛けは、以前.NET Conf Japanでデモをした Visual Studio からリモートLinuxで実行する.NET Coreプロセスをデバッグ実行する、と同様のものです。

github.com

なので、この拡張をさらに拡張すれば、OpenShiftといった任意のコンテナプラットフォームに対してリモートデバッグできるのではと推測している今日このごろです。

*1:OSS Loveを謳うMicrosoftなのでいずれこのVS拡張は公開されて、コードを見ればわかる気もしますが