Docker始めました

作ったツール類をコンテナにまとめてしまえばいろいろ便利と耳にしたので、以前から気になっていたDockerを使い始めることにしました。
今回は、pythonのflaskで作ったwebサーバをコンテナ化し、コンテナの起動・停止だけでwebサーバを管理できるようにしてみようと思います。

使用した環境はこちら

```
$ cat /etc/redhat-release 
Red Hat Enterprise Linux release 8.2 (Ootpa)
$ docker --version
Docker version 19.03.12, build 48a66213fe
```

※ 余談ですが、CentOS 8でDockerをインストールしようとすると `containerd.io` が無いぞって言われることがあるようです。私の場合は[こちら](https://vexpose.blog/2020/04/02/installation-of-docker-fails-on-centos-8-with-error-package-containerd-io-1-2-10-3-2-el7-x86-64-is-excluded/)で無事解決できましたので参考までに。


## Dockerおさらい

まずはDockerの使い方（コンテナの作り方）について簡単に整理します。Dockerコンテナの作り方は次の4ステップ。

1. ベースとなるイメージをpull
1. Dockerfile作成
1. Dockerイメージのビルド
1. イメージからDockerコンテナの生成・起動

個人的に、最初はDockerfileを作成するところが一番よくわからないところだと思います。なので、まずは第一段階としてpythonの簡単なスクリプトをコンテナ内で動かすところまで作り、第二段階としてflask製webサーバを動かすところまで改良していきます。


## コンテナ内でpythonスクリプトの実行

第一段階として、コンテナ内で次の `myapp.py` を実行するところまで作ります。

```myapp.py
print("hello Docker!")
```


### ベースとなるイメージをpull

最初にpython3系の実行環境となるイメージを用意します。Docker Hubに登録されているpython3系のイメージを検索。

```
$ sudo docker search python3
NAME                                                                    DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED
rackspacedot/python37                                                                                                   11                                      
sellpy/python3-jupyter-sklearn                                          python3-jupyter-sklearn                         5                                       [OK]
openwhisk/python3action                                                 Apache OpenWhisk runtime for Python 3 Actions   5                                       
sellpy/python3-jupyter-sklearn-java                                     python3-jupyter-sklearn-java                    2                                       [OK]
quoinedev/python3.6-pandas-alpine                                       Python 3.7 on alpine with numpy and pandas i…   2                                       
（略）
```

今回はSTARSが最も多い `rackspacedot/python37` を使用します。これをpullしましょう。

```
$ sudo docker pull rackspacedot/python37
```

`rackspacedot/python37` もDockerイメージなので、pullした後は `docker images` で確認できます。

```
$ sudo docker images
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
rackspacedot/python37   latest              3d51361ee118        7 weeks ago         1.06GB
```

このイメージがあればコンテナ内でpythonが使えるようになるので、これをベースとしてその他の必要な部分（スクリプトの配置など）を追加すればOKという感じです。


### Dockerfile作成

次に、 `Dockerfile` という名前のファイルを作成します。

```Dockerfile
FROM rackspacedot/python37

WORKDIR /usr/local/script
COPY myapp.py .

CMD python myapp.py
```

簡単に説明しますと、

- `FROM rackspacedot/python37`
    - ベースとなるイメージを指定する部分です。先ほどpullしたイメージを指定します。
- `WORKDIR /usr/local/script`
    - **コンテナ内での**作業ディレクトリを指定します。今自分がいるディレクトリと違っていても問題ありません。
- `COPY myapp.py .`
    - myapp.pyをコンテナ内の第二引数で指定したディレクトリにコピーします。今回は `.` で指定しているので、 `WORKDIR` で指定したディレクトリにコピーされます。
- `CMD python myapp.py`
    - コンテナ起動時に実行するコマンドです。

ここまでで用意したファイルは次の通り。

```
$ ls -l
total 8
-rw-r--r--. 1 user developer 146 Sep 11 16:39 Dockerfile
-rw-r--r--. 1 user developer 173 Sep 11 17:04 myapp.py
```


### Dockerイメージのビルド・コンテナ起動

Dockerfileを作成したので、イメージのビルド→コンテナ起動をしていきます。
まずはイメージのビルドから。

```
$ sudo docker build -t myapp:test .
```

`-t 名前:タグ` はイメージの名前とタグを設定します（スクリプト名と違っていてもOKです）。 `.` はDockerfileがあるディレクトリのパスを指定します。
`Successfully built ハッシュ値` と出ればイメージが生成されたので、 `docker images` で確認できるようになります。

```
$ sudo docker images
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
myapp                   test                e577339515a7        10 seconds ago      1.06GB
rackspacedot/python37   latest              3d51361ee118        7 weeks ago         1.06GB
```

イメージができたので、コンテナを作成・起動します。

```
$ sudo docker run myapp:test
hello Docker!
```

無事コンテナでpythonスクリプトを実行できました。
ここで起動したコンテナは残るので、再度同じコマンドで実行することができます。また、 `docker ps -a` で作成したコンテナを確認することができます。


## コンテナ内でflask製サーバの実行

さて、コンテナ内でpythonスクリプトを実行できるようになったので、第二段階としてflaskでサーバを作成していきましょう。
`myapp.py` を次のように書き換えます。

```python
from flask import Flask

app = Flask("myapp")

@app.route("/")
def main():
    return "hello Docker!!"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080)
```

一応先ほどと変化がわかるように `!` の数を2個にしました。
これをコンテナ内でwebサーバとして使える状態にしましょう。


### Dockerファイル修正

flaskを使用するので、コンテナ内にflaskをインストールする必要があります。 `rackspacedot/python37` にはデフォルトでpipが入っていますので、イメージのビルド時に `pip install` する部分をDockerfileに追加します。

```Dockerfile
FROM rackspacedot/python37

RUN pip install flask

RUN useradd docker
USER docker

WORKDIR /usr/local/script
COPY myapp.py .

CMD python myapp.py
```

追加した分を説明すると、

- `RUN pip install flask`
    - コンテナ内にflaskをインストールします。
- `RUN useradd docker / USER docker`
    - `docker` というユーザーを追加し、その後の操作を `docker` ユーザーとして実行します。
    - 今回は無くても動作しますが、セキュリティ上の理由で追加しています。


### Dockerイメージのビルド・コンテナ起動

ファイルを書き換えたので、イメージを再ビルド→コンテナ起動します。
まずは再ビルドから。

```
$ sudo docker build -t myapp .
```

今回はタグを省略したので、自動的に `latest` というタグが割り当てられます。 `docker images` で確認してみましょう。

```
$ sudo docker images
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
myapp                   latest              6c747be5d597        3 seconds ago       1.06GB
myapp                   test                e577339515a7        50 minutes ago      1.06GB
rackspacedot/python37   latest              3d51361ee118        7 weeks ago         1.06GB
```

それではコンテナを起動しましょう。webサーバなので、オプションを追加しています。

```
$ sudo docker run -d -p 8080:8080 myapp
```

ポート番号ですが、 `-p 受付ポート番号:コンテナ側の番号` の書き方になります。受付ポート番号は何番でも良いのですが、コンテナ側の番号はコンテナ内の `myapp.py` で指定した番号（今回の場合は8080）にします。

問題なく起動していれば、 `docker ps` で起動中のコンテナを確認することができます。

```
$ sudo docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
00d1ab6fc24a        myapp               "/bin/sh -c 'python …"   3 seconds ago       Up 2 seconds        0.0.0.0:8080->8080/tcp   thirsty_mccarthy
```

実際にアクセスしてみましょう。

```
$ curl http://localhost:8080
hello Docker!!
```

いいですね。これでwebサーバのコンテナ化完了です。
コンテナを停止したい場合は

```
$ sudo docker stop thirsty_mccarthy
```

停止後に再度起動したい場合は

```
$ sudo docker start thirsty_mccarthy
```

`thirsty_mccarthy` の部分はコンテナ名（ `docker ps` 実行時の `NAME` に表示されるもの）を指定してください。`docker start` で起動すると、自動的に `docker run` の時に指定したポートで実行してくれます。
また、コンテナ化しておくと作ったものを他の環境で実行する時もイメージのビルド→コンテナ起動ですぐに動かせるので、非常に便利ですね。

今回は以上になります。Docker Composeで複数コンテナをコマンド一つで実行できるようになればさらに幅が広がるので、そのうちこれも勉強しようかなと思います。