🍫

Windowsコンテナにアプリをインストール!~続・WSLもDocker Desktopも使わずにDockerをWindowsでやってみる

2024/04/15に公開

概要

Windows上でDocker環境を作るための方法を説明します。

この記事は「WSLもDocker Desktopも使わずにDockerをWindowsでやってみる」の続編です。
以前の記事ではWindowsコンテナをDockerで利用する方法までを説明していましたが、今回の記事ではその環境で実際にアプリケーションを動かしてみたいと思います。

今回、準備方法などについては説明していませんが、前回の記事を読んでいただければわかるかなと思います。
https://zenn.dev/0_0/articles/40dd471fffc8b4

前回の記事で足りなかったこと 現実性

前回の記事では「Docker HubにWindowsコンテナ用のイメージも配布されてますよ~」といいながらセットアップの手順を説明してたのですが、Docker Hubで公開されているイメージはほとんどがLinuxのもの。
つかえるイメージというのがそこまでいうほどありません。

PythonのようにGitHub上でdockerfileが公開されている場合もあるので、シェルスクリプトをWindows向けに読み替えてることでdockerfileを作るのはできなくはありませんが、少々骨が折れます。

また、exeを実行してインストールをしようにもいろいろな問題にぶつかりがちで、手間がかかるのが現実です。

そこで今回、手動でのインストールをあきらめ、なおかつ完成された環境をWindowsコンテナに展開すべく、ChocolateyをインストールすることでWindowsコンテナでのDocker実行を現実的にしてみることにしました。

イメージサンプル

今回はPythonをインストールするイメージを作ってみます

プロジェクト構造

あとで整理しやすいように少々仰々しく組んでいます

フォルダ配置
📂 . (プロジェクトのルート)
├── 📂images
│   └── 📂python
│       ├── 📂data
│       │   ├── 📂psm
│       │   │   └── chocolatey.psm1
│       │   └── image.ps1
│       └── dockerfile
└── docker-compose

imagesにイメージ毎のフォルダを作成する想定。
dataはイメージ内にコピーするファイルをまとめておくためのフォルダです。

docker-compose

1つのイメージしか作りませんのでシンプルに

docker-compose
services:
    python:
        container_name: python
        build: ./images/python
        tty: true

dockerfile

こちらはほとんど定型文になると思います

dockerfile
# Windows coreサーバー
FROM mcr.microsoft.com/windows/servercore:ltsc2022

# データをあらかじめコピーしておく
COPY data/ C:/data

# 管理者権限
USER ContainerAdministrator

# PowerShell で実行
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]

# イメージ作成処理を実行
RUN C:/data/image.ps1 C:/data

chocolatey.psm1

Chocolateyを使うのにあたって定型文になりそうな関数をモジュール化したもの

chocolatey.psm1
# Chocolateyのインストール関数
function installChocolatey {
    [alias("Install-Chocolatey")] param()
    Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
    Import-Module -Global $env:ALLUSERSPROFILE\chocolatey\helpers\chocolateyInstaller.psm1
    refreshenv | Out-Null
}
# フォルダを検索し、PATHへ追加する
function appendPath {
    [alias("Append-FindPath")] param($folderName)
    $path = (Get-ChildItem $folderName).FullName
    $old = [System.Environment]::GetEnvironmentVariable('PATH', 'Machine')
    [System.Environment]::SetEnvironmentVariable('PATH', $old + ';' + $path, 'Machine')
    refreshenv | Out-Null
    return $path
}

image.ps1

Dockerイメージを作る本処理、モジュール化してるので結構シンプル

image.ps1
param($data_root)

# choco のインストール
Import-Module $data_root\psm\chocolatey.psm1
Install-Chocolatey

# python のインストール
choco install -y python
$path_python = Append-FindPath C:\python*
python -VV

サンプルの解説

使用すべき元イメージ

こちらの記事でWindowsイメージは複数用意されていると説明しました。

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

