Docker (for Windows)と Vagrant の比較と活用事例

#はじめに
ここのところ仕事としてDocker（厳密にはDocker for Windows）を勉強してきました。それの集大成みたいな感じでついさっきチームにプレゼンをしたところです。時を同じくして、別の同僚はVagrantの勉強をして、二人でツートラック戦略で「どういう問題にどういう風にこいつらを活用する？」というあたりでまとめることになりました。まだどちらのツールに関しても初心者レベルですが、なんとなく活用するための輪郭が見えてきて、いくつか具体的な活用目標も設定されたので、Dockerお勉強のまとめとして紹介します。

#うちで決まったDockerとVagrantの住み分け

うちのチームで大体こんなもんだろとまとまった今後の戦略をざっくりと言うと：

- Dockerは「サービスの完成品をコンテナ化」して、Azureのプライベートレジストリ（Azure Container Registry)で一元的にイメージを管理し、当面は「デベロッパーの開発環境を補完する」目的で進める（詳細は後述）
- Vagrantは「開発環境をまるっとBox化」して、デベロッパーが新品のマシーンでも「すぐに開発作業が出来る環境を用意する」という目的で進める（詳細は後述）

#Docker (for Windows)でやることになったこと

## Dockerコマンドまとめ
まずはDockerのコマンドのおおまかな利用目的のおさらい。理解すると具体的な使い方が見えてきます。

- docker pull イメージをレジストリからホストにダウンロードする
- docker images ホストにあるイメージ
- docker run 任意のイメージを実行→ コンテナとして実行する（実行するたびにコンテナは増えます）
- docker stop 実行中のイメージを停止させる
- docker start 停止しているコンテナを再スタートさせる
- docker ps 実行中のコンテナをリストアップする
- docker ps -a 実行中のものも停止中のものも全てのコンテナをリストアップする
- docker attach 実行中のコンテナへコンソールをあける
- docker exec 実行中のコンテナ上で任意のプログラムを実行する
- docker info イメージの総数、コンテナの総数（実行中、停止中）、その他のDockerエンジンに関する情報を表示する
- docker rm コンテナを削除する
- docker rmi イメージを削除する
- docker login レジストリにログインする（pushするときなどに必要）
- docker push レジストリに任意にイメージをアップロードする
- docker inspect コンテナのメタデータ（IPアドレスとかマウントされたボリュームのPathを見たり）
- docker history イメージのレイヤーをみる。どんなコマンドがApplyされたかの履歴をみる
- dockerfile イメージを自動的にビルドするためのファイル
- docker build イメージをビルドする（dockerfileを処理する）
- docker volume ボリューム（マウントできるディレクトリ）の管理（リストアップ、新規作成、削除など）

大体こんな感じでしょうか。

### 今までの開発パターン

今回うちでやろうということになったのはチームが開発管理するツールやサービスのそれぞれに対応する「すぐに実行可能なイメージ」をアセンブルし、それをメンテしていくということです。

典型的なサービスの構成は、サーバAとクライエントBが両方ともコンテナ化されて、サーバAはASP.NETのRESTAPIサーバで、クライエントBはAngularjsなどで構築したSPA（シングルページアプリ）、といった感じです。（あとコンソールやAzure Web Jobなどのジョブ系のツールもあります。これらのコンテナ化の機会を見ていきますが当面はそのままです。）

デベロッパーはサーバとクライエントの両方を開発するケースもありますが、多くはサーバを開発し、クライエントを開発するという順番が一般的です。クライエント側の開発のみであっても、従来は開発者のマシンでローカルにサーバを実行できる環境を作ってから、サーバにリクエストを出しながらクライエントを開発する、というパターンが一般的です。逆に、サーバ側のデバッグをしたい時にサクっと前のバージョンのクライエントが欲しい時があります。もちろん`Git pull`で該当するコミットを得ればそれでもいいんですが、開発環境はいじらないでクライエントだけROMでいいから実行してサーバ側のメソッドを呼び出したい、という状況もあります。

##ここでDockerコンテナ化したサーバ（およびクライエント）を利用する

レジストリには今までのバージョンを含めた複数のバージョンを置きます。最新のものは「latest」というタグが付いていて、おそらくはそれを利用します。

クライエントの開発の場合、ホストにクライエント開発環境を設定する必要は出てきます。うちではnpmやらgulpやらWebpackを必要とします。それはそれで開発コストとして時間を費やすことになりますが、サーバ側に関しては`docker pull`して`docker run`で終了です。クライエントとサーバの接続はコンテナ上で実行するサーバAPIのエントリーポイントさえ分かればクライエント側から設定できます（-p オプションを使ってポートをホスト側からコンテナ側にマップして使います。ただし！、Docker for Windows は現時点では残念ながら localhost というエリアスが使えないのでホスト側のIPアドレスを使う必要があります（ipconfigしてね）。

