Docker+Flaskによるお手軽Webアプリ開発

## Dockerについて
Dockerの仕組みそのものについては深くは触れません。私自身があまりよく理解してないですからね！

簡単に私にとってのDockerとは、__使い捨ての仮想PC__が簡単に手に入るツールみたいな物です。

例えば「まっさらなUbuntuPCをください」と言えばまっさらなUbuntuPCが手に入る、例えば「〇〇がセットアップ済みのPCをください」と言えば〇〇がすぐに使える状態で手に入る、といった具合です。

今回の場合でいうと、「Python3+Flaskが入ってるPCをください」という訳ですね。

このとき、Docker公式のPC置き場([Docker Hub](https://hub.docker.com/))を探しに行き、ちょうど良いPCを探してももちろん良いのですが、せっかくなのでまっさらなUbuntuを貰って、自分でカスタマイズ（Python3+Flaskをインストール）して使うことにしましょう。

ちなみに、ここで言う「〇〇なPCください」の「〇〇なPC」、すなわちその__PCの仕様__をDocker用語では__イメージ__と呼び、その仕様に基づいて作られた__具体的なPC__のことを__コンテナ__と呼びます。（厳密には全然違いますが、まずは感覚を掴むためにざっくりとした理解で話を進めます）

## Dockerのインストール
WindowsやMacOSの場合は、Docker公式HPからインストーラーをダウンロードして「次へ」「次へ」と進めれば基本的に問題ありません。

[Docker for Windows](https://store.docker.com/editions/community/docker-ce-desktop-windows)
[Docker for Mac](https://store.docker.com/editions/community/docker-ce-desktop-mac)


UbuntuやCentOSなどLinuxに入れる場合は手順が多少異なりますが、基本的には公式のインストールガイドに則って進めていけば問題ありません。

[Install Docker CE](https://docs.docker.com/install/linux/docker-ce/ubuntu/#install-docker-ce)

インストールが完了したらひとまずPCを再起動し、ターミナルを立ち上げ下記のコマンドでエラーが出なければインストール完了です。

```
$ docker -v
Docker version 18.03.0-ce, build 0520e24
```


## いざ環境構築
### ベースとなるイメージを用意
インストールが完了したら、早速今回の開発環境のベースとなる「まっさらなUbuntuPC」を用意します。

始めに現在PCにあるイメージを確認します。

```
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
```

上記のように、まだUbuntuのイメージがない場合は

```
$ docker pull ubuntu:latest
```

によりイメージを用意（DockerHubからダウンロード）します。


### Python3+Flaskをセットアップ
ベースとなるイメージが取得できたら、実際に必要な環境をセットアップしていきます。

セットアップの方法には大まかに言って２種類あります。

1. ベースとなるイメージを基にPCを立ち上げて（＝コンテナを作って）、そこにインストールする方法
1. PCの仕様書（＝イメージ）に所望の仕様を書き加え、新しいイメージを作る方法

どちらの方法でも問題ないと言えば問題ないのですが、Dockerのコンセプトの一つである__環境の使い捨て性(?)__を考えると、「1. 」のような一時的にしか使われないコンテナ内にインストールする方法はあまり推奨されません。なので、ここでは「2. 」の方法で環境構築を行います。

ここで、「2. 」を行うにもまた２種類の方法があります。

- 2.1. ベースとなるイメージを基にPCを立ち上げ（＝コンテナを作って）色々とセットアップした後、そのコンテナを新しいイメージとして保存する方法
- 2.2. 仕様書（＝セットアップ手順書）を新たに書き上げ、それに基づき構築する方法

なんとも歯切れの悪い説明になってしまいましたが、実際に行なっている様子を見ていただければ感覚を掴んでもらえると思います。

またしても、上記どちらの方法でも問題ないのですが、「2.2. 」の方法には「手順書を残せる」というメリットがあります。もちろん「2.1. 」でも手順を厳密にメモしておけば問題ないのですが、手順書という形で残っていると別の人がそのイメージを使う際にそれがどのようにして作られた物なのかが明確になり、変更や再現が容易であるといったメリットがあります。そのため、ここでは「2.2. 」の方法を用いて環境を構築します。

さて、前置きが長くなりましたが、いよいよ環境構築に入ります。と言っても非常に簡単で、下記のようなセットアップ手順書を書いて、Dockerにビルドしてもらうだけです。

```sh
$ vi Dockerfile
```

```dockerfile
FROM ubuntu:latest

RUN apt-get update
RUN apt-get install python3 python3-pip -y

RUN pip3 install flask
```

これで新しいイメージの仕様書ができました。説明が逆転してしまいましたが、このようセットアップ手順書のことをDocker用語で__Dockerfile__と呼びます。

ここでは、セットアップに用いるコマンドとして`RUN`くらいしか使っていませんが他にもいろいろありますので、興味があればぜひ[調べて](http://docs.docker.jp/engine/articles/dockerfile_best-practice.html)みてください。中でも`ENV`や`EXPOSE`やよく見かけるので覚えておいて損はないかと思います。

あとは下記のコマンドでビルドして完了です。

```sh
$ docker build . -t hoge/fuga:1.0
```

初めてビルドするときは長々と色々なログが出力されます。気長に待って、

```sh
Successfully tagged hoge/fuga:1.0
```

と出たら完了です。

ちなみに`hoge/fuga`の部分は、新しいイメージの名前になるので、お好きな名前をつけましょう。`:1.0`の部分は、イメージのタグと呼ばれる部分で、主にバージョン名をつける場合が多いようです。

完了したら、下記のコマンドで新しく作ったイメージを確認してみましょう。

```sh
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hoge/fuga           1.0                 e6a57212b2fa        6 days ago          442MB
```

表示されるリストの中に、先ほど作ったイメージがあることを確認できれば、環境の準備が整ったことになります。


## 動作確認・コンテナの立ち上げ
イメージの作成が完了した所で、早速作ったイメージを元にコンテナを起動してみましょう。

```sh
$ docker run -it hoge/fuga:1.0 /bin/bash
root@baadc7ca9bdd:/#
```

上記のようにユーザー名@ホスト名が変われば、無事コンテナを起動しログインすることができたということになります。

ログインした状態で、

```sh
$ python3
Python 3.5.2 (default, Nov 23 2017, 16:37:01) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 
```

となれば、無事Python3のインストールが出来ているということになります。

以上の確認が終わったら、`exit`してコンテナからログアウトしましょう。

コンテナはログアウトと同時に自動的に停止（シャットダウン）されます。起動中のコンテナや停止中のコンテナは、下記コマンドで確認が出来ます。

```sh
$ docker ps -a
```

停止中のコンテナにログインするには、まず`docker start <コンテナ名>`で起動し、`docker exec -it <コンテナ名> /bin/bash`でログインすることが出来ます。

コンテナは`docker run`するたびに新しく作られますので、気付いたら停止中の不要なコンテナが山ほどたまっている、なんてことが良くあります。なので、`docker rm <コンテナ名>`などでこまめに消すか、もしくは`docker run`の際に`--rm`オプションをつけてログアウトと同時に自動的に消えるようにすると良いですね。


## Webアプリの準備
環境の準備が整った所で、Webアプリを動かしてみましょう。
まずFlaskにおけるHello world的なサンプルアプリを用意します。

```sh

$ vi app.py
```

```py
from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return "Hello world!!"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
```

アプリができたら、次に先ほどセットアップしたイメージを基にコンテナを起動し、コンテナ内にこのファイルを取り込む（持ち込む？）工程が必要です。この方法としては、FTPで送る等まで含めると様々ありますが、開発の用途で使う場合、コンテナ起動時に必要なファイルが入ったフォルダを__マウントさせる__という方法がおすすめです。

この方法は、「ローカルのとあるフォルダと、コンテナのとあるフォルダを__リンク__させる」とでも言えば良いでしょうか。

実際にやってみましょう。まずはマウントする用のフォルダを作り、そこの先ほどの`app.py`を移動します。

```sh
$ mkdir vol
$ mv app.py vol/
```

この時点で、作業ディレクトリはこんな状態になっているかと思います。

```
.
├── Dockerfile
└── vol
    └── app.py
```

この状態から、コンテナを立ち上げる際に下記の通りオプションを付けて起動してみます。

```
$ docker run -it -p 5000:5000 -v $(pwd)/vol:/home hoge/fuga:1.0 /bin/bash
```

ここの`-v`の部分の、`：`を挟んだ左側がホスト側のフォルダ、右側がコンテナ内のフォルダを指定しています。
コンテナにログインできたところで、マウントしたフォルダに移動してみて、無事`app.py`の存在が確認できれば、マウントが成功しているということになります。


## アプリの起動
ここまできたら最後、アプリを起動してみて、アクセスできるかどうかを確認してみましょう。

```sh
$ python3 app.py
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
```

起動ができたら、適当なWebブラウザで`http://localhost:5000/`にアクセスしてみましょう。`Hello world!!`と表示されたページが表示されれば、無事完了です。

最後、試しにホスト側にある`app.py`の中のHello world!!の部分を変えてWebページを更新すると、Webページ上の表示が変わっているかと思います。これは、ホスト側のファイルとコンテナ内のファイルが同期しているから、と言うことになります。便利ですね。


<br>
以上で、Webアプリ開発環境の構築は終了です。
ありがとうございました。