しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

Windows / Visual Studio 使いが WSL 2 / Visual Studio Code で環境構築した時の手順

基本的には Windows と Visual Studio を使って Azure Functions や GitHub で公開しているアプリケーションとライブラリを書いていますが、最近は Python や Go を書く必要がちょいちょい出てきたので、色々と観念して WSL 2 の環境を構築して使っています。

特に Python は Azure Functions だと Linux のみ対応となるので、Windows 上での開発は難しくなっています。他にも個人的に PR を投げている Terraform Provider for Azure も Windows 上では一部のテストが通らなくなっているので、WSL 2 を使わないと難しい状況です。

環境構築系はメモっておかないと後ではまるので、自分が必要な範囲で手順を残します。

Azure 開発に必要なパッケージと C# / Node.js は WSL 2 に直接インストールしますが、Python や Go などは基本的に VS Code の Dev Container 経由で使うという割り切った構成にしています。

ちなみにメインマシンは Windows と Visual Studio 専用にしているので、この間購入した Tiger Lake NUC 上に WSL 2 と Visual Studio Code の環境を構築して、必要な時にリモートデスクトップで使っています。

自宅の NUC は有線で繋いで常時稼働させているので、こういった用途には適しています。

基本的な WSL 2 環境構築

Visual Studio Code と Remote 拡張をインストール

ほとんどの人が Visual Studio Code をインストールしていると思うので、このセクションはほぼ不要な感じがありますが、同時に Remote 拡張は必要なので一応書いておきます。

Remote Development Extension Pack をインストールすれば、SSH / WSL 2 / Container 向けのリモート開発が可能になります。これが無いと始まりません。

WSL 2 のインストールと設定

最近は Docs にしっかりとしたドキュメントが揃っているので、WSL 2 のインストールは書いてある手順通りに再起動しつつ行えば問題ないです。再起動と書いてあるタイミングで行っておかないとはまります。Insider Preview だと簡単らしいですが、今のところは個別に設定が必要です。

WSL 2 のデフォルト設定のままだと若干使いにくい部分があるので、設定を Windows と WSL 2 のそれぞれで追加して改善します。設定のリファレンスは Docs にあります。

Insider Preview では改善されているらしいですが、デフォルトだと WSL 2 の VM がメモリを食いつくしてしまったので、%USERPROFILE%\.wslconfig を作成して WSL 2 へのメモリ割り当て上限を追加しました。

[wsl2] 
memory=16GB

NUC は 32GB のメモリを積んでいるので半分を割り当てるようにしました。

もう一つは WSL 2 側に Windows の PATH が自動的に追加されてしまう件ですが、これは WSL 2 側で /etc/wsl.conf を作成して、追加しないように変更しました。

[interop]
appendWindowsPath=false

結構この挙動で混乱することが多かったので、WSL 2 と Windows は切り離しておきます。

このままだと VS Code の Remote 開発に影響が出るので code に対して alias を追加して対応しました。

alias code="/mnt/c/Users/shibayan/AppData/Local/Programs/Microsoft\ VS\ Code/bin/code"

これで code . とターミナルで打てば、これまで通り Windows 側で WSL 2 のディレクトリを開けます。

Docker Desktop をインストール

VS Code で Dev Container を使うために必要なので Docker Desktop をインストールします。インストール時に WSL 2 が有効化されていると、良い感じに WSL 2 Backend も有効化してくれました。

設定画面で WSL 2 Based Engine が有効化されていることを確認しておけば OK です。

f:id:shiba-yan:20210725161237p:plain

インストール後に WSL 2 側から docker コマンドが叩けるようになっていることを確認しておきます。エラーになった場合は WSL 2 と Docker Engine の再起動で大体解決しました。

快適に利用するための設定

Windows Terminal の開始ディレクトリを変更

WSL 2 がインストールされていると Windows Terminal は自動的にプロファイルを追加してくれるので便利ですが、デフォルトでは開始ディレクトリが Windows 側の %USERPROFILE% になっているので不便です。

f:id:shiba-yan:20210726163639p:plain

Windows 側のファイルシステムを WSL 2 から参照するとパフォーマンスがかなり悪いので、基本は WSL 2 側のファイルシステムに寄せておいた方が便利です。

最初は /home/shibayan とでも書いておけばいいかと思ったのですが、Windows 上でのパスを指定する必要があるらしいので、以下のように指定しました。

\\wsl$\Ubuntu-20.04\home\shibayan

これで Windows Terminal で WSL 2 を起動すれば、常にホームから開始できます。

f:id:shiba-yan:20210725154909p:plain

パフォーマンスが良いので Node.js を使った開発が捗ります。ソースコードは GitHub に全て置いてあるので、Windows と共有はせずに WSL 2 側で clone するようにしています。

Git の認証情報を Windows 側と共有

実は意外に知られていないっぽくて少し驚いたのですが、Windows 側で Git Credential Manager を使って保存された認証情報は、設定を追加すると簡単に WSL 2 側からも利用できます。

Git Credential Manager Core が必要なので、最新の Git for Windows を入れておきます。

共有するための設定は公式ドキュメントにあるように、Windows 側の Git Credential Manager Core を WSL 2 側の Git でも使うようにするだけです。

Git のインストールディレクトリを弄っていない限りコピペで実行するだけで終わります。

git config --global credential.helper "/mnt/c/Program\ Files/Git/mingw64/libexec/git-core/git-credential-manager-core.exe"

これで WSL 2 から GitHub の MFA をクリアしつつ、SSH キーの管理無しで扱えます。かなり便利です。

開発に必要なパッケージをインストール

.NET Core / .NET SDK

C# を使った開発をメインで行っているので .NET Core / .NET SDK のインストールが重要ですが、apt には .NET 6 Preview が公開されていないのでインストールスクリプトを使うようにしています。

公式サイトから dotnet-install.sh をダウンロードして適当に実行権限を付ければ、以下のようにパラメータを渡してサクッとインストールできます。

# 最新の LTS バージョン (.NET Core 3.1)
./dotnet-install.sh -c LTS
# 最新の Current バージョン (.NET 5.0)
./dotnet-install.sh -c Current
# 最新の .NET 6.0 Preview バージョン
./dotnet-install.sh -c 6.0
# 明示的にバージョン指定可能
./dotnet-install.sh -v 6.0.100-preview.6.21355.2

$HOME/.dotnet にインストールされるので、このディレクトリを PATH に追加すれば完了です。

export PATH="$HOME/.dotnet:$PATH"

適当に dotnet --info を実行すると、インストールされている SDK が確認できます。

f:id:shiba-yan:20210725154952p:plain

Azure Functions v4 のような .NET 6 Preview が必要なアプリケーションの検証もこれで捗ります。

Node.js (nvm)

C# の次に Node.js を使うことが多いのと、Windows 上だと npm install のパフォーマンスが悪いので WSL 2 上で利用します。これも公式ドキュメントに nvm を使ったインストール方法が載っているので簡単です。

基本的に LTS を使うようにしているので、nvm で LTS バージョンである v14.x を入れています。

f:id:shiba-yan:20210726113712p:plain

.NET SDK の時のように nvm install --lts コマンド一発で入るので簡単でした。nvm インストール時に PATH は自動で通してくれたので、.NET SDK の時より手間は省けました。

Azure CLI

Azure 開発をしていると必ず必要になる Azure CLI は WSL 2 側にも入れておきます。自動インストール用のワンライナーが公開されているのでかなり簡単です。

普通はインストール後に Azure へのログインが必要ですが、WSL 2 の場合は Windows 側で Azure CLI へのログインが完了していれば、認証情報の共有が可能です。

