🐳

Windowsの上のDockerの上のLinuxの上でDockerを動かしてみたかった

2024/02/10に公開

概要

この記事は結論としてできなかったことを供養するためのものです。
Dockerをいじりはじめて1週間なのもありいろいろ気が付いたこともあったので、一緒に笑い飛ばしていただけると幸いです。

気づき

先日、Windows ContainerをDockerで利用できるようにしました。
https://zenn.dev/0_0/articles/40dd471fffc8b4

この時、Hello World代わりに作成したDockerfileを見ていて気になったことがありました。

Dockerfile
FROM mcr.microsoft.com/powershell:windowsservercore-ltsc2022

# powershellイメージでは「C:\Program Files\PowerShell\pwsh.exe」がpowershellの実行ファイル
SHELL ["pwsh.exe", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]

# test用のユーザーを作成
RUN net user testuser /ADD
RUN NET LOCALGROUP Administrators /add testuser

USER testuser

CMD whoami

今回はpwsh.exe(コンテナにおけるpowershell.exe)を指していますが、ここをWindowsコマンドに変更しても実行できることを私は知っていました。

cmd.exeに書き換えたDockerfile
FROM mcr.microsoft.com/powershell:windowsservercore-ltsc2022

# cmd.exeに変更してみても動きは変わらない
SHELL ["cmd", "/c"]

# test用のユーザーを作成
RUN net user testuser /ADD
RUN NET LOCALGROUP Administrators /add testuser

USER testuser

CMD whoami

このことからSHELLとは、ここではRUNで実行するコマンドのプレフィックス(お約束的に前置する)コマンドと考えていました。
そうであるならば、RUNとは「SHELLに引数を追加して実行する」コマンドでしかありません。

実行できるかは別として、PostgreSQLのpsql -c(コマンドラインから直接SQLを実行するオプション)などもSHELLに書けちゃうんだろうなぁと予想。

妄想したDockerfile(実行できるかは不明)
# PostgreSQLをSHELLとして指定(test_dbはデータベース名)
SHELL ["psql", "test_db", "-c"]

# SQLが実行できるのでは……?(test_tableはテーブル名)
RUN "\
    create table test_table ( col varchar(100) );\
    insert test_table ( col ) values ('dummy');\
"

これが本当なら1ファイルに初期化処理が収まるのでなかなか便利そうです、未検証ですが
外部ファイル化するのが一般的かなと思いますが、状況によってはレイヤーも減らせそうなので謎の魅力があります、未検証ですが

すると気になりませんか。
他に同じようにSHELLへ共通化することで便利に使えそうなコマンドがないか。
そんなに便利に使えるコマンドなんてあるわけが――

wsl.exe --exec xxxxx
docker exec FFFFFFFFFFFF xxxxx

――ありました、一番身近な奴が。
そういえば私はまだWSLをインストールしないでDockerを触っています。
特に忌避する理由もなかったのですが、どうせなら仮想環境内で完結できるならそれに越したこともなさそうです。

しかしそもそも仮想環境だとはいえ、コンテナー上に仮想環境を構築することはできるのでしょうか?

Windowsコンテナとイメージの選択

とりあえず、まずはWSLを使えることを確認しなければ話になりません。

Windowsコンテナでのベースとなるイメージにはいくつか派生がありますが、大きく分けて4種類あります。
https://learn.microsoft.com/ja-jp/virtualization/windowscontainers/manage-containers/container-base-images

イメージ名称
mcr.microsoft.com/windows/nanoserver Windowsコンテナを実行する最低限をサポートしたイメージ
mcr.microsoft.com/windows/servercore Windowsのコマンドをある程度サポートしたイメージ
mcr.microsoft.com/windows/server 完全なWindows APIをサポートしたイメージ
mcr.microsoft.com/windows windows/serverの旧版。Windows Server2019までをサポートしている

