WSL2上のDockerをWindows上のVSCodeから操作する
目的
以前に WindowsでもサクサクDocker (Docker on WSL2 without Docker Desktop) という記事を公開していますが、WSL上のDockerを使ってVSCodeで開発する際の環境を構築します。
元になるWSL上でDocker環境を構築する手法や、その環境でGPUを使えるようにする方法に関しては、下記記事を参照してください。
内容
WSL上のDockerを使って開発を行うためにはまずWSLにアクセスし、WSL上でリポジトリを展開したうえでコンテナを起動するのが通常の手順です。
ただ、WSLはあくまでサンドボックスとして scrap and build を繰り返したいですし、Windowsのファイルシステムとは別にWSL上のファイルを管理するのは非常に面倒です。
できればWindowsのファイルシステムでWSL上のDockerを使って開発を行いたいところです。
本記事ではWindows上で実行中のVSCodeから、WSL上のDockerを操作するための環境を構築します。
本記事の構成は、以下になります。
- DevContainers 拡張機能
- Docker 拡張機能
- Docker CLIのインストール
- Docker CLIのインストール
- Docker Composeのインストール
- WSL上のDocker Engineへのアクセス設定
- リモート(WSL側)の設定
- ホスト(Windows側)の設定
- Docker CLIのインストール
- 制限事項
方法
DevContainers 拡張機能
DevContainers 拡張機能はVSCodeからDevContainerを起動し、起動されたコンテナ内で開発を行う非常に便利な機能です。
Windowsで実行中のVSCodeからWSL上のDockerでDevContainerを実行するための設定は非常に簡単です。
@ext:ms-vscode-remote.remote-containers
の Execute in WSL
をチェックするだけで、ホストPC(Windows)からDevContainerの起動時にはWSL上のDockerが使われるようになります。
また、必要に応じて Execute in WSLDistro
を変更し、Docker Engineが起動しているディストリビューションを指定することが可能です。(未設定の場合はデフォルトのディストリビューション(通常はUbuntu)が使われます)
これらの設定を行うことでDevContainer利用時には、VSCodeは内部的に指定されたディストリビューションに接続したうえで、コンテナの起動を試みるようです。
コンテナの起動に成功すると、VSCodeは起動したコンテナにアタッチし、そのコンテナ内で開発を行うことができるようになります。
なお、こちらの設定は環境に依存するため、複数の環境をでVSCodeを使っている場合は、歯車アイコンから同期設定を外すことをオススメします。
過去に発生していた問題 (2024/10/29 時点で解消済み)
また、ワークスペースオープン時などに表示される「フォルダーに開発コンテナーの構成ファイルが含まれています。…」の通知画面にある「コンテナーで再度開く」ボタンからコンテナを起動しようとすると下記のエラーが発生します。
(WSL内でWindowsのPathを使おうとしてエラーになっているっぽい)
[6810 ms] Start: Run in Host: wslpath -w c:/workspace
[6988 ms] Command failed: ls -a c:/workspace
[6988 ms] ls: cannot access 'c:/workspace': No such file or directory
[6988 ms] Exit code 2
こちらについては、通知ボタンは利用せず、VSCodeの画面の左下にあるリモートメニューから「コンテナーで再度開く」を実行するか、コマンドパレット(Ctrl+shift+P)から「Dev Containers: Reopen in Container」を実行すれば、正常にDevContainerが起動します。
Docker 拡張機能
続いてDocker 拡張機能の設定を行います。
Docker 拡張機能は、VSCodeからDockerコマンドを使ってコンテナの操作を行うための拡張機能です。
GUIでイメージやコンテナの管理を行えるため、CLIでの操作が苦手な方には非常に便利な拡張機能です。
Docker 拡張機能に関しては DevContainers 拡張機能とは異なり、いくつかの事前準備が必要です。
まずは動作を確認します。
何も設定されていない状態でDocker 拡張機能を使おうとすると、以下のようなエラーが表示されます。
Dockerコマンドがインストールされていないため、このような表示になります。
また、Dockerコマンドがインストールされていたとしても、Docker Engine自体はWSL上で実行されているため、必要な情報を取得することができない状態のはずです。
WSL上のDocker Engineの情報を取得するには、ホスト(Windows)からリモート(WSL)にアクセスして情報を取得できるようにする必要があります。
Docker CLIのインストール
まずはWindowsにDockerコマンドをインストールします。
Dockerコマンド自体は公式サイトでビルド済みのバイナリが公開されているため、それをダウンロードしてパスを通すだけで使用できます。
公開先は下記になります。
上記のサイトより最新版のZIPファイをダウンロードし、適当なフォルダ(例えば c:/tools/docker
)に解凍することで、Dockerコマンドを使用できるようになります。
このままだとフルパスで docker.exe
を指定する必要がありますので、
環境変数 PATH
に解凍した docker.exe
を含むフォルダのパスを追加しておくことをオススメします。
続いてDocker Composeコマンドをインストールします。
Docker Composeコマンドは公式GitHubのReleasesページからダウンロードできます。
最新版のリリースページの Assets
からWindows版である docker-compose-windows-x86_64.exe
を取得し docker-compose.exe
にリネームして、docker.exe
と同じフォルダに保存しておきましょう。
Assets
の中にWindows版が見当たらない場合、 Show all ** assets
をクリックすると表示されます。
また docker-compose.exe
を %USERPROFILE%\.docker\cli-plugins
に保存しておけば docker compose
コマンドとして利用できるようになります。
なお、これらのコマンドのインストールと、コマンドパスを設定を行うための PowerShell スクリプトを用意しました。
このスクリプトでは c:\tools\docker
フォルダ内に Docker コマンドと Docker Compose コマンドの最新版をインストールし、環境変数 PATH
にコマンドパスを追加する処理を行います。(インストール済みの場合は上書き)
# インストール先フォルダ ($DEST_DIR/docker にインストールする)
# $DEST_DIR="$env:ProgramFiles" # ← ProgramFilesにインストールする場合は管理者権限が必要
$DEST_DIR="c:\tools"
$DOCKER_DIR=Join-Path $DEST_DIR "docker"
##############################
# docker.exe のインストール
##############################
# dockerの最新バージョンのファイル名を取得
$LATEST_VERSION_ZIP = (Invoke-WebRequest -UseBasicParsing -uri "https://download.docker.com/win/static/stable/x86_64/").Content.split("`r`n") |
Where-Object { $_ -like "<a href=""docker-*"">docker-*" } |
ForEach-Object { $zipName = $_.Split('"')[1]; [Version]($zipName.SubString(7,$zipName.Length-11).Split('-')[0]) } |
Sort-Object | Select-Object -Last 1 | ForEach-Object { "docker-$_.zip" }
# dockerの最新版(ZIPファイル)をダウンロードして $DEST_DIR に展開
curl.exe -L "https://download.docker.com/win/static/stable/x86_64/$LATEST_VERSION_ZIP" -o $LATEST_VERSION_ZIP
Expand-Archive $LATEST_VERSION_ZIP -DestinationPath $DEST_DIR -Force
# ダウンロードしたZIPファイルを削除
Remove-Item -Force $LATEST_VERSION_ZIP
##############################
# docker-compose.exe のインストール
##############################
# docker-composeの最新バージョンを取得
$LATEST_VERSION=(curl.exe -s "https://api.github.com/repos/docker/compose/releases/latest" | ConvertFrom-Json).tag_name
# docker-composeの最新版をダウンロードして docker コマンドと同じパスに保存
curl.exe -L "https://github.com/docker/compose/releases/download/$LATEST_VERSION/docker-compose-windows-x86_64.exe" -o "$DOCKER_DIR\docker-compose.exe"
# cli-plugins にインストール
mkdir $env:USERPROFILE\.docker\cli-plugins -Force
cp $DOCKER_DIR\docker-compose.exe $env:UserProfile\.docker\cli-plugins\
##############################
# docker.exe と docker-compose.exe のバージョン表示
##############################
pushd $DOCKER_DIR
./docker.exe --version
./docker-compose version
popd
##############################
# 環境変数 Path (ユーザ設定) に $DEST_DIR\docker フォルダを設定
##############################
if (Test-Path -Path $DOCKER_DIR) {
# 既存のPath設定を取得し $DOCKER_DIR の設定状況を確認
$user_path= [System.Environment]::GetEnvironmentVariable("Path", "User")
if (";$user_path;" -notlike "*;$DOCKER_DIR;*") {
# 未設定の場合は既存の設定に追加する (永続設定)
[Environment]::SetEnvironmentVariable("Path", "$user_path;$DOCKER_DIR", "User")
Write-Host "Docker コマンドの Path を登録しました"
}
else
{
Write-Host "Docker コマンドの Path は登録済みです"
}
}
WSL上のDocker Engineへのアクセス設定
Docker コマンドのインストールができたら、dockerコマンドのバージョン情報を確認してみましょう。
Windows上のターミナルで以下のコマンドを実行してください。
# パスが通っていない場合はフルパスで指定する (例: c:\tools\docker\docker.exe version)
docker version
Client:
Version: 26.1.0
API version: 1.45
Go version: go1.21.9
Git commit: 9714adc
Built: Mon Apr 22 17:07:39 2024
OS/Arch: windows/amd64
Context: default
error during connect: this error may indicate that the docker daemon is not running: Get "http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.45/version": open //./pipe/docker_engine: The system cannot find the file specified.
Clientの情報は取得できていますが、サーバ(Docker Engine)の情報が取得できていません。
引き続き、WSL上のDocker Engineにアクセスするための設定を行います。
まずはWSL上のDockerの設定を行い、Windowsから TCP
接続でWSL上のDocker Engineへ接続できるようにします。
ここからはWSL上での作業になります。
まずはWSL上でDocker Engineの設定を行います。
WSLに接続したうえで /etc/docker/daemon.json
を編集して、tcp
プロトコルでリッスンするように設定します。
sudo vi /etc/docker/daemon.json
/etc/docker/daemon.json
に以下の内容を追記して保存します。
{
"hosts": ["unix:///var/run/docker.sock", "tcp://127.0.0.1:2375"]
}
nvidia containerをインストール済みの場合は、すでに設定が記載されていますので、hosts
セクションを追記すると以下のようになります。
{
"hosts": ["unix:///var/run/docker.sock", "tcp://127.0.0.1:2375"],
"runtimes": {
"nvidia": {
"args": [],
"path": "nvidia-container-runtime"
}
}
}
以上で Deocker Engine の設定は完了ですが、このままですとサービス起動時にエラーになるため、サービス設定を変更します。
具体的には /lib/systemd/system/docker.service
の ExecStart
セクションを以下のように書き換えます。
sudo vi /lib/systemd/system/docker.service
# ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
ExecStart=
ExecStart=/usr/bin/dockerd
1行目は既存の設定なので、コメントアウトしています。(削除可能)
2行目と3行目が新たに追加した設定です。
設定が完了したら、Docker Engineを再起動します。
# サービス設定の再読み込み
sudo systemctl daemon-reload
# Docker Engineの再起動
sudo systemctl restart docker
Docker Engine が再起動したらサービスの状態を確認して、正常に起動しているかを確認してください。
# Docker Engineの状態確認
sudo systemctl status docker
エラーがなく正常に起動しているようでしたら、dockerコマンドからTCPで接続できるか確認します。
docker -H tcp://127.0.0.1:2375 version
Client: Docker Engine - Community
Version: 26.1.0
API version: 1.45
Go version: go1.21.9
Git commit: 9714adc
Built: Mon Apr 22 17:06:41 2024
OS/Arch: linux/amd64
Context: default
Server: Docker Engine - Community
Engine:
Version: 26.1.0
API version: 1.45 (minimum version 1.24)
Go version: go1.21.9
Git commit: c8af8eb
Built: Mon Apr 22 17:06:41 2024
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.6.31
GitCommit: e377cd56a71523140ca6ae87e30244719194a521
runc:
Version: 1.1.12
GitCommit: v1.1.12-0-g51d5e94
docker-init:
Version: 0.19.0
GitCommit: de40ad0
エラーがなければ正常に接続できています。
続いてWindowsからWSL上のDocker Engineにアクセスするたの設定を行います。
以降の操作はWindows上のターミナルで実行してください。
まずはWSL上で行ったように、WindowsからTCP接続でWSL上のDocker Engineにアクセスできるか確認します。
docker -H tcp://127.0.0.1:2375 version
Client:
Version: 26.1.0
API version: 1.45
Go version: go1.21.9
Git commit: 9714adc
Built: Mon Apr 22 17:07:39 2024
OS/Arch: windows/amd64
Context: default
Server: Docker Engine - Community
Engine:
Version: 26.1.0
API version: 1.45 (minimum version 1.24)
Go version: go1.21.9
Git commit: c8af8eb
Built: Mon Apr 22 17:06:41 2024
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.6.31
GitCommit: e377cd56a71523140ca6ae87e30244719194a521
runc:
Version: 1.1.12
GitCommit: v1.1.12-0-g51d5e94
docker-init:
Version: 0.19.0
GitCommit: de40ad0
先程はエラーで出てこなかった、Serverの情報が表示されていれば正常に接続できています。
また、 OS/Arch
の部分に注目してください。
Client側のDockerコマンドはWindowsバイナリが使われているのため windows/amd64
表記になっているのに対して、Server側はWSL上Docker Engineの情報を表示しているため、 linux/amd64
表記になっているはずです。
なお、動作確認のために docker version
コマンドを実行していますが、他のコマンドも実行できるはずです。
正常に動作していればイメージの一覧やコンテナの一覧が表示されるはずです。
# イメージ一覧
docker -H tcp://127.0.0.1:2375 images
# コンテナ一覧
docker -H tcp://127.0.0.1:2375 container ls
ここまでくれば、WindowsからWSL上のDockerを操作することができるようになりました。
ただ、いちいち -H
オプションを使って接続先のホストを指定するのは面倒なため、Docker Context を変更して接続設定を省略できるようにします。
具体的には以下のコマンドを実行します。
# WSL上のDocker Engineに接続するためのContextを作成 (Context名は任意 (例: `wsl-tcp`)
docker context create wsl-tcp --docker host=tcp://127.0.0.1:2375
# 作成したContextをデフォルトで使うように設定
docker context use wsl-tcp
上記の設定で、 -H
オプションを使わずにWSL上のDocker Engineにアクセスできるようになります。
docker images
docker container ls
docker version
上記コマンドで正しくWSL上の情報が表示されていれば、VSCodeの Docker 拡張機能からも同じことができるようになっているはずです。
先ほど作成した Docker Context もこちらで確認することができます。
制限事項
ここまでいろいろな手順を踏んで、Windows上のVSCodeからWSL上のDocker Engineを操作する環境を構築しましたが、以下のような制約があります。
Windows上からボリュームをマウントできない
Windows上から docker run
コマンドや docker-compose
コマンドを使ってコンテナを起動する場合に、ファイルまたはディレクトリをマウントに失敗して、コンテナが起動しないことがあります。
これは、WindowsのファイルシステムとWSLのファイルシステムでパスの扱いが異なるためです。
例えばWindows上の C:\workspace
フォルダをマウントする場合、WSL上では /mnt/c/workspace
というパスになります。
このため workspace
ファルダ内の target
というファイルをマウントする場合、WSL上でのパスである /mnt/c/workspace/target
を指定する必要があります。
ちゃんとマウントできるじゃないかと思うかもしれませんが、通常はマウントするパスはフルパスでは書かず、相対パスで書くと思います。
例えば docker run -v ./target:/workspace/target ...
と記載したりしますが、この時 docker コマンドは内部的に絶対パスに変換して処理をしており、なおかつそのパスはWindowsのパス C:\workspace\target
になります。
当然、Linuxで稼働しているWSL上のDocker EngineはWindowsのパスを認識できないためエラーになり、コンテナの起動に失敗します。
同じ理由で docker
コマンドや docker-compose
コマンドにおいて、 --file
または -f
オプションで Dockerfile
や docker-compose.yml
を指定する場合も同様の問題が発生します。
なお、この相対パスによる問題は、DevContainers 拡張機能を使ってコンテナを起動する場合には発生しません。
これは DevContainers 拡張機能の設定で Execute in WSL
を有効にしている場合、VSCode が内部的に WSL にアクセスしたうえでコンテナを起動しているため、すべてWSLの中で処理が行われるからです。
Docker 拡張機能から起動中の DevContainer のコンテナグループの操作ができない
上記で Dev Containers 拡張機能を使ってコンテナを起動する方法を推奨していますが、この場合も問題がないわけではありません。
DevContainer において docker-compose.yml
などを使って複数のコンテナを起動する場合があります。
このとき作成されるコンテナグループをVSCodeのDocker 拡張機能経由で docker compose down
や docker compose logs
などで操作しようとすると、エラーで処理が実行できません。
この現象は、例えば .devcontainer/docker-compose.yml
で定義されたコンテナグループを操作する場合、Docker 拡張機能は内部的に -f
オプションを用いてこのファイルを指定してコンテナグループを操作しようとしますが、DevContainers 拡張機能で実行されたコンテナはWSL上のコマンドで起動されているため、ファイル情報としてWSLのフルパスで渡されることになります。
しかし、Windows上のdocker-composeコマンドはこのパスを認識できずにエラーになります。
こちらの問題は有効な回避方法がありません。
Docker拡張機能にも Execute in WSL
オプションがあると便利なのですが、現状は存在しません。
手動で docker-compose
コマンドの -f
オプションにWindows上のパスを指定してコマンドを実行するか、WSL上でコンテナグループを操作するしかないようです。
ただしこの状態でも、Docker 拡張機能経由でコンテナ一つ一つを個別に操作することが可能ですので、そこまで致命的でもありません。
docker run
で直接コマンド実行時に結果が表示されない
Windows上で docker run
コマンドを使ってコンテナを起動しコンテナ内で直接コマンドを実行する場合、コマンドの結果が表示されないことがあります。
具体的には下記コマンドを実行しても、結果が表示されません。 (WSL上で実行する場合は正常に表示されます)
docker run --rm alpine echo "Hello, World!"
docker run --rm hello-world
本来ならば1つ目のコマンド実行時には Hello, World!
と表示されるはずですが、何も表示されません。
また、2つ目のコマンドはDokcerの動作確認に使われるDockerイメージですが、このイメージのように Dockerfile 内の CMD
文などでコマンドが実行されている場合も同様に結果が表示されません。
この問題に対しては、インタラクティブオプション -i
をつけて実行すると正常に表示されます。
docker run --rm -i alpine echo "Hello, World!"
# ‐> Hello, World!
docker run --rm -i hello-world
# ‐> Hello from Docker!
# attach オプションでも可
docker run --rm -a stdin -a stdout alpine echo "Hello, World!"
docker run --rm -a stdin -a stdout hello-world
パフォーマンスの問題
本記事で作成した環境で開発を行うと、WSL上にマウントされたWindowsファイルを操作することになるため、パフォーマンスが低下すると言われています。
本来であればWSL上にリポジトリを展開して、すべての操作をWSL上で行えば、上記のようなファイルパスの問題やパフォーマンス低下は発生しませんが、利便性の問題から本記事のようにWindows上ですべての操作を行いたい場合もあると思います。
そもそもDockerコンテナにマウントしたファイルを操作する場合にもパフォーマンス低下が見込まれるため、処理時間にシビアな操作を行う場合、最終的には実機での動作確認が必要になります。
では、開発に支障が出るほどのパフォーマンスの低下が発生するのかというと、そこまで問題になったことはないです。
実際にこの環境で大容量の画像を数万枚処理するような操作を行っていますが、開発に支障が出るほどのパフォーマンスの低下は発生していません。
まとめ
このように、ファイルパスの扱いに少し制約があるものの、WSL上のDocker EngineをWindows上から操作する環境を構築できました。
WSLを使うことでWindowsでも気楽にDocker環境が利用できますし、DevContainerと組み合わせることで開発環境の構築も非常に簡単に行えます。
どんどんDockerを使って開発を効率化していきましょう!!
Discussion