Docker コンテナ構築入門

こんにちは。
Dockerについて学習したので、アウトプットも兼ねて__初学者ならではの視点__で記事を書いてみようと思います。

筆者が初めてDockerについて記事を読んで学習した際、__「で、結局何ができるんだ？」__という疑問が残ってしまいましたが、実際にコマンドを叩いてコンテナ構築をしてみると、何が便利なのかイメージができたので実践してみることをお勧めします。

なお、仕組み等の詳細説明は割愛します。

間違った部分等ありましたらご指摘いただけると幸いです。

## Dockerの特徴
コンテナは、__Dockerfile__というソースコードから構築します。(このようにコードベースでインフラ構築を定義する考え方を_Infrastructure as Code_といいます。)
これによって、__開発したアプリケーションが別環境だと動かない__といった環境差異を防ぐことができます。他にも、

* Dockerfileを(Github等で)共有することで、どこでも誰でも同じ環境が作れる。
* 環境を壊してしまった場合などに、環境の再構築が容易。

などといった特徴があります。

## コンテナ構築の流れ
Dockerfileからイメージを作り、イメージからコンテナを立ち上げるという流れです。
![コンテナ構築イメージ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/453628/870c2e95-d0ca-ddcd-e67f-abae77d1a057.png)
`イメージ`とは ... コンテナの構成をまとめたもので、コンテナを構築するベースのようなものです。DockerHubには、`wordpress, mysql, python`などがイメージファイルとして公開されており、それらを自分で組み合わせたイメージを作成します。