下に行けば行くほどサポートは厚くなりますが、容量が大きくなります。
上に行けば容量は軽くなりますが、nanoserverまでいくとwhoamiコマンドがインストールされていないなどプログラムをインストール、実行してもらうのに困る可能性が高くなります。

まずは容量重視でnanoserverを検討してみましょう。

nanoserverでのWSL事情

勘の良い方はお気づきかもしれませんがnanoserverにはWSLがインストールされていません
通常ならC:\Windows\System32にインストールされているはずなので、Sytem32フォルダの中身を確認してみましょう。

nanoserverのSystem32を確認するDockerfile
FROM mcr.microsoft.com/windows/nanoserver:ltsc2022
RUN dir /b C:\Windows\System32\*.exe
実行結果
AggregatorHost.exe ARP.EXE attrib.exe auditpol.exe
autochk.exe bcdedit.exe CertEnrollCtrl.exe CExecSvc.exe
chkdsk.exe cmd.exe conhost.exe convertvhd.exe
csrss.exe curl.exe DeviceCensus.exe DiskSnapshot.exe
diskusage.exe dllhost.exe dtdump.exe expand.exe
findstr.exe finger.exe fltMC.exe HOSTNAME.EXE
icacls.exe ipconfig.exe lodctr.exe lsass.exe
mountvol.exe MRINFO.EXE MuiUnattend.exe net.exe
net1.exe netsh.exe NETSTAT.EXE NIHOST.exe
NSRC.exe ntoskrnl.exe pacjsworker.exe PATHPING.EXE
PING.EXE rdrleakdiag.exe reg.exe regsvr32.exe
ROUTE.EXE runexehelper.exe sc.exe schtasks.exe
services.exe setx.exe smss.exe sort.exe
svchost.exe tar.exe taskhostw.exe TCPSVCS.EXE
TRACERT.EXE ucsvc.exe unlodctr.exe userinit.exe
WerFault.exe WerFaultSecure.exe wermgr.exe wevtutil.exe
wininit.exe wpr.exe WUDFCompanionHost.exe WUDFHost.exe
xcopy.exe

実行ファイルに限って実行すると69ファイルが見つかりました。
手元にあるWindows 11 Proで同じコマンドdir /b C:\Windows\System32\*.exeを実行すると660ファイル見つかったので、通常のPCにくらべてずいぶんと機能が限られていることが分かります。

ちなみに本来であればwhereコマンドでインストールされているかは検証できます。

しかし一覧の通りwhereコマンドはインストールされていません。
したがって今回はdirコマンドで確認せざるをえませんでした。
他にも使いそうなコマンドすらもちょくちょくないのでnanoserverを使用する際は注意したいですね。

閑話休題。

ないのであれば用意すればいいだけの話です。
面白いことに、WSLは様々なインストール方法が存在しています。

  • Microsoft StoreでWSLをダウンロードする
  • Windowsの機能の有効化または無効化から「Linux用Windowsサブシステム」を有効化にする
  • dismコマンドを実行する
  • PowerSellから有効化する
  • 直接WSLをインストールする

Microsoft Storeからインストールする

まずは1つ目。
インストールするためには当然、Microsoft Storeを起動する必要があります。
そういえばGUIがDockerで使用できるという話は聞いたことがあるので、きっと起動できるなら操作できるでしょう。

Microsoft Storeを起動するDockerfile
FROM mcr.microsoft.com/windows/nanoserver:ltsc2022

USER ContainerAdministrator
CMD start ms-windows-store:

docker runしてdocker ps -a……っと。

ステータスが「Exited」。つまり起動失敗ですね。

ここで気が付いたのですが、コマンドを実行したいだけならCMDを書く必要はなくdocker execで構いません。

ユーザーログインだけしてもらうDockerfile
FROM mcr.microsoft.com/windows/nanoserver:ltsc2022

USER ContainerAdministrator

修正したDockerfileからコンテナを作成するとうまく起動したのでdocker.exe execでコマンドを実行してみましょう。