今回は知らない間に .aws.azure へのシンボリックリンクが作成されていました。

f:id:shiba-yan:20210725155102p:plain

.azure へのシンボリックリンクが存在しない場合は、改めて手動で作れば問題ないです。

f:id:shiba-yan:20210725155112p:plain

これで WSL 2 側の Azure CLI から az login を実行することなく操作可能になりました。

Azure Functions Core Tools

Azure Functions の開発には Core Tools が必要になるので .NET SDK とは別にインストールします。npm でもインストール出来ますが、nvm との相性が悪いらしいのでドキュメントの通り apt を使って入れます。

現在、実質的にサポートされているのは v3 のみなので、間違えて古い v2 を入れないように注意します。

f:id:shiba-yan:20210725155045p:plain

Azure CLI が使えるようになっていると、Core Tools から Functions のデプロイが出来るので開発中には便利に使えます。そろそろ v4 のプレビュー版も公開されそうな気配があります。*1

Terraform CLI

Terraform Provider for Azure へちょいちょい PR を投げているのと、しっかりリソースを作成したい場合には Terraform を使うようにしているので CLI を apt を使ってインストールします。

Windows の場合は zip をダウンロードして、手動でどこか PATH の通った場所に置く必要があるので結構悩むのですが、WSL 2 だとサクッと準備できます。

f:id:shiba-yan:20210725155129p:plain

自前でビルドした Terraform Provider を使うのも WSL 2 の方が楽です。シングルバイナリで動作するので Dev Container までは流石に不要という感じです。

Speedtest CLI

WSL 2 で今のところ GUI を使いたい要求が無いので、ブラウザ不要でネットワーク速度を測定できる Speedtest CLI をインストールしています。これも公式サイトを見れば簡単にインストール出来ます。

ネットワーク速度を測定したい理由としては、WSL 2 では一部の環境でネットワーク速度が異常に遅くなることがあると知ったので、それの確認目的という感じです。

自分の環境では特にネットワーク周りの問題は発生していないので、apt を使ったインストールや Docker Image の pull / push 含めて快適に使えています。

f:id:shiba-yan:20210726164248p:plain

目的がアレなので 1,2 回使えば不要になりそうですが、コマンドでサクッと測定出来るのは地味に便利なので入れたままにしています。

*1:v4 は npm ではすでにプレビュー版が公開済み

.NET 6.0 Preview で WPF が ARM64 に対応したのでビルドして Surface Pro X で試した

.NET 5.0 で Win Forms は ARM64 の Windows に対応しましたが、WPF は 2021 年に先送りになっていて非常に残念でしたが、最近リリースされた .NET 6.0 Preview の Nightly Build でついに対応しました。

GitHub の Issue ではマイルストーンが 5.0.0 になっているので、バックポートされるのかも知れません。

既に Twitter では .NET 6.0 Preview で WPF アプリケーションを ARM64 向けにビルドして動かした例が上がっていたので、自分も WPF アプリケーションを公開しているのでビルドを試しておきました。

アプリケーションは Windows Store に公開しているので MSIX まで作成できることを確認しておきました。

まずはシンプルな WPF アプリケーションを使って確認することにします。適当に .NET 6.0 Preview SDK の Nightly Build をインストールして、最近の Visual Studio Preview で WPF アプリケーションを作成すると .NET 6.0 が選べるので、ここから始めます。

f:id:shiba-yan:20210131182422p:plain

とりあえず ARM64 向けにビルド出来るかの確認をしておきたいので、以下のようなコマンドで RID として win-arm64 を指定してビルドします。ReadyToRun も確認してみましたが問題ありませんでした。

この時パッケージの復元でエラーになることがありますが、その場合は .NET 6.0 の Nightly 向け NuGet パッケージソースを追加すれば解決します。

# ARM64 Windows 向けに発行
dotnet publish -c Release -r win-arm64 -o ./publish

# ARM64 Windows 向けに ReadyToRun を有効にして発行
dotnet publish -c Release -r win-arm64 -o ./publish -p:PublishReadyToRun=true

ちなみに .NET 5.0 では WPF に非対応なのでビルド時にエラーになります。

ビルドした dll を dumpbin で確認すると、ちゃんとヘッダーのアーキテクチャが AA64 になっているので ARM64 向けにビルドされていることが分かります。

f:id:shiba-yan:20210131182431p:plain

RID を指定してビルドすると Self-Contained アプリになるので、出力されたファイル一式を Surface Pro X などの ARM64 Windows 上にコピーして持っていくと、それだけで動くようになります。

Self-Contained でファイル数が多いからか、若干時間がかかった気がしましたが無事に起動しました。Surface Pro X 上で 64 bit プロセスとして認識されているので ARM64 です。

f:id:shiba-yan:20210131183459p:plain

.NET CLI を使ったビルドは問題ないことが分かったので、MSIX を各プラットフォーム分作成できれば Windows Store で公開しているアプリケーションの移行がスムーズに出来るはずです。

正直なところ MSIX の作成は結構はまりましたが、最終的には WPF 側の csproj を以下のように定義することで、いい感じに各プラットフォーム向けに ReadyToRun 付きで MSIX をビルドできるようになりました。

何故かこの辺りは情報が非常に少なくて正解なのか分からないですが、各プラットフォーム向けのビルドと MSIX の作成は問題なく動いています。.NET Core 系では RID を指定すれば何とかなる感じはあります。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net6.0-windows</TargetFramework>
    <UseWPF>true</UseWPF>
    <Platforms>x86;x64;arm64</Platforms>
    <RuntimeIdentifier>win-$(Platform)</RuntimeIdentifier>
    <PublishReadyToRun>true</PublishReadyToRun>
  </PropertyGroup>

</Project>

WPF アプリケーションを作成すると Any CPU 向けで基本は作成されますが、Windows アプリケーションパッケージプロジェクトを追加すると x86 や x64 向けのプラットフォームが自動で作成されます。

この設定と WPF アプリケーションがビルドされる時の RID をキッチリ合わせておかないと、大体の場合は x86 以外のビルド時にエラーとなります。具体的には csproj に Platforms を追加して、構成マネージャーからソリューションプラットフォームへのマッピングを行います。

f:id:shiba-yan:20210131191016p:plain

この時に必要ない Any CPU や ARM 向けのソリューションプラットフォームは削除しておきました。後はソリューションプラットフォームの ARM64 が大文字になっていますが、RID は case-sensitive なので全て小文字に変更しておきます。*1

ビルドした msixbundle ファイルを Surface Pro X にコピーしてインストールして試しましたが、問題なく ARM64 で実行されました。特に ARM64 で動くことによって見た目に違いがあるわけではなく、面白みがないのでインストーラー画面だけ拾ってきました。

f:id:shiba-yan:20210131191656p:plain

msixbundle ファイルには x86 / x64 / ARM64 それぞれの実行ファイルが含まれているので、自動的に適切なプラットフォーム向けのものがインストールされます。

サンプルプロジェクトで ARM64 向けビルドと MSIX の作成まで問題なく行えたので、メインターゲットである WinQuickLook でも ARM64 向けビルドを試しましたが、ALINK でエラーが出て成功しませんでした。一応 ARM64 に対応するための PR だけは作成してあります。

エラーの原因を調べたところ、ALINK が ARM64 に対応していないのが根本原因でした。ALINK がそもそも使われる理由は WPF アプリケーションでサテライトアセンブリのためなので、多言語対応のリソースファイルを削除して 1 つにすると通るようになりました。

ローカライズに WPF 側の仕組みを使うのではなく、MRT と PRI に移行することで解消はしそうですが、このあたりの情報もさらに少ないので非常に悩むところです。

