VirtualBox上でのWindows Dockerコンテナ実行環境、プライベートレジストリを可能な限りGUIで用意したい

**★ 注:** 可能な限りGUIを目指したけどGUI成分が少い。リバースプロクシや証明書部分をGUIにできれば。。

実は最近のWindowsにはWindows Admin Center(旧Project Honolulu)というWeb管理ツールが用意されており、これを使うことでPowerShellを覚えなくてもかなりの処理がWebブラウザからできる。

というわけで、これを可能な限り活用してWindows Docker実行環境をVirtualBox上に用意してみた。今回は、ついでにnginxの背後にSonatype Nexus3でDockerのプライベートリポジトリも用意している。


# 知見

![SnapCrab_NoName_2018-9-24_17-17-29_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/67446/f234a86d-ac8b-a9b6-8ff4-613a4afb799f.png)

* VirtualBoxに入れたWindows Serverで普通にDockerホストを構築できること(Hyper-Vコンテナは実行できないけど)
* Windows Admin Center を Firefoxで使う方法
* Windows Admin Centerではファイルのアップロードができなかったので代わりに共有フォルダを設定する方法
* nginxに指定した証明書をWindows Admin Centerを使ってサーバに信頼させる方法
* Nexus3 で構築したDocker registryをnginxの裏に置く方法

## 前提

* MSDNに入っているか、石油を掘っている等の理由でWindows Serverを自由にインストールできる
* VirtualBoxや他の3rd Party製仮想化ソリューションを使っていてHyper-Vが使えない -- Hyper-Vが使えるなら、普通にDocker for Windowsで良い
* PowerShellを覚えたくない -- PowerShell<ruby>力<rt>ちから</rt></ruby>に自信が有るなら、普通にWinRMとPowerShellでやった方が早い

## 何故？
個人的には諸般の事情でWindows10とLinuxのOpenGLが両方必要で、これのためにVirtualBoxが常時稼動している。VirtualBoxとHyper-Vは両立しないので、Hyper-Vが必要なHyper-V Containerは使えない。が、Windows10上のDockerではHyper-V Containerしかサポートされていない。

幸いMSDNに加入しているのでWindows ServerはMAKで認証でき無限にインストールできる。というわけで、Windows Containerを動かしたいときはVirtualBoxにWindows Serverをインストールすれば良い。

通常の人間は素直にDocker for Windowsを使うべきだと思う。

# Windows Server側のセットアップ

まず、Webブラウザから管理できるWindows Serverを用意する。ここでは Windows Server 1709 を使った。

## Windows Serverのインストール

... スクリーンショットを撮り忘れたので割愛。Server Coreインストールで十分。

インストールが完了したら、一旦 **直接ログインして `sconfig` コマンドからリモートデスクトップを有効にする** ことをお薦めする。というのは、Windows Admin Centerではパスワードを覚えてくれなかったり、1passwordでパスワードを上手に入力できなかったりするため。この点、リモートデスクトップ接続では確実にパスワードを覚えてくれるので安心。

また、DHCPだとIPアドレスを調べるのが面倒だったりするので、 `sconfig` コマンドで静的IPアドレスを振っておいた方が便利かもしれない。 `sconfig` コマンドはWindows Serverにプリインストールされた対話式設定ツールで、Windows Admin Centerが繋がるようになるまでの設定には重宝する。

(Windows Server Coreの初期パスワード入力ってフィールド間の移動が Enter じゃなくて 下矢印キーなのってもう直ったんだろうか。。)

## Windows Admin Centerのインストール
Server側はWindows Server 2016のServer Coreインストールで十分で、追加のセットアップは不要になっている。