The system cannot execute the specified program.(指定されたプログラムを実行できません)
GUIが起動できないのか、Microsoft Storeが存在しないのか、あるいはms-windows-store:がないのかはわかりませんが実行できないようです。
そういえば:がつく名称というのは確かプロトコルとして扱われたような気がします。
Windowsではプロトコルをプログラムと関連付けすることができるので、実体がどこにインストールされているのかネット検索したところC:\Windows\System32\WSReset.exeがそうなのだとわかりました。

……先ほどのリストにはありませんね。
WSResetをインストールするということも考えてみましたが、通常はOSのインストールや修復をする
方法しか見つからなかったので、この方法はできないということがわかりました。

それと別にMicrosoft Storeにはwingetというコマンドも存在します。
こちらはオプションで特定のアプリをダウンロードすることができるものですが、画像の通りユーザーフォルダのAppDataにインストールされているものです。

ですが、今回ログインしているContainerAdministrator(Windowsコンテナの標準管理者アカウント)のフォルダにはマイドキュメントしか存在しません。
つまるところMicrosoft Storeはインストールされていないのでしょう。

これは他のユーザーでも同様でした。

失敗!

「Windowsの機能の有効化または無効化」を変更する

「Windowsの機能の有効化または無効化」は基本的にコントロールパネルから起動するものですが、その実態はOptionalFeatures.exeという実行ファイルです。

OptionalFeaturesにオプションを入れて設定する方法はわかりませんでしたが、GUIから操作ができるなら設定を変更するだけでいけそうです。

じゃあOptionalFeatures.exeのパスはどこなのかというと……

やはり先ほどのリストにはありません。

次!

dismコマンドを実行する

dismコマンドとは「Windowsイメージを変更するためのツール」……らしいです。
https://learn.microsoft.com/ja-jp/windows-hardware/manufacture/desktop/enable-or-disable-windows-features-using-dism?view=windows-11

説明はよくわかりませんが、要はシステムの有効、無効を切り替える機能を持つツールで、コマンドプロンプトで以下を実行すればWSLを有効化できるんだそうです。

dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart

で、そのdismがどこにあるのかというと……

はい、解散!

PowerSellから有効化する

いえ、まだ解散するのは早いです。
ここまではコマンドプロンプトから起動するという発想が間違っていたのです。
最初に話していたとおり、WindowsのCUIはPowerShellもあります。

PowerShellはもう一覧を見ていただくとわかる通りインストールされていないのですが、すでにPowerShellがインストールされているnanoserverというのが存在しています。

https://learn.microsoft.com/ja-jp/powershell/scripting/install/powershell-in-docker?view=powershell-7.4
https://hub.docker.com/_/microsoft-powershell

ちなみにPowerShellはLinuxでもインストール可能で、上記のDocker Hubではインストール済みのLinux系OSのイメージをダウンロードすることもできます。
「LinuxコンテナからDockerを利用しているがWindowsに慣れすぎていてBashは無理!」という方は一度試してみるのもいいのではないでしょうか。

さて、ではさっそくWSLを有効にしてみましょう。

機能を有効化する
FROM mcr.microsoft.com/powershell:lts-nanoserver-ltsc2022

SHELL ["pwsh.exe", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]

USER ContainerAdministrator
RUN Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux

……はい、わかってました。
「Windowsの機能の有効化または無効化」の実行ファイルがなかった時点で実行できないのは明らかだったので、このコマンドが失敗してしまうのは予測できていました。

でも、あきらめていません。
PowerShellにはInstall-Moduleというコマンドがあります。
このコマンドはPowerShell Galleryからモジュール(関数をパッケージしたようなもの)をダウンロードするものです。

そしてみつけました、WSLのパッケージ。
https://www.powershellgallery.com/packages/Wsl/2.1.0

さっそくDockerfileに含めて実行してみましょう。

モジュールのインストール
FROM mcr.microsoft.com/powershell:lts-nanoserver-ltsc2022