WinUI 3.0 や Project Reunion が正式リリースされたタイミングで改善される気はしますが、この辺りはまだまだ時間がかかりそうなのでしばらくはこれまでのリソース管理を使い続ける予定です。

*1:例えば RID として win-ARM64 を指定するとビルドエラーになる

PDFium を Windows on ARM (ARM64) 向けにビルドする

作っているアプリケーションに ARM64 対応を将来的に入れるにあたって、依存しているライブラリで PDFium だけが x86 / x64 だけの対応だったので、ARM64 向けビルドを試しておきました。

既に Chromium は Microsoft からのコントリビューションが行われているので、ARM64 向けビルドは多少はまりましたが比較的すんなりと行えました。

手順をメモしておかないと絶対に忘れるので残しておきます。

事前準備

ビルドに必要なものは Chromium と共通なので、以下のドキュメントを読みつつ準備します。

手順に従い ARM64 向けのツールをインストールする必要がありますが、実際のコンパイルには Clang が使われているようだったので、ここでインストールしたコンパイラは使われていない気がします。

中でも "Debugging Tools For Windows" は Visual Studio から Windows SDK をインストールしただけでは入らないので、アプリケーションの変更からコンポーネントを選択する必要があります。

depot_tools を展開しつつパスを通して、gclient で PDFium のソースと依存関係をダウンロードすれば大体完了です。set DEPOT_TOOLS_WIN_TOOLCHAIN=0 は忘れがちなので注意です。

PDFium をビルドする

GN と Ninja を使ってビルドしていきますが、必要な args.gn は以下のように用意しました。target_cpu = arm64 以外は x86 / x64 と共通です。

pdf_is_standalone = true
pdf_enable_v8 = false
pdf_enable_xfa = false
pdf_use_win32_gdi = false

is_component_build = false
is_debug = false

target_cpu = "arm64"

今回は必要なかったので v8 と XFA はオフにしてビルドします。GDI も使わないのでオフにしました。

普通にこのままビルドすると DLL が生成されないので、多少パッチを当てて DLL を生成しつつ呼び出し規約を一応 stdcall にしておきました。*1

diff --git a/BUILD.gn b/BUILD.gn
index 8bfe0ca55..1abb71741 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -42,6 +42,7 @@ config("pdfium_common_config") {
   if (is_win) {
     # Assume UTF-8 by default to avoid code page dependencies.
     cflags += [ "/utf-8" ]
+    defines += [ "FPDFSDK_EXPORTS" ]
   }
 }
 
@@ -139,7 +140,7 @@ group("pdfium_public_headers") {
   ]
 }
 
-component("pdfium") {
+shared_library("pdfium") {
   libs = []
   configs += [ ":pdfium_core_config" ]
   public_configs = [ ":pdfium_public_config" ]
diff --git a/public/fpdfview.h b/public/fpdfview.h
index debe083be..a228cdc50 100644
--- a/public/fpdfview.h
+++ b/public/fpdfview.h
@@ -175,7 +175,7 @@ typedef int FPDF_ANNOT_APPEARANCEMODE;
 // Dictionary value types.
 typedef int FPDF_OBJECT_TYPE;
 
-#if defined(COMPONENT_BUILD)
+#if defined(COMPONENT_BUILD) || defined(FPDFSDK_EXPORTS)
 // FPDF_EXPORT should be consistent with |export| in the pdfium_fuzzer
 // template in testing/fuzzers/BUILD.gn.
 #if defined(WIN32)
@@ -193,7 +193,7 @@ typedef int FPDF_OBJECT_TYPE;
 #endif  // defined(WIN32)
 #else
 #define FPDF_EXPORT
-#endif  // defined(COMPONENT_BUILD)
+#endif  // defined(COMPONENT_BUILD) || defined(FPDFSDK_EXPORTS)
 
 #if defined(WIN32) && defined(FPDFSDK_EXPORTS)
 #define FPDF_CALLCONV __stdcall

これで DLL が生成されるようになります。ビルド自体は gn gen を実行して Ninja の定義を作成した後に ninja -C directory pdfium を実行すると行われます。

今回は out\arm64 以下に args.gn を置いてあるので、以下のようなコマンドを実行します。

gn gen out\arm64
ninja -C out\arm64 pdfium

これで x86 と x64 の場合は問題なく DLL が生成されますが、ARM64 の場合は以下のようなエラーが出るケースがあるようです。CRT のディレクトリ構成が微妙に異なっているのが原因のようです。

f:id:shiba-yan:20200630181202p:plain

ソート関数が int と str で比較できないのが原因なので、今回は適当に数値に変換できない場合は 0 を返して選ばれないようにしました。

f:id:shiba-yan:20200630181210p:plain

修正後は ARM64 向けでも問題なく Ninja でビルドが行えるようになりました。

f:id:shiba-yan:20200630181651p:plain

ビルド後の DLL のヘッダーを調べると AA64 になっているので、ARM64 向けの DLL であることが分かります。エクスポート関数も一応調べましたが、問題なく定義されていました。

f:id:shiba-yan:20200630181705p:plain

後は実際に ARM64 マシン上で動作するか確認するだけです。もちろん Surface Pro X を使います。

Surface Pro X で実際に試す

PDFium ではテストコードもビルド出来るのでそれを使っても良いのですが、それだとつまらないので .NET 5 Preview 6 で追加された Win Forms の ARM64 版で試しました。

サンプル自体は以下のようなコードを書きました。GDI サポートを使えば HDC に対して直接レンダリング出来るようですが、GDI も今更感あるので Bitmap に対してレンダリングします。

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        var dialog = new OpenFileDialog();

        if (dialog.ShowDialog() != DialogResult.OK)
        {
            return;
        }

        var filePath = dialog.FileName;

        NativeMethods.FPDF_InitLibrary();

        var document = NativeMethods.FPDF_LoadDocument(filePath, null);
        var page = NativeMethods.FPDF_LoadPage(document, 0);

        var width = (int)NativeMethods.FPDF_GetPageWidth(page);
        var height = (int)NativeMethods.FPDF_GetPageHeight(page);

        var bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
        var bitmapData = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

        var pdfBitmap = NativeMethods.FPDFBitmap_CreateEx(width, height, 2, bitmapData.Scan0, bitmapData.Stride);

        NativeMethods.FPDFBitmap_FillRect(pdfBitmap, 0, 0, width, height, 0xffffffff);
        NativeMethods.FPDF_RenderPageBitmap(pdfBitmap, page, 0, 0, width, height, 0, 0);

        NativeMethods.FPDFBitmap_Destroy(pdfBitmap);

        bitmap.UnlockBits(bitmapData);

        NativeMethods.FPDF_ClosePage(page);
        NativeMethods.FPDF_CloseDocument(document);

        NativeMethods.FPDF_DestroyLibrary();

        pictureBox1.Image = bitmap;
    }
}

ARM64 向けビルドは以下のコマンドを使って Self-contained 形式でビルドしました。

これまでもコンソールアプリの場合は RID に win-arm64 を指定できましたが、Preview 6 では Win Forms に対応したのでビルドが通ります。

dotnet publish -c Release -o ./publish -r win-arm64

WPF の ARM64 サポートもひっそり入っているのを期待しましたが、ビルドエラーになりました。

ビルドしたファイルを Surface Pro X にコピーして実行すると、ちゃんと 64bit で動作しているのが確認できます。PDF 自体もちゃんとレンダリングされて表示できています。

f:id:shiba-yan:20200630190243p:plain

既に Chromium Edge が ARM64 に対応しているからか、ビルドから実行まで問題なく行えました。

