クジラに乗って海に出た

久しぶりの投稿になりました, かどわき(ゲートサイド)です
今までとは状況が変わったので, 気持ち新たに記事を書いていこうと思います!


# はじめに
この記事では, Dockerを使ってインフラ, サーバサイド, フロントエンドを全て自分で用意し, Webサービス開発を始める環境を整備した経緯についてご紹介します.
タイトルは
　　　**クジラに乗って(Dockerを使って)**
　　　**海に出た(Webサービス開発を始めた)**
的な意味でつけました!!!!
変なタイトルつければ記事の流入増えるってばあちゃんが言ってたんですね!!!!

主な記載事項はDockerに偏っちゃうかもですが, Webサービス開発を始める上での題材として, 以下の様な人に役立つ記事にしたいと思います.

* Dockerの基本を知りたい人
* Webサービスの開発環境および動作環境を用意したい人
* フルスタックに興味がある人

多岐にわたるレイヤーの話が出てくるので, 上記以外の人にもお役に立てればと思います.

## この記事のゴール
1. インフラ, サーバサイド, フロントエンドの構成を大体理解できる
2. **コマンド一発でサービス(今回は動作環境)をデプロイできる**


# 使用した言語など
### インフラ
* OS: **Linux2.6**
* コンテナ管理: **Docker**
* DB: **MySQL**
* Cacheサーバ: **Redis**
* HTTPサーバ & リバースプロキシ: **Apache**

### サーバサイド
* 言語: **Java**
* フレームワーク: **SpringBoot**
* Webサーバ(組込): **Tomcat**

### フロントエンド
* 言語: **Javascript (ES6)**
* モジュールバンドラ: **Webpack**
* UIライブラリ: **React**
* 状態管理フレームワーク: **Redux**


# ローカル開発環境
**Mac OS X 10.11.4 (El Capitan)**

# サービスディレクトリ構成
```
MyApp
├── MyApp-client ・・・ フロントエンド
│   ├── node_modules
│   ├── package.json
│   ├── src
│   └── webpack.config.js
├── MyApp-server ・・・ サーバサイド
│   ├── Dockerfile
│   ├── build
│   ├── build.gradle
│   ├── build.sh
│   ├── gradle
│   ├── gradlew
│   ├── gradlew.bat
│   ├── run.sh
│   └── src
├── README.md
├── apache ・・・ HTTPサーバ
│   ├── Dockerfile
│   ├── bash.sh
│   ├── bundle.js
│   ├── httpd.conf
│   ├── index.html
│   └── run.sh
├── datastore ・・・ データストア
│   ├── Dockerfile
│   ├── build.sh
│   └── run.sh
├── db ・・・ データベース
│   ├── Dockerfile
│   ├── build.sh
│   ├── charset.cnf
│   └── run.sh
├── docker-compose.yml
├── redis ・・・ キャッシュサーバ
│   └── Dockerfile
├── remove_all_images.sh
├── remove_containers.sh
└── stop_containers.sh
```

# ソースコード
https://github.com/gates1de/MyApp

# 準備
## インストール
### homebrew-caskを利用する方法
(homebre-caskは便利なので入れとくといいかも)

```
$ brew install caskroom/cask/brew-cask
$ brew cask install dockertoolbox

# docker, docker-machine, docker-compose, virtualbox, kitematic(GUI tool)が入ります
```

### homebrewのみでやる方法

```
$ brew install Caskroom/cask/dockertoolbox

# 同様に docker, docker-machine, docker-compose, virtualboxが入ります
# もしvirtualboxが入っていた場合は以下を実行

$ brew install docker docker-machine docker-compose
```

### パッケージを利用する方法
https://www.docker.com/products/docker-toolbox