SHELL ["pwsh.exe", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]

USER ContainerAdministrator
RUN Install-Module -Name Wsl -Verbose -Force -SkipPublisherCheck

なんだかさきほどまでと実行結果が違います。
なんか行けそうな気がします。
行けそうな気がするのですが……どうすればいいんでしょう?

PowerShellのモジュールは、インストールするとモジュールに内包されている命令が使えるようになるはずです。

https://github.com/SvenGroot/WslManagementPS

Wsl.psd1
FunctionsToExport = @("Get-WslDistribution",
    "Set-WslDistribution",
    "Stop-WslDistribution",
    "Remove-WslDistribution",
    "Import-WslDistribution",
    "Export-WslDistribution",
    "Invoke-WslCommand",
    "Enter-WslDistribution",
    "Stop-Wsl",
    "Get-WslVersion",
    "Get-WslDistributionOnline")

これは後で見つけたPowerShell Galleryと対応しているGitHubのプロジェクトのページ。
その中にあったWsl.psd1の一部抜粋なのですが、モジュールをインストールしたのにいずれも使用できませんでした。

それもそのはず。
このモジュール、readme.mdを読めば分かったことなのですがwsl.exeをラッパーするものでした。
つまりwsl.exeが存在していることが前提。
インストールされていないnanoserverにおいては全く無意味です。

ちくしょーめ。

直接WSLをインストールする

さて、最終手段。WSLを直接インストールしてみましょう。

実はWSL、GitHub上にプロジェクトが存在していてインストーラも公開されています。
https://github.com/microsoft/WSL

ここからmsiを拝借してDockerfileと同じフォルダに置いてみました。

直接インストールするためのDockerfile
FROM mcr.microsoft.com/windows/nanoserver:ltsc2022

USER ContainerAdministrator

RUN mkdir "C:\temp"
COPY "wsl.2.0.14.0.x64.msi" "C:\temp"

WORKDIR "C:\temp"
RUN start wsl.2.0.14.0.x64.msi

こんな感じでしょうか。
さっそく実行してみましょう。

……エラーですね。

すっかり忘れていましたが、コマンドプロンプトでmsi拡張子のインストーラを実行するためにはmsiexecというコマンドから呼び出す必要があります。
https://qiita.com/lanevok/items/270c4ee73dba1d77e1f9

ですのでオプションまで含めると以下の通りに修正するとインストーラは実行できるようになるでしょう。

Dockerfileの変更すべき点
- start wsl.2.0.14.0.x64.msi
+ msiexec.exe /i wsl.2.0.14.0.x64.msi /L*V wsllogs.txt

本来なら。

ここまで読んでいただいた方はオチがもうお分かりかと思いますが、この処理は失敗します。

それはなぜか。

msiexecは本来、C:\Windows\System32にインストールされているはずで、nanoserverにはmsiexecがインストールされていないからです。

完全に詰みまみた。
失礼、詰みました。

nanoserverに関するオチ、あとから気が付いたこと

この記事を執筆するのに整理と検索のし直しをしていたら、以下の記事を見つけました。
https://atmarkit.itmedia.co.jp/ait/articles/1611/25/news051.html

  • GUIに関する機能やAPIはサポートされない
  • Windows OSでアプリやドライバーのインストールによく使われていたMSI形式のインストーラは利用できない

ここまでにやろうとしていたことがすべて集約されていました。
「Windowsの機能を駆使」してnanoserverを拡張しようとするのは誤りだったということですね。

残念。

ちなみにIISの構築や、
https://learn.microsoft.com/ja-jp/iis/get-started/whats-new-in-iis-10/introducing-iis-on-nano-server
VMの起動はできるそうです。
https://learn.microsoft.com/ja-jp/system-center/vmm/hyper-v-nano?view=sc-vmm-2016

だったらコンテナの起動もできそうなものですけど……

フルサイズのWindowsコンテナを使う