割と簡単に PDF のレンダリングまで行えましたが、これを実際に PDF Viewer まで仕上げるのはちょっと面倒な感じです。時間を見つけつつ WPF で書いていこうかなという気持ちです。

*1:x64 / ARM64 だと特に意味はないと分かってはいる

Electron で Surface Pro X にネイティブ対応したアプリを作る

Surface Pro X というか ARM64 にネイティブ対応したアプリケーションが中々増えないですが、.NET Framework / .NET Core 以外ならいい感じに ARM64 対応が進んでいます。

特に Electron 7 から ARM64 対応したのは結構インパクトが大きいと思っています。

Electron の公式ドキュメントに Windows on ARM 向けのチュートリアルが用意されていますが、微妙に古い情報だったので最新の Electron 8 と Visual Studio 2019 で試してみました。

と言っても自分は Win32 とか Windows on ARM に関する知識はあっても Electron 周りの知識はほぼないので、サンプルプロジェクトを全編通して利用しています。

ARM64 ネイティブで動くと、常駐するタイプのメッセージングアプリで有利になってくるはずなので、是非とも Slack や Teams には対応してほしいところです。てか Teams は出すべき。

とりあえず Electron サンプルを動かす

前述したように Electron の経験がゼロなので、まずは普通の Windows 10 上で Electron のサンプルをビルドして動かしてみました。今回は公式の Quick Start を利用しました。

Node.js がインストールされていれば、以下のコマンドを叩くだけでアプリケーションが起動します。

git clone https://github.com/electron/electron-quick-start
cd electron-quick-start
npm install
npm start

実際に起動した例は以下になります。見慣れたインターフェースを持つアプリケーションが起動しました。

f:id:shiba-yan:20200416015437p:plain

Node.js の Web アプリケーションを動かすのとほとんど変わらない手順で Electron のアプリケーションが起動できました。このアプリケーションを Surface Pro X 上でネイティブ動作するように持っていきます。

実行可能ファイルとしてビルド

Electron を使ったアプリケーションを実行可能ファイルにするには electron-packager というツールが公式に提供されているようですが、最低限の機能しか持っていないようなので、インストーラ付きでビルドできる electron-builder を使った方が便利なようです。

使い方も簡単で、公式ドキュメントの通りに npm を使って electron-builder をインストールします。

npm install -D electron-builder

インストール後に packages.jsonscripts にコマンドを追加しつつ、同時に build セクションを追加しておきます。スクリプトベースでも定義できるようですが、簡単な方を選びました。

{
  "name": "electron-quick-start",
  "version": "1.0.0",
  "description": "A minimal Electron application",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "pack": "electron-builder --dir",
    "dist": "electron-builder"
  },
  "build": {
    "appId": "SampleApp"
  },
  "author": "GitHub",
  "license": "CC0-1.0",
  "devDependencies": {
    "electron": "^8.2.4",
    "electron-builder": "^22.6.0"
  }
}

appId は無くてもビルドは通りますが、ドキュメントには明示的に指定するように強く書かれていたので設定しています。本来ならユニークな値を設定する必要があります。

設定完了後に npm run dist を実行すると実行ファイルが生成されます。

f:id:shiba-yan:20200429193134p:plain

アイコンなどのリソースは決められたパスにファイルを置いておけば自動的に使われます。指定しない場合は Electron のデフォルトアイコンのままになるので、実際のアプリ開発時には用意しておきましょう。

複数アーキテクチャ対応のインストーラーを作成

デフォルトの設定のままでは x64 向けの実行可能ファイルが生成されましたが、アプリケーションの配布時には複数アーキテクチャに対応したインストーラーが必要になってきます。

Electron では Windows 環境において ia32 (x86) / x64 / arm64 がサポートされています。

electron-builder を使って複数アーキテクチャに対応させるには win セクションを追加して、その中でビルドするアーキテクチャを指定します。

以下のように書くと Windows で利用可能な全てのアーキテクチャ向けにビルドします。

{
  "name": "electron-quick-start",
  "version": "1.0.0",
  "description": "A minimal Electron application",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "pack": "electron-builder --dir",
    "dist": "electron-builder"
  },
  "build": {
    "appId": "SampleApp",
    "win": {
      "target": {
      	"target": "nsis",
      	"arch": ["ia32", "x64", "arm64"]
      }
    }
  },
  "author": "GitHub",
  "license": "CC0-1.0",
  "devDependencies": {
    "electron": "^8.2.4",
    "electron-builder": "^22.6.0"
  }
}

x86 ではなく ia32、amd64 ではなく x64 と書く必要があるのが少し罠っぽいです。若干統一感に欠ける印象がありますが、間違った場合にはビルドエラーになるのですぐに気が付くはずです。

これも先ほどと同様に npm run dist を実行するとインストーラー付きで作成されます。

f:id:shiba-yan:20200429204541p:plain

このインストーラーを Surface Pro X にコピーして実行してみると、問題なく arm64 版がインストールされてアプリケーションが動作していることが確認できます。

Surface Pro X 上では 64bit プロセスは ARM64 で動作している場合だけなので、タスクマネージャーから簡単に確認することが出来ます。

f:id:shiba-yan:20200429203052p:plain

Quick Start は特にプラットフォームに大きく依存するものを使っておらず、外部のライブラリも参照していないので簡単に ARM64 向けビルドが行えました。

しかし普通は何かしらライブラリを使うはずなのでその辺りも検証していきます。

Native Module をビルドして利用

Node.js のモジュールにはネイティブコードを使うものがあり、通常なら node-gyp でいい感じにビルドするか、node-pre-gyp でビルド済みバイナリをダウンロードするのであまり気にする必要がないです。

現状 Windows 10 の ARM64 向けバイナリはほぼ提供されていないので、自前でビルドする必要があります。Electron 向けにビルドするのはドキュメントがありますが割とめんどくさい感じです。

しかし electron-builder は自動的に上のドキュメントにある処理を行ってくれるので、何も考えなくてもコマンドを叩くだけで ARM64 向けビルドが行えます。

実際にビルドが必要になるライブラリで試してみます。今回は bcrypt を使ってみました。

npm install bcrypt

ARM64 向けのビルドを行うためにはコンパイラーやライブラリをインストールする必要があるので、予め Visual Studio Installer からインストールしておきます。

f:id:shiba-yan:20200430165434p:plain

これまでのように npm run dist を実行するだけで、bcrypt のビルドが実行されていることがログから確認できます。ARM64 の場合は node-gyp によってビルドが行われています。

f:id:shiba-yan:20200430163456p:plain

Native Module を使っていても、electron-builder を使っていれば自動でビルドが走るので便利です。ただしビルド対象のライブラリが ARM64 向けにコンパイルできないコードの場合はエラーになります。

アプリケーションを AppX としてビルド

インストーラーとしてこれまでビルドしてきましたが、Windows Store での配布を行う場合には AppX や MSIX としてビルドする必要があります。electron-builder は AppX としてのビルドに対応しているので、多少の設定変更でビルド出来ます。

設定は以下のように変更しています。変更点としては targetappx に変更し、appx セクションを追加しているぐらいです。AppX の場合は identityName の設定は必須のようです。

{
  "name": "electron-quick-start",
  "version": "1.0.0",
  "description": "A minimal Electron application",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "pack": "electron-builder --dir",
    "dist": "electron-builder"
  },
  "build": {
    "appId": "SampleApp",
    "win": {
      "target": {
        "target": "appx",
        "arch": ["ia32", "x64", "arm64"]
      }
    },
    "appx": {
      "identityName": "ElectronQuickStart"
    }
  },
  "author": "GitHub",
  "license": "CC0-1.0",
  "devDependencies": {
    "electron": "^8.2.4",
    "electron-builder": "^22.6.0"
  },
  "dependencies": {
    "bcrypt": "^4.0.1"
  }
}

