Windows Containerを使ってみて、独自イメージビルド時の注意点

この記事は富士通システムズウェブテクノロジーの社内技術コミュニティで、「イノベーション推進コミュニティ」
略して「いのべこ」が企画する、[いのべこ夏休みアドベントカレンダー 2020](https://qiita.com/jugemsan/items/5761773308fb1df0c23a)の12日目の記事です。
本記事の掲載内容は私自身の見解であり、所属する組織を代表するものではありません。
ここまでお約束 :wink:

# Windows Containerとは

Windows ContainerとはWindows10またはWindows Server（2016以降）で利用できる、
Windwosベースのコンテナのことです。

docker runでWindowsが動き出すのはちょっと感動です。もちろん、Windows環境でしか利用できませんが・・・

本記事では、Windwos ServerでWindows Containerの機能をインストールし、起動～Dockerfileによる独自イメージの作成を行った際に、ハマったこと・Windows ContainerならではのDockerfileの書き方の工夫を書き残します。

Windows Containerを使用するためのセットアップは、以下のサイトを参考にしています
https://docs.microsoft.com/ja-jp/virtualization/windowscontainers/quick-start/set-up-environment?tabs=Windows-Server

### 環境

* Windows Server 2019 Standard 1809 (ビルド番号:17763.1397) 

## 基本イメージ

Windows Containerには、いくつかの基本イメージがあります。
https://docs.microsoft.com/ja-jp/virtualization/windowscontainers/manage-containers/container-base-images

イメージごとに、インストールされているコンポーネントが異なり、サイズもそれに応じて異なります。

| 基本イメージ名 | サイズ |
| -------------- | --------| 
| mcr.microsoft.com/windows | 4.2G |
| mcr.microsoft.com/windows/servercore | 3.7G |
| mcr.microsoft.com/windows/nanoserver | 251M |
| mcr.microsoft.com/windows/iotcore | 307M |

※すべてタグは「10.0.17763.1217-amd64」を指定しています。

### タグについて

ホストしているWindows Serverのビルド番号（今回の例だと、17763.1397）と、コンテナのタグのビルド番号(10.0.{ビルド番号}-xxxx-amd64の部分)が異なると、コンテナは起動しません。

例えば

ホストのビルド番号が14393.*で、コンテナのビルド番号が17763.1217の場合、コンテナの起動はブロックされます。
私の環境だと、そもそもpullするときに`指定したタグ名: Pulling from windows no matching manifest for windows/amd64 10.0.17763 in the manifest list entries`というエラーが出て、pullできなかったです

これは、Windows Containerと、ホストしているサーバのカーネルは共有されるためです。
公式ページの説明によると、「起動するかもしれませんが・・・」と書かれておりますが、起動させることすらできませんでした。
https://docs.microsoft.com/ja-jp/virtualization/windowscontainers/deploy-containers/version-compatibility?tabs=windows-server-2004%2Cwindows-10-2004#windows-server-containers
Windows Updateにより、ホストしているサーバのビルド番号に変更があった場合には、コンテナも再ビルドが必要になると覚えておいたほうがよさそうです。

### ベースイメージの使い分け

各種ベースイメージには、インストールされているコンポーネントが異なり、公式ページでは以下のように案内されています。
https://docs.microsoft.com/ja-jp/virtualization/windowscontainers/manage-containers/container-base-images#guidelines

* アプリケーションに完全な .NET フレームワークは必要ですか。 
    * この質問への答えが「はい」の場合、Windows Server Core をターゲットにすることをお勧めします。
* .NET Core に基づいて Windows アプリを構築していますか。
    * この質問への答えが「はい」の場合、Nanoserver をターゲットにすることをお勧めします。
* IoT アプリケーションを構築していますか。
    * この質問への答えが「はい」の場合、IoT Core をターゲットにすることをお勧めします。
* アプリに必要な依存関係が、Windows Server Core コンテナー イメージに不足していますか。 
    * この質問への答えが「はい」の場合、Windows をターゲットにしてみることをお勧めします。 このイメージは、他の基本イメージよりもサイズがはるかに大きく、多数のコア Windows ライブラリ (GDI ライブラリなど) が含まれています。

こんな感じのイメージでしょうか。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280383/51c23798-7dc8-df0d-5c6b-74df4379f7e2.png)

そのほかにも、NanoserverにはPowerShellがインストールされていないなどの相違点があるようでした。

# docker run 実行時の注意点（Volume指定時）

volume指定時、マウント先を`D:\`などに指定しようとするとエラーになります。
`C:\`以下にマウントされるように`docker run xxxx -v ${pwd}:C:\share xxxx`と指定してあげる必要があります。

# Dockerfileの書き方

Dockerfileの書き方について、詰まったことを書き記しておきます。

## パッケージマネージャーが弱い

基本的に、Windowsにはyum/aptのような高性能なパッケージマネージャーがありません
PackageManagementという機能があるので、それを使えば必要そうなのはある程度取れそう・・・？
ProviderもChocolateyとかを選べるので、あとはリポジトリの中に欲しいのを探してみてください。

wingetもWindows Serverでも使えるようになったりするかしら？

## RUNコマンドの形式

RUNコマンドは、cmd.exeに対して実行されるようで、cmd.exeで実行してうまく動かないようなコマンドは失敗します（悲しいかな・・・）

Linuxコンテナの場合、以下のように書いたりすると思います

```Dockerfile
FROM alpine:latest