さてここまでnanoserverについて言及してきましたが、ここでそもそもの前提を変えてみます。
イメージの大きさを気にしていろいろ犠牲にしてしまったのがいけないのです。

WSLをインストールする

素直にwslが実行可能な環境でwsl --installをしてみましょう。

WindowsServer 2022のDockerfile
FROM mcr.microsoft.com/windows/server:ltsc2022
USER ContainerAdministrator

Dockerfileは管理者権限を得るためのユーザー選択のみです。

イメージを作成したのち、WSLが存在しているのか確認してみます。

nanoserverと異なり、whereコマンドもwslコマンドもコンテナにインストールされているので簡単に確かめることができました。
次にWSLのバージョンを確認してみます。

wsl --versionを実行したのにも関わらず、コマンドのヘルプ情報が表示されました。
WSL2が使えるバージョンであれば次のようにバージョン情報が表示されるはずなのに、です。

これは古いWSL(WSL1)の挙動で、--versionオプションがないので未定義時の処理が行われた(=ヘルプが表示された)というわけです。

したがってDockerを使用するためには、WSL2を使えるバージョンに上げる必要があります。
docker execwsl --installを実行してみましょう。

おや、おやおやおや……?
Errorが表示されるまでの間にラグはあったのですが失敗してしまいました。
きっといくつかの処理、たとえばWSLのダウンロードには成功したのでしょう。

エラーコード0x800F081Fは調べてみると.NET Framework 3.5に関するエラーのようです。
https://www.saka-en.com/windows/windows10-dot-net-framework-0x800f081f/

.NET Frameworkを含むイメージで試してみる

ならば.NET Frameworkが使用できるイメージからコンテナを作りましょう。
https://github.com/Microsoft/dotnet-framework-docker?tab=readme-ov-file
https://hub.docker.com/_/microsoft-dotnet-runtime/

しかしこのコンテナ。
作成元がWindowsCoreになっています。つまりフルサイズのWindowsコンテナに比べてサポートされているソフトウェアが少ない。なんだか嫌な予感がします。
ま、まあコンテナを作って実行してみましょうか。

.NET Frameworkの使えるDockerfile
FROM mcr.microsoft.com/dotnet/framework/runtime:4.8.1
USER ContainerAdministrator

はい、読めてた。読めてました。
この「Class not registered」、ここでは省略しますが純粋なWindowsCoreでも発生しました。
つまるところ実行するには何かしら足りないんですね。
これはもうツンダツンダです。お手上げです。

結論

「Windowsの上のDockerの上のLinuxの上でDocker」を動かしてみたかったのですが、Dockerにたどり着くどころかWSLを実装することができませんでした。

ここまでの説明の中に含めていませんが、nanoserverで試してみたことをフルサイズのWindowsコンテナでも試してみたりすると今度は再起動を要求するエラーでRUNCMDが失敗したりしています。
もう思いつく限りは試してみたので「現状は不可能である」というのを結論とします。
できた方はぜひアンサー記事を書いていただけると嬉しいです。

それはそれとしてここで思い出したのですが、Hyper-Vの仮想環境にHyper-Vの仮想環境を入れ子で起動するためにはSet-VMProcessorコマンドを一番外側の環境で実行する必要があります。
しかしこのコマンド、オプションに仮想環境の名称を指定する必要があり、Hyper-Vアーキテクチャを利用して稼働するWSLには名称がないので指定することができません。
Hyper-Vで起動中のWindowsでWSLを実行することは可能なので関係のない推測の可能性もありますが、仮にWSLを実行することができたとしても次の段階、入れ子のDockerを導入する段階でエラーになっていた可能性もあります。

面白そうなことが実行できないのは少々悔しいですが、Microsoftがいろいろな派生イメージを用意してくれていること、Dockerの上でのコマンドの挙動など得られた知識は多かったので無駄な一日にはならなかったと思います。

さて、次は何をしてみましょうか。

Discussion