electron-builder のバージョンは 22.6.0 以上を使わないと、ARM64 向けビルドの時にエラーになります。

これまでのようにビルドを行うと、各アーキテクチャ向けに AppX が生成されます。

f:id:shiba-yan:20200430170351p:plain

appxbundle は生成されないですが、Windows Store へのアップロード時には appx で問題ないです。

appx を開いてみるとインストーラーが立ち上がります。署名がないので今はインストールエラーになりますが、Windows Store へのアップロード目的の場合は署名無しで問題ありません。

f:id:shiba-yan:20200430225908p:plain

サイドローディングの場合は署名を行う必要がありますが、これもまた electron-builder が対応しています。

アプリケーションに署名を行う

electron-builder を使うと PFX とパスワードだけ用意しておけば簡単に署名が行えます。証明書はとりあえず自己署名証明書を作成して試すので、PowerShell を使って適当に PFX を作成しておきました。

署名の設定は環境変数を使って行うので、CI でも簡単に実行可能でしょう。以下のような環境変数を設定しておけば、自動的に署名が実行されます。

set CSC_LINK=certificate.pfx
set CSC_KEY_PASSWORD=P@ssw0rd

ビルド後に署名されているか確認するには、AppX のファイルプロパティを開くだけです。

f:id:shiba-yan:20200430231237p:plain

Windows Store 以外からインストールするためには信頼済みの署名が必要なので、認証局から発行されたコードサイニング証明書を使うか、自己署名証明書を「信頼されたルート証明機関」に入れる必要があります。

証明書をインストールすれば、appx を開いたときに警告が表示されなくなります。

f:id:shiba-yan:20200430232435p:plain:w550

これでインストールが完了したので、後はスタートメニューからアプリケーションを選べば起動出来ます。

f:id:shiba-yan:20200430232445p:plain

思ったより Electron のビルド周りのインフラが洗練されているという印象を持ちました。

外部のライブラリ次第ではありますが ARM64 への対応も比較的簡単になっているので、対応アプリが増えていくことを期待しています。まずは Teams は何とかしてほしい。

Vcpkg と CMake を使って Windows 10 ARM64 向けクロスコンパイルを行う

Windows 10 の ARM64 向けビルドは実質 Surface Pro X 向けですが、Surface Pro X でも x86 エミュレーションのオーバーヘッドを調べると無視できるレベルではなかったので、やっぱりアプリケーションは ARM64 ネイティブで動かしたい気持ちが高まってます。

ちなみに Geekbench 4 で x86 と ARM64 を比較した結果は以下のようになります。Surface Go よりは速いのですが、ARM64 ネイティブと比べると差がかなり大きいです。

C# での ARM64 ネイティブ実行は .NET Core 3.0 のコンソールアプリケーション以外不可能なので、現時点で ARM64 ネイティブのデスクトップ向けアプリを作るためには Win32 か UWP になります。*1

ARM64 ネイティブのアプリが増えるにはライブラリがビルド出来る必要があるので、Vcpkg と CMake の 2 つでクロスコンパイルの方法を試しました。昔に比べるとビルドが簡単になってて驚いてます。

ARM64 ビルドツールをインストール

まずは Visual Studio Installer を使って ARM64 ビルドツールをインストールします。ARM64 で検索するとたくさん出てきますが、バージョンの新しいやつを入れておけば良いです。

f:id:shiba-yan:20191201181745p:plain

Win32 向けと UWP 向けの 2 つが出てくるので、両方インストールしておけばよいです。

Vcpkg を使ってビルド

C++ 向けのパッケージマネージャは C# と同じように、NuGet を使ってプリコンパイル済みのバイナリを拾ってくるのかと思ってましたが、最近はソースからビルドする方が流行っているようです。

マシンパワーが格段に上がった昨今では、環境依存を少なく出来るので確かに便利です。

Vcpkg の使い方は既にいろんな人が書いているのと、README に書いてあるコマンドをいくつか叩くだけでセットアップできるので丸っと省略します。

最初から ARM64 Windows に対応した Triplets が用意されているので、以下のようにインストール時に arm64-windows を指定するだけでビルドが行われます。

vcpkg install zlib:arm64-windows

vcpkg install boost-regex:arm64-windows

たまに先に x86-windows 向けのパッケージをインストールしてから再実行しろというエラーメッセージが出ますが、メッセージに従えば大体は問題なくビルドに成功します。

実際に zlib と boost-regex を ARM64 向けにビルドしてみましたが、問題なく完了しました。

f:id:shiba-yan:20191202020646p:plain

パッケージマネージャらしく vcpkg list を叩くと、ビルド済みのパッケージの管理が出来ます。

f:id:shiba-yan:20191201182431p:plain

Vcpkg でビルドしたバイナリをそのまま、いい感じに C# のプロジェクトから参照するような MSBuild Task を用意したい気持ちでいっぱいです。

P/Invoke で使いたいシチュエーションも結構あると思うので、もうちょっと調べてみるつもりです。*2

CMake を直接使ってビルド

Vcpkg のベースになっている CMake も最初から ARM64 向けクロスコンパイルに対応しているので、こっちの方法も試してみました。CMake に関してはドキュメントを読むのが特に重要な感じでした。

全てのパッケージが Vcpkg でビルド出来れば楽ですが、そういう都合のいいことは無いので CMake 力を多少なりとも上げておきます。非対応のパッケージは当然ながらあります。

今回は Vcpkg で対応していたのですが、ARM64 ビルドは出来なかった OpenCV で試しました。というか ARM64 に対応させるために CMake の定義にパッチを当てました。

ARM64 向けツールセットは -A ARM64 を指定すると自動的に選択されましたが、CMAKE_SYSTEM_PROCESSOR は自動設定されないようだったので ARM64 を明示的に追加して対応しました。

今回 ARM64 のクロスコンパイルに成功した CMake のパラメータは以下のような感じでした。

cmake -G "Visual Studio 16 2019" -A ARM64 -DCMAKE_SYSTEM_NAME=Windows -DCMAKE_SYSTEM_VERSION=10.0 \
           -DCMAKE_SYSTEM_PROCESSOR=ARM64 -DWITH_OPENCL=OFF -DWITH_FFMPEG=OFF -DWITH_CUDA=OFF \
           -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON C:\opencv4

内部で参照されている CMAKE_CROSSCOMPILINGCMAKE_SYSTEM_NAMECMAKE_SYSTEM_VERSION を明示的に指定しないと設定されないらしく、結構な時間を溶かしてしまいました。

CMake でのクロスコンパイルで重要なのは CMAKE_SYSTEM_NAME / CMAKE_SYSTEM_VERSION / CMAKE_SYSTEM_PROCESSOR のようなので、忘れないようにしっかり残しておきます。Vcpkg は微妙にこの辺りの設定をミスっている気配ありますが…。

作業中にはまったポイントなど

C/C++ ランタイムのインストール

Vcpkg や CMake のデフォルトでは C/C++ ランタイムは動的リンクになるので、ビルドしたバイナリを直接 Surface Pro X にコピーしても動きません。忘れないように ARM64 向けのランタイムを入れておきます。

ひっそりと Visual Studio のページからダウンロード可能になっています。

f:id:shiba-yan:20191201222205p:plain

Visual Studio のインストールディレクトリの中にも C/C++ ランタイムのインストーラがあります。