この表は上の方が軽量なイメージで、下にいくほど既にインストールされているプラグインが多いです。

この「インストールされているプラグイン」というのがミソで、Chocolateyのインストールには.NET 4.8が必要なのですが、nanoserverにはそれがインストールされていません。
そのまま実行してしまうと、下記の通りのエラーが出力されてイメージの作成に失敗します。

そのため、今回が真ん中のservercoreを使用しています。

chocolatey.psm1について

今回、Dockerそのものについては一般的なものと変わりがないので解説が必要なのはPowerShellで記述されたもの、特にchocolatey.psm1に限られると思います。

installChocolatey

まずはChocolateyのインストール処理について。

Chocolateyのインストールする処理
# インストーラをダウンロードし、即時実行する処理
Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))

Chocolateyは公式HP上にインストーラとなるスクリプトが公開されています。
このスクリプトをダウンロードし、即時実行することでWindows上にchocoがインストールされます。

※こちらのリンクを開くとスクリプトファイルの中身(ps1)が表示されます
https://community.chocolatey.org/install.ps1

インストールはされる……のですが、これだけでは少し足りません
というのも、Chocolateyをインストールした後、本来ならばシステム環境変数であるPATHが更新されてchocoコマンドが利用できるはずでこれには再起動が必要となので、再起動ができないDockerではすぐには使用できません。

ですのでその代わりにrefreshenvを使用します。
https://docs.chocolatey.org/en-us/create/functions/update-sessionenvironment
refreshenvはChocolateyで用意されている関数で、システム環境変数が変更されているかを検出して現在のセッション(イメージの作成処理)にも変更を反映するという関数です。

しかしrefreshenvを使用するためにもrefreshenvをセッション中に読み込んでもらわないと実行できないため、手動で読み込む必要があります。
それをしてくれるのがchocolateyInstaller.psm1です。

Chocolateyのモジュールを読み込む処理
# Chocolateyのモジュールを読み込む処理
Import-Module -Global $env:ALLUSERSPROFILE\chocolatey\helpers\chocolateyInstaller.psm1

chocolateyInstaller.psm1を実行した後はChocolateyが提供している様々な関数が使用できるようになるので、この後にrefreshenvを実行することでchocoコマンドが使用できるようにします。
https://docs.chocolatey.org/en-us/create/functions/

appendPath

もう一方の関数appendPathは、システム環境変数PATHに任意のパスを追加するためのものです

システム環境変数PATHにパスを追加する
function appendPath {
    [alias("Append-FindPath")] param($folderName)
    # パスが存在するか検索、名称を取得(ワイルドカード可)
    $path = (Get-ChildItem $folderName).FullName
    # 現在のPATHを取得
    $old = [System.Environment]::GetEnvironmentVariable('PATH', 'Machine')
    # PATHの末尾に取得したパスを追加する
    [System.Environment]::SetEnvironmentVariable('PATH', $old + ';' + $path, 'Machine')
    # セッションにPATHを反映
    refreshenv | Out-Null
    return $path
}

特殊なのはその使用方法。
Append-FindPathで指定された引数$folderNameは、ワイルドカードを許可しています。
というのも、Chocolateyでインストールするソフトウェアのフォルダ名が基本的に完全には不明であるためです。

今回例にしているPythonは、Chocolateyでインストールすると例えばC:\Python312に配置されます。
この312とはPythonのバージョン番号です。
これはPythonのリポジトリがインストール先を指定しているためまだいいのですが、今回は例示していませんがインストール先を指定していないソフトウェアは「Chocolateyが用意しているToolフォルダに展開(解凍)する」という使用もあります。
たとえばNginxが該当し、その展開先は(Toolフォルダ)\nginx28.x.x(28.X.Xはバージョン番号)です。
このToolフォルダはrefreshenvで実行したchocolateyProfile.psm1によって追加されたコマンドGet-ToolsLocationで知ることができます。