# インフラ
## Docker
まずはインフラ構築として, Dockerについて説明していきます.
ただし, 今更1から説明するのは冗長的なので, [この方の記事
](http://qiita.com/zembutsu)が参考になるかと思います.
このQiitaにも既に2600を超える記事に「Docker」タグがついていて(2016/06/02時点), それなりに利用されていることが伺えます.

### メリット
自分が感じている主なメリットはこんな感じです.
* 用意したい環境をすぐに構築できる(例: Nginxを利用したHTTPサーバ, Rails + unicorn, キャッシュサーバなど)
* インフラ構築をコードで自動化できる → **環境によってアプリケーションが動かないということがなくなる**
* コンピュータのリソースを隔離・制限できる → **少ないリソースでアプリケーションを動かす事ができる**
* **オーバーヘッドが少なく, インスタンスの作成や起動が早い**  
など

### コマンド要約
今回の記事で使うのは以下3つのコマンドのみです
`$ docker` : コンテナを操作するコマンド
`$ docker-machine` : コンテナを動かすための仮想マシン(VM)を操作するコマンド
`$ docker-compose` : 複数のコンテナをまとめて操作するコマンド

### Docker Machine
まずはこれでVM作りましょう!
`$ docker-machine create --driver virtualbox MyAppVM`
**はい終わり!!!**

というと不安かと思いますので,
`$ docker-machine ssh MyAppVM`
とか実行してみてください.
普段サーバにsshするのと同じようにVMで操作できるでしょう!(ログアウトするときは同様に `ctrl + d`で)

今後のコンテナ操作のために以下を実行しておいてください.

* `$ eval $(docker-machine env MyAppVM)`
dockerコマンドで利用する環境変数が設定され, コンテナの管理先VMが自動的に認識されます.

* `$ docker create network myapp-net`
あらかじめコンテナ同士を繋ぐネットワークを作っておきます.


### Dockerfile
Dockerfileはコンテナの構成内容を記述するファイルであり, この内容を元に`docker build`をおこなうと, 思い通りのコンテナイメージが出来上がります.

今回は, 各コンテナの設定ファイルで簡単に説明します.

#### serverコンテナイメージのDockerfile
``` Dockerfile:/myapp/myapp-server/Dockerfile
FROM java:8

ADD build/libs/myapp-0.0.1-SNAPSHOT.jar /opt/tomcat8/webapps/myapp-0.0.1-SNAPSHOT.jar

WORKDIR /opt/tomcat8/webapps

EXPOSE 8080

CMD ["java", "-jar", "myapp-0.0.1-SNAPSHOT.jar"]
```

| 命令 | 引数 |
|:--:|:--:|
|FROM|元となるイメージの指定|
|ADD|ファイル・ディレクトリの追加|
|WORKDIR|作業ディレクトリの指定|
|EXPOSE|他コンテナに晒すポートの指定|
|CMD|コンテナの作成後に実行するコマンド|

上記の設定をするようなケースを, ストーリーで説明してみます.

* SpringBootのWebアプリが動く環境が欲しいなぁ
* javaさえ入ってれば動くんだけどなぁ
* どうやら`java`のイメージがあればjavaが動くらしいぞ
* じゃあ`java:8`というイメージを元にコンテナを作成して, この中にSpringBootのWebアプリ(jarファイル)を置くぞ
* コンテナの◯◯ディレクトリにjarファイルを追加することにしよう
* Webアプリの起動コマンドを実行する場所はjarファイルの置いてある場所にしておこう
* 後でapacheから繋げるように, 8080ポートは晒しとくのだ
* `$ docker build -t spring-boot .` ｯﾀｰﾝ

という感じで, そのWebアプリが動く環境のイメージを元にしてファイル追加や起動コマンドなどの命令をDockerfileに書き込んでおくと, 思い通りのコンテナが出来るわけです.

「`java:8`イメージって何や!(# ﾟДﾟ)」って人には申し訳ない説明になりましたが, イメージは自分で作成する他, **DockerHub**というDocker版Githubみたいなサービスからpullしてくることができます.
今回使ったイメージは, DockerHubのjava公式イメージになります. `FROM`で指定したイメージは, 手元になければDockerHubから探して勝手にとってきます. それでもなければエラーになるでしょう.

他に以下4つのコンテナを作成するためのDockerfileを記載します
* HTTPサーバ(Apache)
* DBサーバ(MySQL)
* キャッシュサーバ(Redis)
* データストア

#### HTTPサーバ
``` Dockerfile:/myapp/apache/Dockerfile
FROM httpd

RUN mkdir -p /usr/local/apache2/htdocs/myapp/build

COPY ./httpd.conf /usr/local/apache2/conf/httpd.conf
COPY ./index.html /usr/local/apache2/htdocs/myapp/index.html
COPY ./bundle.js /usr/local/apache2/htdocs/myapp/build/bundle.js

EXPOSE 80
```
少しだけ解説すると, `RUN`はコンテナの作成前に実行される命令で, いくつでも実行できる模様. `CMD`と似ているが, `CMD`はコンテナ作成後に実行される命令で, 1回のみ実行できる模様.
この変ややこしいのですが, 基本`RUN`でディレクトリを作成したりライブラリをインストールしたりしてから, `CMD`でコンテナ内部で動かしたいツールやソフトの実行命令を指定するのが良さそうです.
~~他にも`ENTRYPOINT`とかあるけど今回はいっかなぁ~~

`COPY`はその通り, 指定元ファイルを指定先ファイルへコピーする命令です.
`ADD`はファイルを追加しつつzipファイルとか展開してくれるのと, リモートからファイルも追加できるらしいので, `ADD`の方が多機能っぽいです. でも`COPY`ですむなら`COPY`を使えばいいと思います.

ちなみに, `index.html`と`bundle.js`はフロントエンドの開発で生成したファイルです.
`Webpack`を使うと`js`や`css`を, よしなに処理してひとまとめにしてくれます.

#### DBサーバ
``` Dockerfile:/myapp/db/Dockerfile
FROM mysql

COPY charset.cnf /etc/mysql/conf.d/charset.cnf

EXPOSE 3306
```
公式のMySQLイメージ利用したのですが, 日本語環境がアレなので`charset.cnf`を追加しました.
こんな感じ.

``` charset.cnf
[mysqld]
character-set-server=utf8
[client]
default-character-set=utf8
```

#### キャッシュサーバ
``` Dockerfile:/myapp/redis/Dockerfile
FROM redis

EXPOSE 6379
```
~~特に使わなかったので公式イメージそのままコンテナにするという暴挙~~


#### データストア
``` Dockerfile:/myapp/datastore/Dockerfile
FROM busybox

VOLUME /var/lib/mysql
```
MySQLのデータを永続化するために, 必要最低限のLinuxコマンドだけ入ってるコンテナを作って, ホストマシンの特定のディレクトリをボリュームとしてマウントする役割を与えました.
そうしておけば, サービスのコンテナを全部潰してもデータは消えずにすむはずです.
ログファイルなどもこのデータストアコンテナを利用して保存するのが良いようです.

以上5つのコンテナでWebサービスを構成します.

### Docker Compose
今回の中心になりうるツールの1つで, これがあることでコマンド1発デプロイが実現できるのです.
何故かと言うと, この設定ファイルで上記のDockerfileを元にコンテナの一斉ビルドと起動をおこなえるようになるからです.
自分が説明するより 'docker compose' と検索して一番上に出てくる[この記事](http://qiita.com/y_hokkey/items/d51e69c6ff4015e85fce)が非常にわかりやすそうです.

docker composeでは, `docker-compose.yml`というファイルを作成してコンテナの構成管理を設定します.
以下, 今回の記事で利用する`docker-compose.yml`の内容とざっくりとした解説をおこないます.

``` yml:/myapp/docker-compose.yml
version: '2'   # ① 
services:   # ②
  server:   # ③
    container_name: spring-boot   # ④
    build: ./myapp-server   # ⑤
    ports:   # ⑥
      - '8080:8080'
    environment:   # ⑦
      REDIS_URL: redis://redis:6379
    links:   # ⑧
      - db
      - redis
    network_mode: 'myapp-net'   # ⑧
  db:
    container_name: mysql
    build: ./db
    environment: # ⑨
      MYSQL_DATABASE: myapp_db
      MYSQL_ROOT_PASSWORD: root
      MYSQL_USER: myapp
      MYSQL_PASSWORD: password
    ports:
      - '3306:3306'
    volumes_from:   # ⑩
      - datastore
    network_mode: 'myapp-net'
  datastore:
    container_name: datastore
    build: ./datastore
    network_mode: 'myapp-net'
  redis:
    container_name: redis
    build: ./redis
    ports:
      - '6379:6379'
    network_mode: 'myapp-net'
  apache:
    container_name: apache
    build: ./apache
    ports:
      - '80:80'
    links:
      - server
    network_mode: 'myapp-net'
```
| 番号 | key | 解説 |
|:-----------|:------------:|:------------|
|①|version|ymlのバージョン(サービスのバージョンじゃないです)|
|②|services|トップレベルオブジェクトの1つで, この階層に各コンテナの設定情報を書いていきます. <br> トップレベルオブジェクトには他に`volumes`と`networks`が定義できます(今回は省略).|
|③|server|ここでは`server`としていますがkeyは何でもよいです. <br> ただし, Webサービスとして何の役割を持つコンテナなのかが分かるkeyにしたほうが良いでしょう. <br> <br> 上記の例で言えば, `docker-compose.yml` が置いてあるディレクトリの名前が **MyApp** , keyが **server** なので, 作成されるコンテナのイメージ名が **myapp_server** となります. <br> これなら, **MyAppサービスのサーバサイドコンテナ**だと一目で分かるかと思います.|
|④|container_name|そのままの意味でコンテナ名となります. 人にもよりますが, 主にそのコンテナで作動しているアプリやソフト, ツールの名前にしてます. <br> (`spring-boot`, `apache`, `mysql`など)|
|⑤|build|Dockerfileを元に, コンテナをビルドするための設定です. Dockerfileが置いてあるディレクトリを指定します.|
|⑥|ports|公開するポートを指定します. ハッシュのvalueとしてシーケンス(配列形式)を指定するため, 以下のように複数のポートを公開することが可能です. <br>`ports:` <br> `- "80":"80"` <br> `- "8080" : "8080"`|
|⑦|environment|コンテナ内部の環境変数を追加する設定です.|
|⑧|links|他のコンテナとのリンク設定です. これを設定しておくと, コンテナ名がホストネームとなり, 名前解決が出来るようになります. <br> 上の例で言うと, `spring-boot`コンテナと`db`コンテナにはIPアドレスが自動的に振られているので(`172.20.0.4`と`172.20.0.3`), コンテナが起動した後にしかDB接続先がわからず, わざわざ`spring-boot`コンテナでDB接続設定を書き換えなければなりません. <br> ダルい上に今回は`spring-boot`をjarファイルとして動かしていることもあり, ソースコードは書き換えられません. <br> そこで`links`を使うと, `mysql`という名前のホストネームを`spring-boot`のDB接続設定に書いておくことで, 自動的にDBの接続先は`172.20.0.3`であると判断してくれます.|
|⑨|environment|コンテナ内の環境変数を設定します. <br> 今回のケースではMySQLの環境変数を正しく設定すると, 自動的にDBとユーザを作成してくれます. <br> この辺の環境変数設定はコンテナによりけりな印象なので, DockerHubの公式イメージを見てみると良いかもしれません. |
|⑩|network_mode|作成したコンテナ間ネットワークの設定です. <br> 基本的にサービス全体を1つのネットワークで動かす想定なので, コンテナを同一ネットワークで繋ぐために全てのコンテナ設定でこの記述を追加します. <br> 複数追加できるので, 別のネットワークを作って繋ぎたいコンテナ同士を繋ぐのも良いでしょう. **※1**|

**そしてついに! ここであのコマンドを打つのです**
`$ docker-compose up`

必要なイメージをすべてpullしてきて, Dockerfileの設定を元に全コンテナをビルド&起動します.
これでサーバサイド・フロントエンド・HTTPサーバ・DBサーバ・キャッシュサーバがDockerで作られたインフラ上で動作し, 同一ネットワークで繋がれたサービスが出来上がるのです.

↑の方にGithubのリポジトリへのリンクを貼っているので, `docker`やら`docker-machine`やらをいれたなら, `README`の`Get Started`通りにやってみてください!

多分動くと思います(動かなかったらコメントにお願いします!!)

**※1**
`docker-compose`でコンテナを立ち上げる時に, 今回のようにこだわりなく全て同じネットワークで繋ぎたい場合は, わざわざ`$ docker create network hogehoge`でネットワークを作って`network_mode`を全てに指定しなくても, デフォルトでネットワークが作成されます.
リポジトリに上がっているものに関しては`network_mode`を削除しています.


# おわりに
ちょっと文章が多くなってしまったのと, 省略してしまったところは申し訳ないですorz

とにかく, `Docker`はどんなエンジニアにとっても便利なツールになるはずです.
モバイルエンジニアも簡単なAPI作って連携してみたり, 学生エンジニアで一つのレイヤーを専門にしてしまっている人でもWebサービス全体を学べる環境が作れたり, 幅広く役に立ちます.
また, 会社で業務をおこなっている人でも新規サービスを提案するにあたり, モックアプリなどを何人かで爆速開発したいと思った時に, この環境さえあればレイヤーの切り分けを簡単にできて, デプロイも簡単にできるはずです.

使って損はないと思うので是非試してみてください! ♪〜(´ε｀ )


また, この後数週間にわたってこの記事の中で実装したフロントエンドやらサーバサイドやらについて書くつもりです〜

それでは長文失礼致しました!

---
p.s. ここが分からない!といったことがあれば質問受け付けてます!!

### 子記事
1. [Spring Bootで作ったRESTful APIをコンテナにして動かす](http://qiita.com/gates1de/items/5ac83f48212580e6c7fd)