f:id:shiba-yan:20191120215340p:plain

デバッグ用の C/C++ ランタイムは Visual Studio からリモートデバッグする時に、自動的にインストールがされるようなオプションがありましたが試せていません。

OpenGL ライブラリが存在しない

ARM64 Windows 10 の制約として書いてあるように OpenGL は基本的に使えないと考えてよいです。

使えないだけならまだ良いのですが、ARM64 向け SDK には opengl32.lib すら存在しないので、依存しているライブラリは軒並みビルド出来なくなっています。

代わりに ANGLE を使えと回答が付いていますが、本当に ANGLE が動くのかも少し謎です。

https://developercommunity.visualstudio.com/content/problem/164107/cannot-link-opengl-programs-on-arm64.html

Vcpkg に opengl が存在しているので、依存するライブラリは opengl (!arm64) のように CONTROL ファイルで定義しないとビルド出来ないのがかなり辛いです。

デフォルトで追加されるライブラリが少ない?

これはかなり謎な挙動なのですが、OpenCV の highgui をビルド中にリンクエラーが出たので調べたら、x86 / x64 の場合より ARM64 はデフォルトライブラリが少ないような挙動でした。

user32.libkernel32.lib はありますが、comdlg32.libadvapi32.lib が無かったのでリンクエラーになっていました。どうしようもないので CMake 側で対応をしました。

アセンブラを使っていると辛い

当然ながらコンパイルには ARM64 向けのアセンブラが必要ですが、地味に VC++ の ARM アセンブラにリグレッションが多かったので困りました。

OpenCV の場合は SIMD の利用にコンパイラ側の機能を使っていたので、今度はコンパイラ依存の問題もあったりでひとまず棚上げにしたりも。リベンジはしたいです。

*1:Electron という選択肢もあるが試していない

*2:無ければ多分書く

Surface Pro X (ARM64) に対応したアプリケーションを作る

10 インチの Surface Go に ARM の CPU が載ることを期待していましたが、Surface Pro X に載ってしまったので検証用に買うべきか悩んでいます。本体だけなら $999 から買えるので比較的安いです。

例によって日本での発売は未定ですが、フラグシップとなる Surface Pro X に ARM が載ったことで、それなりの台数が出そうなのでアプリ側の対応をしておくと幸せになれる予感がするので調べました。

先に ARM64 で動作するアプリを作る方法を 32 行でまとめておきました。Windows 向けにアプリを作る一般的な方法を選んでいますが、別の UI Framework を使えば実現出来るかも知れません。

  • .NET Framework を使って Any CPU 向けにビルド
  • UWP を使って ARM64 向けにビルド
  • C/C++ (Win32) を使って ARM64 向けにビルド

もうちょっと具体的に書いてみると、以下のようになると思います。実機がないので細かい部分までは追えていないですが、ビルド通ってしまえば勝ちという感じがあります。

  • .NET Framework (Any CPU) はそのまま ARM64 x86 で動く
    • NuGet などで Native DLL を参照している場合は実行時エラーになる
  • .NET Core は Windows 10 ARM64 を未サポート
    • .NET Core 3.0 でもダメ
    • ARM32 向けにビルドすれば動く (WPF / Win Forms 以外)
    • WPF / Win Forms は x86 向けにビルドするとバイナリトランスレーションで動く
  • UWP (.NET Native) は 6.2.7 から ARM64 をサポート
    • 新しくプロジェクトを作ると ARM64 ターゲット付きになっている
  • C/C++ (Win32) は ARM64 向けにビルドすれば動く
    • ARM64 向け開発ツール (コンパイラ / ライブラリ) のインストールが必要

Any CPU な .NET Framework 向けアプリならすんなり動きますが、ネイティブコードが含まれているライブラリを使っているケースは厄介です。最近は NuGet からビルド済みアセンブリをインストール出来ますが、殆どは ARM64 向けのバイナリを持っていないので詰みます。

意外に思われるかも知れないですが、.NET Core は Windows の ARM64 には公式対応していないです。RID に win10-arm64 を指定するとビルドが通ることもありますが、不具合を抱えている可能性があります。

公式ドキュメントなど

非常に ARM64 に関するドキュメントは少ないです。実機を買って触ってみるのが一番早そうです。

基本的には Visual Studio 2019 と ARM64 の開発ツールを入れてビルドを頑張る、という流れです。

あと、つもりんが Windows on ARM64 の実機を持っているので、ちょいちょいブログに書いてくれてます。

.NET Framework

Windows 10 の ARM64 版には .NET Framework の ARM64 版がインストールされているので、Any CPU でビルドしているアプリケーションは問題なく動作するはずです。

パッと見た感じでは ARM64 に限定したビルドは無理のようでした。普通は Any CPU で作ると思うので問題ないですが、ネイティブ DLL を読み込む場合は ARM64 向けのビルドを用意しておかないと死にます。*1

外部ライブラリを使う場合はマネージドコード実装のものを選んでおくと安心です。

追記

Surface Pro X で確認したところ、.NET Framework でビルドしたアプリケーションは強制的に x86 として実行されました。ARM32 / ARM64 向けに JIT Compile されないようなので、全体的にエミュレーションでの動作になるようでした。

つまり .NET Framework のままでは ARM64 への対応は絶対に無理ということです。

.NET Core

.NET Core は ARM64 に対応していないので、Surface Pro X に持って行っても動きません。ただし ARM32 や x86 としてビルドすると動作するので、とりあえずは妥協するしかなさそうです。

つもりんに協力してもらって ARM64 の Windows 上でいろいろと確認してもらいました。何故か対応してないはずの ARM64 向けにビルドが出来たので、それも確認してもらいました。

コンソールアプリケーションは問題ないですが、WPF / Win Forms に関してはランタイムが x86 / x64 向けにしか公開されていないので、ARM としてビルドは絶対にできないようになっています。

なので x86 としてビルドして、バイナリトランスレーションに頼る形になります。

x86 から ARM への変換が行われて、問題なく WPF のアプリケーションが動きました。

WPF (.NET Core) の ARM64 対応は今のところ 5.0 で予定されているみたいですが、先なので妥協します。

Surface Pro X がめちゃくちゃ売れたら優先順位が上がるかも知れませんが、望みは薄いでしょう。

UWP (.NET Native)

UWP に関しては元々 Windows 10 Mobile で ARM 対応が行われていたのもあり、ARM64 の対応は比較的スムーズに行えそうな感じです。とはいえ外部ライブラリの問題は残って来ます。

新しくプロジェクトを作れば、最初から ARM64 のプラットフォーム設定が追加されています。

f:id:shiba-yan:20191003205511p:plain

ARM のターゲットは Windows 10 Mobile 以外では必要ないので、いっそ削除しても良い気がします。

アプリケーションのパッケージ作成時に ARM64 にチェックを入れれば、対応したパッケージが作られます。依存関係が少なければ非常に簡単なので、まずは UWP から対応するようにしています。

f:id:shiba-yan:20191003205407p:plain

ビルドが成功しても認定ツールを走らせたり、ストアへの申請時のチェック時にエラーとなる場合があります。原因は大体ライブラリが ARM64 に対応していないことなので、ライブラリを特定して対応をお願いしたり、コントリビュートが必要になるでしょう。

C/C++ (Win32)

Win32 API を使って書いている場合も、基本は ARM64 向けのプラットフォームを追加してビルドすれば良いです。依存するライブラリが ARM64 に対応している必要がありますが、自分でビルドすることも可能です。

プラットフォームの追加ですが、最初から ARM64 が用意されているので簡単に行えます。