何がいいたいか。
「バージョン番号が邪魔で常に同じパスを(解説上)保証できない」、「Chocolateyの設定に依存するToolフォルダを動的に取得する必要がある」のであまりベタ書きでパス名を指定したくないのです。
なので極力指定方法は裾野を広く受け付けることにしました。

当然、パスが存在しなければエラーになり、複数存在する場合もエラーになるかと思います。
しかしまぁ、Dockerイメージ上の話なので複数存在する状況の方がおかしいのではないかなと思います(都度イメージそのものが再作成されるので、常に1つのバージョンが残るのが基本です)
複数バージョンが必要な場合は……ごめんなさい、いまのところイメージをわけるしか思いつきません。

最後のrefreshenvにパイプでOut-Nullをつなげているのは、refreshenvが標準出力でRefreshing environment variables from the registry for powershell.exe. Please wait... Finishedという文字列を出力してしまってるので、それをかき消すためです。
https://qiita.com/nkojima/items/1f5c40c7b2aefe289fd0
図は$path_pythonOut-Nullがない状態で標準出力してみた結果

戻り値はパスであってほしいので余計な出力はOut-Nullで消してしまいましょう

image.ps1について

あとはchocolatey.psm1で定義したコマンドを使用してインストールするだけです

image.ps1
param($data_root)

# choco のインストール
Import-Module $data_root\psm\chocolatey.psm1
Install-Chocolatey

# python のインストール
choco install -y python
$path_python = Append-FindPath C:\python*
Write-Host $path_python

# Pythonの実行(Append-FindPathを実行していないとpythonコマンドは使用できません)
python -VV

他のリポジトリ(Nginxなど)をChocolateyでインストール進場合は、Append-FindPath C:\python*の部分に気を付ける必要があります。
リポジトリによってインストールされている先はことなるため、pythonを他の名称に書き換えればいい……ということには一切なりません

たとえば、Nginxの場合はChocolateyが定義しているツールフォルダにインストールされるため、読み込むべきフォルダは以下の通りです。

Nginxの場合
Append-FindPath (Join-Path (Get-ToolsLocation) "nginx*")

これについてはChocolateyリポジトリの各ページを参考にしてもらうほかありませんので、よく読んでから、或いは実行時に書きだされるログから推測して指定してください。

Pythonの場合

実行ログより読み解きました

Nginxの場合

リポジトリのページより読み解きました
https://community.chocolatey.org/packages/nginx

今回はWindowsコンテナでのDockerを利用方法を解説しました。
こんどこそ快適にDockerが楽しめそうです。……ホンマか???

ところで

パッケージ管理ツールをご存じの方だと「Install-PackageProvider(OneGet)を使えばいいじゃん!」と突っ込んでいただいたかもしれません。
……すまん、やり方がわからなかったんや……。

試したコード
# choco のインストール
Find-PackageProvider ChocolateyGet -Force
Install-PackageProvider ChocolateyGet -Scope CurrentUser -Force
# pythonのインストール
Find-Package -Name vscode -ProviderName ChocolateyGet

どうしてもInstall-PackageProviderで下記のエラーが発生してしまうんです。

これについては完全に私の知識不足が原因ですので、ご存じの方はこっそり教えていただくか、記事を書いていただけると嬉しいです。
見てる感じ、nanoserverで実行できる気配があり、より小さなイメージ、コンテナで完結できる見込みがありそうなんですけどねぇ……(※あくまで予想であり、実際にはできないかもしれません)

参考記事

Chocolateyについて

https://qiita.com/k1tajima/items/f7e02e3795963d34e635
https://ytyaru.hatenablog.com/entry/2016/09/18/100000

PowerShellについて

https://qiita.com/tomomoss/items/5f8c027f3bdc3b189791
https://qiita.com/nkojima/items/1f5c40c7b2aefe289fd0

Discussion