クライアント側は、Windows Admin Centerのサイト( https://docs.microsoft.com/ja-jp/windows-server/manage/windows-admin-center/understand/windows-admin-center )からダウンロードしてインストールすることになる。

ここでインストールするクライアントこそWindows Admin Centerの本体で、これ自体がWindowsリモート管理のWebフロントエンドになっている。このフロントエンド自体も別のサーバにホストさせる選択肢も一応存在する。

## Firefoxの証明書セットアップ
クライアントはTLSクライアント認証で認証されるため、Firefoxをクライアントに使う場合はWindowsの証明書管理から証明書を抜き取ってインストールしてやる必要がある。これをやらないとFirefoxでは真っ白なページが表示されるだけでWeb管理画面にアクセスできない。

あとWindows Admin Centerのセットアップする証明書は自己署名証明書なので、いわゆる "セキュリティ例外を承認" も必要になる。

![SnapCrab_NoName_2018-9-24_1-18-40_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/67446/715ffbd5-5c5a-94a6-ea4b-e35787bf108c.png)

certmgr.msc を開き、 *個人* >> *証明書* >> *Windows Admin Center Client* を開き、 *詳細* >> *ファイルにコピー* で **秘密キー入りで** 証明書をファイルにエクスポートする。エクスポート時にはパスワードを設定する必要があるので適当に入れる。(暗号化.zipをメールでやりとるするようなもので、コピー用の一時的なパスワードなので *Firefoxにコピー後は忘れてかまわない* -- Firefoxに取り込まれる際に秘密鍵は一旦復号される)

![SnapCrab_NoName_2018-9-24_1-20-46_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/67446/d99c8e20-59cb-b6b1-05d6-40baab54c519.png)

Firefoxでは、 *オプション* >> *プライバシーとセキュリティ* >> ページ最下部の *証明書* から、エクスポートした証明書を *あなたの証明書* タブにインポートする。

![SnapCrab_NoName_2018-9-24_3-27-27_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/67446/85880d23-57e6-e1a0-a7c1-86d00502e522.png)

最初のアクセス時には証明書の選択画面が出るが一度選択すると以降は表示されない。

(Firefox以外のブラウザでは、Windowsの証明書管理機構をそのまま使用するため、このセットアップは不要になっている。SSLの本家Netscapeを源流とするブラウザゆえの悲しみと言える。)

## Windows Admin Centerへの追加

![SnapCrab_NoName_2018-9-24_3-14-13_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/67446/0baff47a-cd6a-b384-4d29-dc775ab66ec1.png)

... GUIだし説明不要なはず。単に *+ 追加* >> *サーバ接続を追加* でIPアドレスとパスワードを入力すれば直ぐに追加できる。

## 共有フォルダの準備

Windows Admin Centerからいわゆる `chown` に相当する操作をする方法がわからなかったので、Windows Admin Centerのファイルアップロード機能がまだ使えていない。

というわけで、共有フォルダを普通に追加する。まぁサーバーだし。Windows Admin Centerでは、この共有フォルダ設定もWebUIから行える。

![SnapCrab_NoName_2018-9-24_3-15-47_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/67446/71d7ff19-5e17-7f43-2fc5-a5c49c69dc37.png)

1. Windows Admin Center上でサーバを開き *ローカルユーザとグループ* >> *新しいユーザ* でユーザを作成する ★ ここで作成したユーザで共有フォルダにアクセスする
2. *ファイル* >> *C:* >> *新しいフォルダー* でフォルダを作成する
3. 作成したフォルダを選択し *詳細...* >> *共有* で共有設定を開く
4. 先ほど作成したユーザをRead/Writeで追加する

あとはPCから `\\<サーバ名>\<共有名>` で普通に開けば良い。

Windows Admin Centerから直接ファイルを置こうとすると

> C:\Documents and Settings\Administrator\Documents\income をアップロードできませんでした。エラー: この操作は、ロールベースのアクセス制御の設定または他のネットワークの問題によってブロックされました。

のように言われて失敗する。ここではロールベースのアクセス制御(RBAC)は特に関係ないはずで、単に書き込み権限が無いからではないかと思う。

## Windows Container / Dockerのインストール

Windows Admin Centerの機能の充実によって、公式の **実機へのインストールを想定したチュートリアルに従うことができる** 。というわけで単に https://docs.docker.com/install/windows/docker-ee/ に従えば良い。Windows Admin Centerは、Webブラウザ上から操作できるPowerShellや "プログラムの追加と削除" を提供する。

## ポートの解放

後でtcp:2375にDocker daemonを起動して接続するつもりなのでポートを解放しておく。これもWindows Admin Centerから行える。

1. サーバを開き、 *ファイアウォール* >> *着信規則* >> *新規* で適当にルールを作成する
2. *ファイアウォール規則を有効化します* を On にして保存する

簡単。

# Sonatype Nexus3 と プライベートDockerレジストリの準備

GUIで設定できるDockerレジストリとしてはNexus3が有る。Nexus3はJava製のサーバなので、Windowsでもそのまま実行することができる。

## Nexus Repository Manager 3 のインストール

特に迷うことは無いが、設定ファイルでしか設定できない項目がいくつかある。 ここでは、Nexus3を `https://stripe.local/nx/` に配置したいので、webrootを調整する必要がある。

また、HTTPSを終端するリバースプロキシとしてnginxが既にセットアップ済という前提にする。nginxにはWindows版が一応存在し http://nginx.org/en/docs/windows.html から入手できる。

1. 公式サイトからダウンロードして展開しておく https://help.sonatype.com/repomanager3/download
2. `nexus-context-path=/nx/` と書いたファイルを `sonatype-work/nexus3/etc/nexus.properties` に置く (Nexus3をサーバのルートに置くなら不要)
3. `nexus3/nexus-3.13.0-01/bin/nexus3.exe /run` でおもむろに起動する

一応、Nexus3自体にもHTTPSサーバ機能は有るが、証明書の取り廻し等を考えるとリバースプロキシ構成の方が使いやすいと思う。

**追記:** 実際はリバースproxy構成を取ってるんだし、listenするのもlocalhostだけで良いか。

```
application-host=127.0.0.1
nexus-context-path=/nx/
```

## ドメイン名の戦略

... 実はココでは前回の記事 https://qiita.com/okuoku/items/51e12289061ade156107 のものと同じnginxインスタンスを使いまわしている。このため、 **Webアプリはなるべくルートを占有しない** ように用意している。通常のWebサイトでは単純にドメインを分ければ良いが、今回はmDNSのDNS名を直接使っているのでサブドメインを取れない。

実はWindows ServerもmDNS名を直接解決することができる。つまり、Windowsにホスト名を設定していれば、 `<ホスト名>.local` は特に設定しなくても解決できる。see also https://qiita.com/maccadoo/items/48ace84f8aca030a12f1

## nginxの設定

Dockerのプロトコルの都合で、Web上の設定画面とは別に専用のポートを用意する必要がある。ここでは、 `stripe.local/nx` をNexus3のWebUI用に、 `stripe.local:5000` をDockerレジストリ用に用意する。

ここで、 `f:/serv/keys/out.cer` は自己署名証明書とする。

```
http {
    server {
        listen 443 ssl;
        server_name stripe.local;
        ssl_protocols TLSv1.2;

        ssl_certificate f:/serv/keys/out.cer;
        ssl_certificate_key f:/serv/keys/key.pem;

        location /nx {
            proxy_pass http://127.0.0.1:8081/nx; ★ localhostを使うとIPv6を引いて失敗することがある
            proxy_set_header Host $http_host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto https;
            proxy_set_header X-Forwarded-Port 443;
        }
    }

    # Following
    # https://docs.docker.com/registry/recipes/nginx/

    server {
        listen 5000 ssl; ★ ここのポート番号がdockerレジストリの名前に含まれる
        server_name stripe.local;

        ssl_protocols TLSv1.2;

        ssl_certificate f:/serv/keys/out.cer;
        ssl_certificate_key f:/serv/keys/key.pem;

        client_max_body_size 0;
        chunked_transfer_encoding on;

        location / {
            proxy_buffering off;
            proxy_connect_timeout 120s; ★ なんか手元の環境ではNexusが1分以内に応答してくれないこと
            proxy_read_timeout 1200s;      が稀にあったので延ばしている
            proxy_send_timeout 1200s;

            proxy_pass http://127.0.0.1:8082/; ★ ここのポート番号は作成するDockerレジストリに合わせる
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto https;
            proxy_set_header X-Forwarded-Port 443;
        }
    }

```

リバースプロキシとしての設定はいつも通りのものと言える。

## Dockerレジストリの作成

Nexus3 にAdminでログイン(admin:admin123)し、初期パスワードを変更してから、画面上部の歯車アイコンをクリックする。

*Repository* >> *+ Create Repository* >> *docker (hosted)* と選択し、適当な名前とポート番号でレジストリを作成する。

![SnapCrab_NoName_2018-9-24_2-13-34_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/67446/f7254265-c114-ee94-3cfb-03ac76252bb9.png)

今回はHTTPSの終端をnginxにやらせているので、Nexus3は *HTTP* でリポジトリをサーブする必要がある。

# 証明書のインストール

これで Nexus3 上にプライベートリポジトリを作成することができたが、Dockerコマンドは証明書をインストールしないと操作できない。

## Dockerデーモン自体の証明書

Windows Serverに構築したDockerデーモン自体の証明書をインストールする必要がある。Dockerの設定は `c:/programdata/docker/config/daemon.json` に置く。

```json:daemon.json
{
    "hosts": ["tcp://0.0.0.0:2375", "npipe://"],
    "tlsverify": true,
    "tlscacert": "c:\\keys\\ca.pem",
    "tlscert": "c:\\keys\\server-cert.pem",
    "tlskey": "c:\\keys\\server-key.pem",
    "storage-opts": [ "size=120GB" ]
}
```

ここでは証明書と鍵は `c:\keys` 以下に置くことにした。一旦先に作成した共有フォルダに置いてから、Windows Admin CenterのPowershellで諸々移動させる。

TLS証明書の作成方法は、検索すれば色々と出てくるので割愛。 e.g. http://docs.docker.jp/engine/security/https.html

(size=120GB はここでは関係ないが VisualStudioのdockerイメージを作るのに公式が推奨している https://docs.microsoft.com/en-us/visualstudio/install/build-tools-container サイズ。マジかよ。)

## Dockerプライベートレジストリの証明書

先にNexus3で用意したプライベートレジストリはそのまま使用しようとしても `Error response from daemon: ... : x509: certificate signed by unknown authority` のように言われて使用できない。

このためには、nginxに設定した自己署名証明書をWindows Server側に信頼させる必要がある。この作業はWindows Admin Centerから行うことができる。

1. サーバを開いて *証明書* >> *Local Machine* >> *Root* と選択する
2. *インポート* をクリックしてインポートする

![SnapCrab_NoName_2018-9-24_3-18-36_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/67446/24d04436-d15c-1183-846e-c81b4e9516e7.png)

インポートする証明書はnginxで言うところの `ssl_certificate` に設定している `.cer` ファイルになる。Firefoxに証明書をインストールした時とは異なり、パスワード付の証明書はWindows Admin Centerでは扱えないようなので、秘密鍵が証明書に含まれている場合はそれを抜いておく必要があるかもしれない。

インポートする場所は Root でなければならない。自己署名証明書では信頼されるCAが自分自身になるため、CAの証明書を置ける場所に置かないと信頼されないことになってしまう。(CAじゃない公開鍵で署名された証明書を信頼したりしないため)

# 使ってみる

適当な `docker` コマンドが使えるマシンで使ってみる。ここでは、Windows Server側のIPアドレスは `192.168.18.100` としている。

```sh:buildの例
docker --tlsverify --tlscacert=/home/oku/cert/ca.pem \
       --tlscert=/home/oku/cert/cert.pem \
       --tlskey=/home/oku/cert/key.pem \
       -H192.168.18.100:2375 \
       build -f .
```

```sh:pushの例
docker --tlsverify --tlscacert=/home/oku/cert/ca.pem \
       --tlscert=/home/oku/cert/cert.pem \
       --tlskey=/home/oku/cert/key.pem \
       -H192.168.18.100:2375 \
       push stripe.local:5000/vs2017
```

忘れがちだがdockerはクライアント/サーバ構成を持つので他所のサーバに直接接続することができる。

`push` コマンドで使うレジストリ名は `stripe.local:5000` のようにポート番号を含めることができる。このためポート 443 をDockerレジストリのために占有させる必要は無い。

# 感想

* かんたんだった(粉みかん)
* Dockerレジストリなのかリポジトリなのかがよくわからない
* Visual Studioのイメージを作るのを `time` で測りつつやったら `real    257m25.654s` だった。すげぇ。
* 作ったVisual Studioのイメージを `docker push` したら 50 GiB もあった。すげぇ。![SnapCrab_NoName_2018-9-22_23-28-27_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/67446/13b6e800-9506-f17f-0ce7-de3c813dac80.png)
 これができるくらい安定している
* やっぱりメモリもったいないし Desktop Hyper-V 環境にしてVirtualBoxを止めた方が良いかなと思った。

## Server Container の制約

VirtualBox内ではHyper-Vが使えないため、Windows Containerとしては "Server Container" とか "process分離" と呼ばれる *ホストOSのカーネルを直接使用する方式* だけがサポートされる。このため、

* Windows10のようなデスクトップOSをホストにできない
* カーネルバージョンが一致しないコンテナを動作させられない

といった制約がある。これが意外とキツく、例えば、公式ドキュメントで触れられているような `stefanscherer/dockertls-windows` https://hub.docker.com/r/stefanscherer/dockertls-windows/ もカーネルバージョン違いで使うことができない。(10GiBとかダウンロードさせられた後に使えないと解るので結構がっかりする)

この辺の制約は公式のドキュメントでも長々と説明されているが https://docs.microsoft.com/ja-jp/virtualization/windowscontainers/deploy-containers/version-compatibility 要は `1709` のような4ケタのバージョンは少くとも合わせる必要があり、確実を期するならビルド番号も合わせるべきということになっている。

Ubuntuに例えると、 `16.04` で作ったコンテナは `16.10` では使えない -- くらいの制約がある。もっとも、Windowsで作るコンテナなんて(ソフトウェアライセンスの都合で)基本的に配布できないだろうし、現実にはそこまで困らないかな。。