WindowsコンテナでWordPressを動かすまでに気づいたWindowsコンテナを使い始める時の注意点

Windows Server 2016からDockerがサポートされ、Windows Server上でWindowsのコンテナを動かせるようになりました。このWindowsコンテナの検証の一環で、Linuxとの違いの確認やWindowsコンテナに慣れるためにdocker-composeでweb(WordPress、IIS)・db(MySQL)が立ち上がるものを作成しました。この記事ではこれらを作る過程で気づいたWindowsコンテナでDockerfile・Docker Composeを作るときの注意点として気づいたことをまとめました。


# ゴールと作成したDockerfile・docker-compose.yml
`docker compose up -d`で以下のようなweb・dbの二つのコンテナが立ち上がりWordPressが使える状態で立ち上がることをゴールとします。

![構成図.PNG](https://qiita-image-store.s3.amazonaws.com/0/86684/9696ca88-914a-d7d3-cc8c-9fb9cb0dc72b.png)


また、今回作成したDockerfile・docker-compose.ymlは[GitHub](https://github.com/ko-he-/iis-mysql-wordpress_on_windows_container)で公開しています。使用方法等はGithubのReadmeを参照ください。

# ステップ0 環境準備
Windows Server 2016に対し、Dockerとdocker-composeをインストールします。環境準備はそれぞれ、以下のドキュメントに従えば問題ありませんが、ホストにするWindows Serverが仮想マシンだと[面倒な場合](https://qiita.com/ko-he-8/items/5de432905ad7294dda5f)もありますのでご注意ください。

Dockerのインストール
- https://docs.microsoft.com/ja-jp/virtualization/windowscontainers/quick-start/quick-start-windows-server

docker-composeのインストール
- https://docs.docker.com/compose/install/#install-compose のWindowsタブ

# ステップ1 Dockerfileの作成
web(IIS + WordPress)とdb(MySQL)用に二つのDockerfileを作成します。それぞれのDockerfileで行う処理は以下になります。

- web
    - IISの設定・ポート解放
    - WordPressのダウンロード・展開
    - wp-config(DB設定)の設定
    - PHPのダウンロード・インストール
- db
    - MySQLダウンロード・インストール
    - ポート解放
    - MySQL rootパスワード設定
    - WordPress用テーブル・ユーザー作成

これらの処理を実装したDockerfileが以下になります。

- WebのDockerfile

```
FROM microsoft/iis
ARG PORT_NUM
ARG DB_USER
ARG DB_PASSWORD
ARG DB_HOST

RUN mkdir c:\site

ADD https://ja.wordpress.org/wordpress-4.8.2-ja.zip /
ADD https://download.microsoft.com/download/C/A/F/CAF5E118-4803-4D68-B6B5-A1772903D119/VSU4/vcredist_x64.exe /build/vc_redist.x64.exe
ADD http://windows.php.net/downloads/releases/php-5.6.32-nts-Win32-VC11-x64.zip /

RUN powershell -command \
    Expand-Archive -Path c:\php-5.6.32-nts-Win32-VC11-x64.zip -DestinationPath c:\php\ ; \
    Remove-Item c:\php-5.6.32-nts-Win32-VC11-x64.zip -Force

ADD php.ini /php/php.ini

RUN powershell -command \
    Expand-Archive -Path c:\wordpress-4.8.2-ja.zip -DestinationPath c:\site\ ; \
    Remove-Item c:\wordpress-4.8.2-ja.zip -Force

RUN powershell -command \
    dir c:\site\wordpress

RUN powershell -command \
    chcp 65001 ; \
    $WP_CONFIG = $(Get-Content "c:\site\wordpress\wp-config-sample.php") ; \
    $WP_CONFIG = $WP_CONFIG.Replace('database_name_here','wordpress') ; \
    $WP_CONFIG = $WP_CONFIG.Replace('username_here','%DB_USER%') ; \
    $WP_CONFIG = $WP_CONFIG.Replace('password_here','%DB_PASSWORD%') ; \
    $WP_CONFIG = $WP_CONFIG.Replace('localhost','%DB_HOST%') ; \
    $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding($False) ; \
    [System.IO.File]::WriteAllLines('c:\site\wordpress\wp-config.php', "$WP_CONFIG" , $Utf8NoBomEncoding)

RUN powershell -NoProfile -Command \
    Import-module IISAdministration; \
    New-IISSite -Name "Site" -PhysicalPath C:\site -BindingInformation "*:%PORT_NUM%:"

EXPOSE ${PORT_NUM}

RUN setx path "%PATH%;c:\php"
RUN setx pathext "%PATHEXT%;.php"
RUN start /w C:\build\vc_redist.x64 /q /norestart
RUN powershell.exe -executionpolicy bypass "Add-WindowsFeature Web-CGI" 
RUN %windir%\system32\inetsrv\appcmd set config /section:system.webServer/fastCGI /+[fullPath='c:\php\php-cgi.exe'] 
RUN %windir%\system32\inetsrv\appcmd set config /section:system.webServer/handlers /+[name='PHP_via_FastCGI',path='*.php',verb='*',modules='FastCgiModule',scriptProcessor='c:\php\php-cgi.exe',resourceType='Either'] 
RUN %windir%\system32\inetsrv\appcmd.exe set config -section:system.webServer/defaultDocument /+"files.[value='index.php']" /commit:apphost
```

- dbのDockerfile

```
FROM microsoft/windowsservercore

ADD https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.16-winx64.zip mysql.zip

ARG ROOT_PASSWORD
ARG WP_USER
ARG WP_PASSWORD


RUN echo SET PASSWORD FOR 'root'@'localhost' = PASSWORD('%ROOT_PASSWORD%') ; > c:\mysql-init.txt

RUN type c:\mysql-init.txt

RUN powershell -command \
    Expand-Archive -Path c:\mysql.zip -DestinationPath c:\ ; \
    ren c:\mysql-5.7.16-winx64 c:\MySQL ; \
    New-Item -Path C:\MySQL\data -ItemType directory ; \
    c:\MySQL\bin\mysqld.exe --initialize --console --explicit_defaults_for_timestamp --init-file c:\mysql-init.txt ; \
    c:\MySQL\bin\mysqld.exe --install ; \ 
    Start-Service mysql ; \ 
    Remove-Item c:\mysql.zip -Force

ENV MYSQL c:\\MySQL

EXPOSE 3306

RUN setx PATH /M %PATH%;c:\MySQL\bin

RUN powershell -command \
    Start-Sleep -s 3

RUN MySQL --user=root --password="%ROOT_PASSWORD%" --connect-expired-password -Be "grant all privileges on *.* to %WP_USER%@'%' identified by '%WP_PASSWORD%' with grant option;" & \
MySQL --user=root --password="%ROOT_PASSWORD%" --connect-expired-password -Be "CREATE DATABASE wordpress CHARACTER SET utf8 COLLATE utf8_bin;"
```


docker buildやdocker run等のコマンドは従来と同じ感覚で使用可能でした。また、Dockerfileの作成において、windowsコンテナでもLinuxと同じ用に命令(RUNやENV)が使えますが、その挙動はWindowsで実行されることを考慮した注意が必要です。以下では各命令の細かい注意事項をまとめました。

## FROM ベースイメージ
FROMで指定できるwindowsコンテナで使用可能なイメージはDockerHubの[microsoftのページ](https://hub.docker.com/u/microsoft/)で公開されています(Windowsコンテナ用ではないものも混ざってます)。

今回は
- web : [microsoft/iis](https://hub.docker.com/r/microsoft/iis/)
    - microsoft/windowsservercoreのイメージにIISが導入済みのイメージ
- db: [microsoft/windowsservercore](https://hub.docker.com/r/microsoft/windowsservercore/)
    - 特に何も入っていないプレーンなイメージ
を使用しました。

今回使用したイメージはどちらもwindowsservercoreがベースとなっていますが、このwindowsservercoreは数ギガあり意外と重いので、検証していませんが、[nanoserver](https://hub.docker.com/r/microsoft/nanoserver/)のイメージの方が良いのかもしれません。

## RUN コマンド実行
RUNでコマンドを実行する場合基本はコマンドプロンプトで実行されるようです。powershellのコマンドを実行したい場合`powershell -command`を付けてから実行します。

- 例
```
RUN powershell -NoProfile -Command \
    Import-module IISAdministration; \
    New-IISSite -Name "Site" -PhysicalPath C:\site -BindingInformation "*:%PORT_NUM%:"
```

### RUN コマンド実行 powershell -Commandが上手くいかない場合

powershell上で直接実行すると問題ないのですが、`RUN powershell -Command`で実行させようとするエラーが出る問題に度々ぶつかりました。これは、コマンドプロンプトのエスケープ仕様に関係していて、[こちら](https://msdn.microsoft.com/ja-jp/library/78f4aasd(VS.80).aspx)に説明があるのですが分りにくいので以下に成功する例と失敗する例を作成しました。

- テスト用Dockerfile

```
FROM microsoft/windowsservercore
ARG VAR=ABC
RUN echo '123 ;;;;;' > c:\test.txt
# 失敗する例　おそらくスペースありの文字列を「"」で囲うとエラーになる。
#RUN powershell -command  \
#    $TEXT_FILE = $(Get-Content c:\test.txt) ; \
#    $TEXT_FILE = $TEXT_FILE.Replace("123 ;;;;;","%VAR% 123 ;;;;;") ; \
#    echo $TEXT_FILE > c:\test2.txt 
#RUN type C:\test2.txt


# 成功する例 「"」を使用しないで「'」を使用する
RUN powershell -command \
    $TEXT_FILE = $(Get-Content c:\test.txt) ; \
    $TEXT_FILE = $TEXT_FILE.Replace('123 ;;;;;','%VAR% ;;;;;') ; \
    echo $TEXT_FILE > c:\test2.txt
RUN type C:\test2.txt

# 成功する例 「"」を「\」でエスケープする
RUN powershell -command  \
    $TEXT_FILE = $(Get-Content c:\test.txt) ; \
    $TEXT_FILE = $TEXT_FILE.Replace(\"123 ;;;;;\",\"%VAR% 123 ;;;;;\") ; \
    echo $TEXT_FILE > c:\test2.txt 
RUN type C:\test2.txt
```

## ADD 必要ファイルのダウンロード
ADD命令でLinuxの場合と同様にホストマシンのファイル or ダウンロードURLを指定してコンテナ内にファイルの追加が可能です。ただし、Windowsコンテナの場合、圧縮ファイルの解凍はpowershellのコマンドで解凍することになるのでzipのダウンロードが無難です。

またpowershellでファイルの解凍は以下のように行います。この例では解凍後、圧縮ファイルは削除しています。

- 例
```
RUN powershell -command \
    Expand-Archive -Path c:\wordpress-4.8.2-ja.zip -DestinationPath c:\site\ ; \
    Remove-Item c:\wordpress-4.8.2-ja.zip -Force
```

## ARG 引数・変数
ARGで変数化した値をコマンド実行中に使用したい場合は`%VAR%`のようにします

- 例
```
RUN MySQL --user=root --password="%ROOT_PASSWORD%" --connect-expired-password -Be "grant all privileges on *.* to %WP_USER%@'%' identified by '%WP_PASSWORD%' with grant option;"
```

一方、それ以外で使用する場合は`${VAR}`のようにします

- 例
```
EXPOSE ${PORT_NUM}
```

## ENV 環境変数

環境変数の設定にはENVを使用できます。しかし、環境変数の新規作成は問題ありませんが追記の場合はENVが使えません。まずは環境変数`MYSQL`を新規作成する場合、以下のようにENVを使用し問題なく動作します。

```
# 問題なく動作する
ENV MYSQL c:\\MySQL
```

同じように、環境変数`PATH`を追記仕様とすると

```
# エラーは出ないが動かない例1
ENV PATH /M ${PATH};c:\MySQL\bin

# エラーは出ないが動かない例2
ENV PATH /M %PATH%;c:\MySQL\bin
```

このように書きたくなりますが、どちらも動きません。
ではどうするのかというと以下のようにsetxコマンドで設定します。

```
RUN setx PATH /M %PATH%;c:\MySQL\bin
```

この問題に関してはPRも上がっているようなので近いうちに修正が入るのかもしれません。

## その他注意事項・参考情報
- Dockerfileのサンプルとして、DockerHubの[microsoftのページ](https://hub.docker.com/u/microsoft/)以外にDockerHubの[nanoserverのページ](https://hub.docker.com/u/nanoserver/)(非公式？)で公開されているものが参考になります。
- コンテナ起動後の設定ファイルの変更等はコマンドラインエディタがないので不便でした。デバッグではホスト上に同じファイルを持っておいてホストの適当なテキストエディタで編集後、`docker cp <コンテナ名>:<ホスト上のファイルのパス>`で送るほうが簡単です。
- コンテナ起動後のデバッグ・設定変更では`docker exec -it <コンテナ名> powershell`でpowershellからコンテナ内に入ることができます
- ファイルのエンコードには注意が必要です。(今回はwp-config.phpがBOMなしUTF-8なので変換しています)


# ステップ2 docker-compose.ymlの作成
今回はwebとdb二つのコンテナをwebにdbをリンクさせ起動するようにしています。docker-compose.ymlとしては特にLinuxとの違いはないように見えました。

- docker-compose.yml

```
version: '3'
services:
  web:
    build:
      context: ./iis
      dockerfile: Dockerfile
      args:
        - PORT_NUM=8000
        - DB_USER=wordpress
        - DB_PASSWORD=p@ssw0rd
        - DB_HOST=db
    ports:
      - 8000:8000
    links:
      - db
    tty: true
  db:
    build:
      context: ./mysql
      dockerfile: Dockerfile
      args:
        - ROOT_PASSWORD=p@ssw0rd
        - WP_USER=wordpress
        - WP_PASSWORD=p@ssw0rd
    tty: true
networks:
  default:
    external:
name: nat
```

## ネットワークの新規作成で失敗？
 docker-compose.ymlでnetworkを指定しないで作成した場合以下のようなエラーが出ました。
 
 ```
Creating network "wordpress_default" with the default driver
ERROR: HNS failed with error : パラメーターが間違っています。
```

ネットワーク作成に失敗しているようですが、これの原因調査は未完了です。ただ、同様の問題は起きているようで、おそらく私の環境の問題(今回はvSphare5.5上の仮想マシンで検証)です。一旦の回避策として以下のように作成済みのnatネットワーク(Dockerインストール時デフォルトで作られた)を指定して回避しています。

```
networks:
  default:
    external:
      name: nat
```

# まとめ
　今回は、WindowsコンテナでWordPressの立ち上げを行い、Dockerfile、docker-composeの動作を確認しました。結果としては、一部問題が残っているところもありますが、基本的にはいままでLinuxでDockerを使用していた人であれば同じような使用感で利用できることが分りました。ただし、今回の例だとLinuxのDockerを使用した方が楽です。Windowsコンテナを何に使うべきかは考える必要があると感じました。