f:id:shiba-yan:20191003210637p:plain

適当にビルドしてみましたが、ちゃんと ARM64 向けのコードが生成されていることが確認できます。

f:id:shiba-yan:20191003194053p:plain

ビルドしたアプリケーションを開発用のマシンから ARM64 へ展開する場合は、Remote Tools for Visual Studio 2019 の ARM64 版をインストールすれば良いです。開発者モードをオンにしても出来る気がします。

ARM64 への対応は簡単な場合はやばいぐらいすぐに終わりますが、極端に難易度が高くなるケースもあるので行うべきかの判断が難しいです。

Surface Pro X がバカ売れしたら対応せざるを得ないはずなので、もう少し様子を見ても良いとは思います。

*1:Win32 API は ARM64 なので問題なし

Windows Server 2019 時代の Windows Containers を使ったアプリケーション移行

Kubernetes が 1.14 から Windows Server 2019 をサポートするようになり、周辺ツールやサービスも整ってきた現状、今年こそ Windows Containers がぼちぼち使われるようになるのではないかと思っています。と言っても新規開発は ASP.NET Core を推奨しているので、基本は既存のアプリケーションの移行目的です。

まだ AKS で Windows Node を作ることは出来ないですが、例によって AKS-Engine を使うと作れるようです。最近は k8s への興味が薄れつつあるので AKS が対応するまでは試さないです。

今回 k8s でサポートされたのは Windows Server 2019 だけっぽいので、2016 は捨てていく方向で良いと思います。2016 では Docker Image サイズ削減の恩恵も受けられないですし、リビジョンが違うとサポートを受けられないのは致命傷です。

毎年言ってる気がしますが、今度こそ一通り必要な環境やサービスが整ったと思うのでまとめます。

Docker Image

去年から徐々に MCR に移行が進んできましたが、今月になって .NET Framework / .NET Core の Docker Image が MCR に移行されたので、これで移行はほぼ完了となります。

Docker Hub を指している方は今後更新されなくなるはずなので、早めに差し替えておきましょう。移行先の情報は Docker Hub か以下の記事を確認してください。

具体的な例を挙げると .NET Framework を使っている場合は、以下の 2 つだけ覚えておけば良いです。

  • mcr.microsoft.com/dotnet/framework/sdk:4.7.2-windowsservercore-ltsc2019
  • mcr.microsoft.com/dotnet/framework/aspnet:4.7.2-windowsservercore-ltsc2019

Windows Containers を使う場合には、今後は ltsc2019 を指定しておけば良いです。.NET Core の場合も同じような命名なので覚えやすいはずです。

開発環境

Visual Studio 2017 では ASP.NET アプリケーションを Docker 化する場合には、何故か Docker Compose が必須になっていました。専用のプロジェクトも追加されて使い勝手が悪かったです。

f:id:shiba-yan:20190328163652p:plain

来月リリースされる Visual Studio 2019 では、ASP.NET Core と同じように Dockerfile だけが追加されるシンプルな形式に変わります。

f:id:shiba-yan:20190328163721p:plain

Visual Studio を使ったデバッグはこれまで通り行えるので、別に Docker になったからといって使い勝手が変わることは特にありません。しいて言えば初回の Docker Image の pull に時間がかかるぐらいです。

現実問題として Windows Containers を使いたいケースは実行環境のカスタマイズ系だと思うので、ベースとなるイメージを CI などで作っておくことで、ある程度のタイミングは制御可能でしょう。

CI / CD

必ず必要になってくるのが CI / CD です。単純に Dockerfile から Image を作って ACR にプッシュするだけなら ACR Tasks を使って簡単に用意できます。

ACR Tasks は既に Windows Server 2019 にアップデートされているので安心です。

もう少し複雑なリリースを行いたい場合は Azure Pipelines を使います。こっちも少し前に Windows Server 2019 と Visual Studio 2019 に対応した Hosted Agent が追加されています。

f:id:shiba-yan:20190328165433p:plain

残念なことに Windows Server 2019 の Docker Image がキャッシュされていないのでビルド毎に無駄な時間が発生しますが、今後の Agent アップデートで直ると思います。

地味に嫌な問題としては公開されている .NET Framework SDK の Docker Image をそのまま使うと、ASP.NET アプリケーションのプリコンパイルに失敗するというのがありましたが、回避策があるのと Visual Studio 2019 の SDK によって解決しそうです。

Visual Studio 2017 の MSBuild が .NET 4.6.2 SDK をデフォルトで見るようになっているので、4.7.2 SDK しか入っていない Docker Image で失敗するという話でした。

実行環境

先述したように Kubernetes が Windows Server 2019 をサポートしたことで、近いうちには AKS でも簡単に Windows Server 2019 の Node が作成可能になるのではと思っています。*1

Web App for Containers (Windows) も順調にアップデートを重ね、まあまあ使えるようになってきた印象です。少し前に Windows Server 2019 へのアップデートも行われています。

GitHub ではちょっとマニアックなサンプル Dockerfile も公開されています。

この辺りの作業は VM 向けの Image を作るのとあまり変わらないので、Dockerfile を書いて環境をカスタマイズしていく形です。Server Core なので少し注意が必要ですが。

まとめ

実際のところ 98% は通常の App Service に移行が行えると思っているので、ソフトウェアのインストールが必要だったり、サンドボックスで制限されている機能を使っている 2% を VM や k8s を使って動かすのではなく、マネージド環境で動かしたいときに使うものだという認識です。

Docker Image が大きくて扱いにくいという問題は抱えつつも、環境自体は Linux と同じような構成を取れるようになったので、主にエンプラ向けで活躍できるのではないかと思います。

*1:ARM Template を使えばこれまでもデプロイは出来ていたけど

Windows 10 で Process Isolation を使う時の注意点など

Docker Engine 18.09.1 と Windows 10 Version 1809 の組み合わせ時に Process Isolation *1 が使えるようになっています。Windows Server Containers は Hyper-V Containers より軽量なので助かります。

早速弄っていましたが、クライアント OS で使う場合にありがちな問題に当たったので、調べてメモとして残します。Windows Server で使う場合には問題になりにくいです。

Process と Hyper-V で ACL が異なる

例えば Nano Server を Process Isolation かつ Volume をマウントして起動してみます。

docker run --rm --isolation=process -v "C:\Users\shibayan\source\repos\AspNetCore:C:\AspNetCore" \
           -it mcr.microsoft.com/windows/nanoserver:1809 cmd

この時にマウントしたディレクトリを確認しようとすると、アクセスが拒否されます。

f:id:shiba-yan:20190208172133p:plain

Hyper-V を使った場合は問題なくアクセス出来るのに、Process の場合は拒否されるので割と悩んでいましたが、以下のドキュメントに答えが載っていました。

Hyper-V の場合は単純に RO / RW という制御しかされないみたいですが、Process の場合は NTFS の ACL が使われるので、コンテナ内の実行ユーザー次第でアクセスが出来ないという話でした。

特に Nano Server は 1709 から実行ユーザーが ContainerAdministrator から ContainerUser に変更されているため、マウントするファイルに権限を付けるか、実行ユーザーを変更する必要があります。*2

ドキュメントには Authenticated Users が載っていたので、とりあえず付けて試します。

f:id:shiba-yan:20190208173436p:plain

これでコンテナから再度アクセスしてみると、問題なくディレクトリを確認出来ました。

f:id:shiba-yan:20190208171523p:plain

通常はサーバー OS でこういった使い方はしないので、Windows 10 で使えるようになると似たような問題が他にも出てきそうな気がします。

ちなみに Server Core の場合は ContainerAdministrator で動いているのと、ACL に大体は Administrators が入っているので問題ないみたいです。