[Docker Hub 概要 — Docker-docs-ja 19.03 ドキュメント](https://docs.docker.jp/docker-hub/overview.htmlm "Docker Hub 概要 — Docker-docs-ja 19.03 ドキュメント")

なお、Dockerではコンテナ1つにアプリケーション1つが推奨されているので、例えば`MySQL` + `nginx` + `flask`など、複数のアプリケーションを組み合わせているサービスををDockerで動かす場合は、利用するアプリケーション分コンテナを構築し、docker-composeやKubernatesなどの__コンテナオーケストレーションサービス__を利用する必要があります。

# ローカル環境にコンテナを構築
今回は、コンテナ環境でPythonのwebフレームワークである、flaskを動かしてみようと思います。
なお、__ホスト環境にPythonやFlaskをインストールする必要はありません。__

ローカル環境にDockerがインストールされていることが前提です。

[Get Docker | Docker Documentation](https://docs.docker.com/get-docker/ "Get Docker | Docker Documentation")

## 前提ファイルの準備
Dockerfileとpythonファイルのmain.pyを同じディレクトリに用意します。

>./
>├ main.py
>└ Dockerfile

```python:main.py
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello from Docker!'

if __name__ == '__main__':
    app.debug = True
    app.run(host='0.0.0.0')
```

pythonファイルの内容は、ルートにアクセスがあったらHello from Dockerと表示する単純なflaskプログラムです。

```dockerfile:Dockerfile
FROM python:latest

RUN mkdir /src && \
    pip install flask

COPY main.py /src

CMD ["python", "/src/main.py"]
# "python /src/main.py"と同じ
```
* `FROM` ... 作成するDockerイメージの__ベースイメージを指定__。DockerHubからダウンロードされる。pythonの最新バージョンを指定。
* `RUN` ... __イメージのビルド時__に実行されるコマンド。`src`というディレクトリを作成するコマンドと、pipを使ってflaskをインストールするコマンドを指定。
* `COPY` ... ホストマシンのファイルやディレクトリをコンテナ内にコピーする。先程用意した`main.py`をコンテナ内の`src`ディレクトリにコピーする。
* `CMD` ... __コンテナ起動時__に実行されるコマンドを指定。スペース区切りで配列化した形式。`python /src/main.py`でpythonファイルを実行している。

## イメージのビルド

```bash
$ docker image build -t tutorial/docker-flask:latest .
 ```
`-t`以降は、`<account name>/<image name>:<tag>`の形式で指定します。

* `account name` ... 名前衝突を避けるための__名前空間__
* `image name` ... __イメージ名__
* `tag` ... __バージョン__や__ラベル__

末尾にDockerfileのあるディレクトリを指定します。(今回はカレントを指定)


このように表示されればビルドが完了です。

```shell-session
Successfully built 03bb880c97ac
Successfully tagged tutorial/docker-flask:latest
```
`docker image ls`コマンドで、イメージのリストが確認できます。

```bash
$ docker image ls
REPOSITORY              TAG       IMAGE ID       CREATED         SIZE
tutorial/docker-flask   latest    03bb880c97ac   7 minutes ago   895MB
```

## コンテナ起動

最後に、作成したイメージからコンテナを起動します。

```bash
$ docker container run -d -p 8080:5000 tutorial/docker-flask:latest
```
`-d`はバックグラウンドで実行するオプションです。

ここで__重要__なのが、`-p 8080:5000`の部分です。
これは__ポートフォワーディング__という設定で、ホストのポートとコンテナ内のポートを接続する設定です。
flaskでは、デフォルトで`5000`番ポートを使用しているため、この記述で__8080番ポートへのアクセスを、5000番ポートへ転送__しています。

## アクセスしてみる

![localhost](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/453628/9267cff3-f707-b021-7563-64c88a028f72.png)

localhostの8080番ポートにアクセスすると、無事`Hello from Docker!`と表示され、コンテナ内でpythonが動いていることが確認できます。

# 後片付け

## コンテナの停止

`$ docker ps -a`で起動しているコンテナを確認します。
`-a`を指定することで、停止中のコンテナも表示されます。

```bash
$ docker ps -a
CONTAINER ID   IMAGE                          COMMAND                 CREATED          STATUS          PORTS
      NAMES
8b15d95aa3b2   tutorial/docker-flask:latest   "python /src/main.py"   10 minutes ago   Up 10 minutes   0.0.0.0:8080->5000/tcp   sad_blackwell
```

`$ docker stop <CONTAINER ID or NAMES>`でコンテナが停止します。

```bash
$ docker stop 8b15d95aa3b2
```

もしくは

```bash
$ docker stop sad_blackwell
```
`$ docker ps -a`で、`STATUS`が`Exited`となっており、停止されているのが確認できます。

```bash
$ docker ps -a
CONTAINER ID   IMAGE                          COMMAND                 CREATED          STATUS                      PORTS     NAMES
8b15d95aa3b2   tutorial/docker-flask:latest   "python /src/main.py"   15 minutes ago   Exited (0) 3 minutes ago              sad_blackwell
```

コンテナを再度起動する際も、同様に`$ docker start <CONTAINER ID もしくは NAMES>`でコンテナを起動することができます。

## コンテナ削除(任意)

停止のやり方と同様に、`$ docker ps -a`でCONTAINER IDもしくはNAMESの欄を確認します。

```bash
$ docker ps -a
CONTAINER ID   IMAGE                          COMMAND                 CREATED          STATUS                      PORTS                  NAMES
8b15d95aa3b2   tutorial/docker-flask:latest   "python /src/main.py"   23 minutes ago   Up 6 minutes                0.0.0.0:8080->5000/tcp   sad_blackwell
```

`$ docker rm <CONTAINER ID or NAMES>`でコンテナの削除ができます。

```bash
$ docker rm 8b15d95aa3b2
```

もしくは

```bash
$ docker rm sad_blackwell
```

`$ docker ps -a`で削除できたか確認する

```bash
$ docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
```

無事消えていることが確認できました。

## イメージ削除(任意)

`$ docker images`で保存されているのイメージの確認をします。

```bash
$ docker images
REPOSITORY              TAG       IMAGE ID       CREATED          SIZE
tutorial/docker-flask   latest    03bb880c97ac   39 minutes ago   895MB
```
イメージの削除は、`$ docker rmi <REPOSITORY or IMAGE ID>`で行います。

```bash
$ docker rmi tutorial/docker-flask
```

もしくは

```bash
$ docker rmi 03bb880c97ac
```

`$ docker images`で削除の確認

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

イメージが削除されていることが確認できました。