# パッケージマネージャでいろいろインストールして、そのあと本当に必要なコンポーネントをインストールする。みたいな
RUN apk update \
 && apk add --no-cache curl\
 && curl -L "https://golang.org/dl/go1.15.linux-amd64.tar.gz" -o /tmp/go1.15.linux-amd64.tar.gz \
 && mkdir /go
 && tar /tmp/go1.15.linux-amd64.tar.gz -C /go
 && apk del curl \
 && rm -f /tmp/go1.15.linux-amd64.tar.gz
```

イメージサイズを縮小するためにこのようにするかと思いますが、当然Windowsですがコマンドが変わってきます。

コマンドプロンプトでやるように、`&`で区切ってコマンドを記述していきます。

```Dockerfile:Windows版

RUN powershell.exe Invoke-WebRequest -Uri "https://golang.org/dl/go1.15.windows-amd64.zip"  -Outfile ".\go1.15.windows-amd64.zip" \
  & mkdir C:\go \
  & powershell.exe Expand-Archive .\go1.15.windows-amd64.zip -DestinationPath C:\go \
  & del .\go1.15.windows-amd64.zip
```

powershell内のコマンドをたたくとき、いちいち`powershell`を指定しないといけないので少し面倒です。
１行で実行したいときには、powershell化したりすることを検討したほうがいいかなと思ってます。

## ADDコマンドについて

RUNコマンドなどの場合は、cmd.exeで実行しているので&yen;区切りでコマンドを記述する必要がありますが、ADDコマンドは/区切りでパスを記述しないとエラーになります。。。ﾄﾞｳｼﾃ :sob:
おそらく、Dockerfile側の都合なので、仕方ないのかなと思います。

例えば、こんなDockerfileを書いたとします。

```Dockerfile:Windows版
FROM mcr.microsoft.com/windows/servercore:10.0.17763.1217-amd64

RUN mkdir C:\app
ADD test.exe C:\app
```

これでビルドをかけると、

```
Sending build context to Docker daemon   2.56kB
Step 1/3 : FROM mcr.microsoft.com/windows/servercore:10.0.17763.1217-amd64
 ---> 503b39abeba7
Step 2/3 : RUN mkdir C:\app
 ---> Using cache
 ---> e2b88aa43ff5
Step 3/3 : ADD test.exe C:\app
failed to copy files: failed to copy file: mkdir \\?\Volume{7d8c5343-b2a5-499b-add3-7f392ece5972}\C:.: The directory name is invalid.
```

こんな感じのログで異常終了します。なので、かならず以下のようにしましょう

```Dockerfile:Windows版
FROM mcr.microsoft.com/windows/servercore:10.0.17763.1217-amd64

RUN mkdir C:\app
ADD test.exe C:/app # ADDの時は/区切り！覚える！
```

## 追加ソフトウェアのインストール時

Package Managerに目当てのソフトウェアがない場合、exeやmsiをダウンロードしてきて、ビルド時に実行する必要があります。
その際、以下のようにすることでコマンドラインからsilentインストールができます。

※基本的には、下記の記事の受け売りです
https://qiita.com/lanevok/items/270c4ee73dba1d77e1f9

### msi形式の場合

```cmd
C:\ msiexec /i インストール対象.msi /passive /norestart
```

### exeの場合

```cmd
C:\ インストール対象.exe /s /v"/qn REBOOT=ReallySuppress ADDLOCAL=ALL"
```

上記の方法でうまくいかないものは、個別のオプションがあったりするので、インストールしたいコンポーネントごとにオプションを変えたりしてみてください。

# 感想

触ってみて、起動が若干遅い（ウィルススキャンソフトとかの影響らしいです。Windows Defenderだけでも結構遅い・・・）し、
パッケージ系のインストールがどうしても難ありな感じしかなくて、たぶん検証用途だったり、
開発環境用のサーバ構築用途ぐらいにしか使わないなぁと・・・
もう少しフットプリントが軽くなったりエコシステムが出そろってくると、メリットも出てくるのかもしれませんが。

とはいえ、コンテナ起動時のメモリ使用量も、windowsベースコンテナで起動直後に80MB前後、
dockerdも30MB程度しかメモリを消費していないところを見ると、VMをポコポコ立ち上げるよりはよっぽど軽量です。

これを機に、Windows Server系の資産をコンテナに置き換えられたらいいなと思う今日この頃でした。

それにしても、Windows Containerなのか、Windows Server Containerなのか。どっちなんでしょう。
Windows Serverで動かしたらWindows Server Containerになるとかそういうあれですかね？