サーバ側の開発やトラブルシューティングも同様で、サーバ側のコードをステップ実行する準備が出来ている場合、クライエントを使ってコードを実行するとしたらコンテナ化されたクライエントで事足ります。実際、クライエントを立ち上げるのが面倒で、ブラウザ拡張などを使って生のHTTPリクエストなどを生成したりすることもあります（ただしこの場合も認証用のHTTPヘッダを設定したりなど面倒な場合がおおい）。

こういったパターンで、なんと言いましょうか「松葉づえ一本モード」というか、クライエントとサーバのどちらかをコンテナで補ってしまう、そういう利用形態を考えています。

##アーキテクチャへの影響は？

そもそもDockerはマイクロサービスのenablerであります。サーバ側をよりドメイン単位で切り離してマイクロサービスとしてコンテナ化していく、そういう方向へ進んでいくような気がします。この辺りは急には変われない部分だとは思いますが、Dockerの利便性とその理解が浸透していけば、おのずとそういうアーキテクチャが支持を得るようになっていくのではないかと思います。

##プロダクション環境での利用は？

Dockerの最大の魅力の一つは、開発者が使ったコンテナをそのまま、環境ごとまとめて、プロダクション環境にデプロイできることです。さらに、複数のコンテナを実行してスケールしていくことなどもその利用形態のひとつです。実際AzureもAzure Container Serviceというサービスでもってプロダクション環境へのDocker導入の道筋をつけてます。（注：現段階では残念ながらAzure Container ServiceはLinuxコンテナしかサポートされてません。Windowsコンテナはもうじきリリースだとかなんとか）。

うちのチームはまだそこまでのコミットというかリソースがないので、当面は開発シーンでの補完的な利用から始めていこうと思っています。

#Vagrantでやることになったこと

VagrantがDockerと違うのはやはり完全な仮想マシン環境を扱うということでしょう。Dockerのコンテナ技術となんとなく区別がつきにくくて、学習・調査の前はよく見えてこなかったことですが、ここにきてVagrantの強みは、平たく言えばVMWareやHyper-Vでおなじみの仮想マシン技術による恩恵です。ただ、Vagrantが提供するprovisioningツールが、より安定した仮想マシンの活用を可能させてくれます。

## 現在起きている困ったこと

Problem Statementですが、サービスやツールによっては開発環境の設定だけで1日を費やしてしまう、といったようなことがよくあります。うちのチームではAgileの原則に基づいて、いろんなストーリーをSprintの間に担当します。ストーリーごとに異なるサービスや製品だったりするので、コンテクスト（意識の面でも開発環境の面でも）をスイッチするコストは低くはありません。

例えばとあるサービスの開発環境にはVisual Studio 2013 (TCP/IPで通信するTFSAPIのNuGetがないので仕方なく・・・）Visual Studio 2017（メイン）、VS拡張各種、npm（特定のバージョンに縛られている状況）、IISととある特定の設定、などいろいろあったりします。開発者がそれぞれに自分のマシンに環境を設定していくとなれば、オーバーヘッドは増える一方です。

また、開発用マシンを慎重したり、やむなく修理に出したり、自宅から非力なマシンで働いたり、などさまざまな状況が発生することもあります。

## ここでVagrantでBox化した環境を利用する

「Box化」というのはVagrant用語でそう言ってるっぽいのでそうしてるだけで、要はHyper-Vのイメージのことです（ちなみにOracleのVirtualBoxがデフォのようですがHyper-Vも使えます。たぶんVMWareも使えるのかな？）この環境を構築するのにVagrantfileを使います、まぁだいたいにおいて。というのもベースのイメージを作るのに、すべてVagrantfileを使ってたら、もちろん不可能ではないけど、エラーが出た時の対処などを考えると何時間も時間が必要ということである程度のベースイメージは手作業で、残りの細かい部分でバリエーションを設けたい部分ではVagrantfileを使って自動化する、という作戦で同僚は処理しました。妥当な判断でしょう。

Boxは基本「全部乗せ」です。新入りのチームメンバーが初日に読んで設定する手順とほぼ同じものをVagrantfileでスクリプト化します。すると`vagrant up`というコマンド一つで待つこと15分（これは環境によります。まぁイメージのダウンロードと仮想マシンの起動に時間がかかるということ）、すると開発環境がまるまる準備されている、というところからその日が始まることになります。

これは別に今までの仮想マシン技術でも十分可能な利用形態ですが、いままではPowerShellなどを使って自動化したりしてましたがVagrantfileというやや抽象化されたスクリプトとそれを処理するツール群・エコシステムを活用してより安定的に明示的に仮想マシンの構成とバージョン管理が出来るようになります。

## ソースコードは？

ソースに関しては、BoxにGitのレポジトリをクローンしてある状態です。とうぜんソースは時間の経過とともにDriftしていきますが、そこは通常のマシーンでも同じです。`git fetch`と`git pull`で対応できます。

#まとめ

ということで、ずっと気になっていたDockerのコンテナ技術と、よく比較されるVagrantですが、いくつかの問題の解決・状況改善に利用できそうだということが分かりました。今後はいくつかのパイロットプロジェクトを通してチームメンバーからの利便性などでのフィードバックをもとに少しずつ活用範囲を広げていく方針です。