Visual Studio からは一部利用不可

Process Isolation で Nano Server を使った場合に ACL 周りで問題が発生するため、Visual Studio のコンテナサポートを使った ASP.NET Core アプリケーションの開発は行えなくなります。

イメージのビルドまでは問題なく行えますが、デバッグ実行では Visual Studio が User Profile 以下にあるファイルをマウントしようとするため、リモートデバッガのプロセスを起動できずに失敗します。

f:id:shiba-yan:20190208174937p:plain

ASP.NET アプリケーションの場合は Server Core を使うので、問題なく Process Isolation でもデバッグ実行が行えます。Docker Compose を使おうとしたり、ベースイメージが古かったりと罠が多いですが、Hyper-V よりは素早く起動してくれます。

Default Isolation を Process に変更する

今のところ Visual Studio には Isolation を設定する機能などはないので、Process Isolation を使う場合には Default 自体を変更しておく必要があります。

Docker Deamon の設定に exec-opts を追加するだけなので簡単です。

{
  "exec-opts": [
    "isolation=process"
  ]
}

Docker の設定から Advanced を選べば JSON を書けるようになっているので、以下のように追記します。

f:id:shiba-yan:20190121021001p:plain

これで設定を保存すると Default Isolation が hyperv から process に変わります。

f:id:shiba-yan:20190121020911p:plain

Default Isolation を Process にしていても LCOW は問題なく Hyper-V で動くので、Volume 周りの影響を受けない場合は Process を使うと素早くコンテナを実行出来るので便利です。

*1:Windows Server Containers と呼ばれているやつ

*2:Nano Server の ContainerAdministrator は将来的に削除される予定なので注意

Windows Server 2019 の Docker Image と MCR への移行

停止されていた Windows Server 2019 の公開がようやく再開されました。同時に Windows Server 2019 ベースの Server Core / Nano Server イメージも公開が再開されています。

これでようやく LTSC なバージョン上でサイズが縮小された Docker Image を使う準備が出来ました。

既にこれまで Windows Server 2016 を使っていたイメージに 2019 版が追加され始めています。主に .NET Framework 周りは対応が早く、既に ltsc2019 で各種イメージが選べます。

サイズが ltsc2016 と比べて 1/3 近くになっていることも確認できます。やっと実用が可能なサイズに。

f:id:shiba-yan:20181116182816p:plain

ASP.NET や ASP.NET Core (Nano Server) や IIS 向けには 2019 はまだ提供されていないですが、既に PR は作成されているので、近日中にリリースされると思われます。

それはそうと今回の 2019 リリースに合わせて、これまで Docker Hub で公開されていたイメージが Microsoft Container Registry からの公開に切り替わっています。

2019 年から latest タグを使わないようにしていくみたいなので、既に latest を使っている場合には注意。

Docker Hub で新しいイメージ名が確認出来るので迷わないと思いますが、MCR は公開されているイメージやタグをブラウズする機能がないので、正直使いにくいなと思います。

一応タグ名は同じでイメージ名だけが変わっているみたいなので、単純な置き換えで済みます。

# これまで
docker pull microsoft/windowsservercore:ltsc2016

# これから
docker pull mcr.microsoft.com/windows/servercore:ltsc2016

命名のルールは MCR の方が分かりやすいです。現実的にはタグは ltsc2016 か ltsc2019 しか使わないと思うので、この二つだけ覚えておけばよいと思います。

今のところは Server Core / Nano Server などの OS ベースとなるイメージや、PowerShell Core / Azure Functions Runtime / SQL Server 2019 Preview などが MCR に移行済みのようです。

https://hub.docker.com/u/microsoft/

.NET Framework や .NET Core 周りのイメージも順次 MCR への移行を進めていく予定のようです。GitHub の Issue にイメージ名のプロポーザルが載っています。

まだリリースされていないし、確定でもないようですが Server Core のように階層化された形で管理されるようです。既に .NET Core のイメージは結構使われているので、バッサリと移行は行わないみたいです。

MCR は当然ながら Azure 上に構築されていて、さらに Traffic Manager を使って各地に分散されているので、Azure Pipelines や Web App for Containers などで使うとネットワーク的に有利な予感です。

Multi-arch 時代の Windows Containers 関連イメージの扱い

Windows Containers を使っていて非常に悩ましいのが OS バージョンですが、最近は Multi-arch が使われているのでホスト OS のバージョンから適切なものを自動で利用してくれます。

Multi-arch の詳細は Docker 公式ブログを読めば大体理解できるはずです。

最近はちょいちょい Server Core や Nano Server に対応したイメージを見かけますね。

イメージに含まれているプラットフォームを確認する方法も用意されているので、例えば .NET Framework 4.7.2 の SDK イメージの情報を確認したい場合には以下のコマンドを使います。

docker run --rm mplatform/mquery microsoft/dotnet-framework

実行すると、含まれているプラットフォームの一覧が出力されます。

f:id:shiba-yan:20180810133852p:plain

Windows Server の LTSC 2016 / Version 1709 / Version 1803 それぞれに対応していることがすぐにわかります。1709 からはリビジョンの制約がなくなっているので、気にせず使えるのはメリットです。

ビルド済みのイメージに対して docker history を実行すると、ベース OS のバージョンが確認出来ます。

f:id:shiba-yan:20180810141512p:plain

バージョンを気にすることなくイメージを取得して実行できるのは便利なのですが、同じイメージ名なのに落ちてくるものは環境依存となるので、開発やビルド、実行時の OS が異なると簡単に事故ります。

特に実際にイメージを作成する CI 部分と実行環境のバージョン違いは致命的なので、よく使われそうなサービスに関して情報を軽くまとめておきます。

CI SaaS

サービスの大半は LTSC 2016 が使われているので、開発環境が Windows 10 の場合はビルド番号が大きく異なっているはずです。

  • ACR Build
    • Version 1803 (Hyper-V)
  • AppVeyor
    • LTSC 2016
  • AWS CodeBuild
    • LTSC 2016
  • Visual Studio Team Services
    • LTSC 2016

ACR Build に関してはホスト OS が 1803 ですが、Hyper-V Containers が使われているので LTSC 2016 と Version 1709 のイメージも問題なく扱えるようになっています。

実行環境

数少ない実行環境ですが、こっちも大半が LTSC 2016 が使われています。2019 リリース待ちですね。

  • Web App for Containers (Windows)
    • LTSC 2016
  • Azure Container Instances
    • LTSC 2016
  • Azure Kubernetes Service
    • Version 1709 or 1803

AKS に関しては前に試した時は 1709 が使われていましたが、最近の ACS Engine を見ると 1803 にも対応しているようだったので、両方とも記載しています。Hyper-V Containers ではないので非常に厄介です。

(現時点での)最適なイメージ

現時点での結論としては、使用するイメージは全て LTSC 2016 に統一し、Dockerfile でも明示的に OS バージョンを指定することをお勧めします。

指定していない場合はホスト OS からイメージのバージョンが決定されるので、例えば ACR Build を使うと自動的に 1803 が使われることになります。

# .NET Framework の場合
FROM microsoft/dotnet-framework:4.7.2-sdk-windowsservercore-ltsc2016

# ASP.NET の場合
FROM microsoft/aspnet:4.7.2-windowsservercore-ltsc2016

例外として AKS で Windows Containers を使う場合には、既存の CI SaaS の大半が未対応となってしまうので、ACR Build しか組み合わせる選択肢が存在しないことになります。

2019 リリース後には LTSC 2016 からの移行が発生するであろうこともお忘れなく。