Docker初心者でも分かったDockerfileの作り方(API環境の共有)

flaskでweb APIを作ったものの、Dockerイメージ(1.4GBくらい)でその環境を渡したら冷ややかな目で見られました。
そんな訳でこの記事では、Python環境をDockerfileにして渡す方法を書き殴ります。

### 目次
1. Dockerとは？
2. ディレクトリ構造
3. .dockerignoreについて
4. requirements.txtについて
5. Dockerfileの心
6. ビルド
7. ラン。コンテナを作る
8. コンテナの起動
9. 最後に

## Dockerとは？
linux系のOSを簡単に共有したりやりたい放題環境を荒らしたりできる便利なサービスです。OSレベルの仮想環境を扱うことのできるソフトと考えて下さい。[インストール方法はこちら](https://qiita.com/tomp/items/50081b99df7963462488)
dockerイメージという1~2GBくらいのOSの素をdocker run ~ とすることでdockerコンテナができます。コンテナが仮想環境 (OS) みたいなものです。コンテナができればもう"""勝ち"""なのです。
ではこの環境をどうやって共有しましょう？取り敢えず、dockerイメージを共有すれば相手側でもrunしてコンテナを立ち上げることができますね。まあ、そうすると環境のパッケージによっては大きな容量になって不便ですね... (執筆当時はdockerhubを知りませんでした)
***そこで、Dockerfileを利用します***
Dockerfileとは、dockerイメージの設計図みたいなテキストのファイルで容量を軽っかっるなやつです。例としてはこんなんです⇓

```Dockerfile
FROM ubuntu:18.04

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

# setup directory
RUN mkdir /api
ADD . /api
WORKDIR /api

# install py
RUN pip3 install -r requirements.txt
```
これで、Python3系と必要なパッケージが揃ったubuntu18.04の環境ができます。8行で！
こうやってDockerfileで相手と共有して、相手はdocker build ~ とすることでDockerイメージを作ることができます。後はdocker run ~ とイメージからコンテナを立ち上げ仮想OSの中に入ることができます。おんのじ！

## ディレクトリ構造
僕を含め初心者の方がここまで聞くと「Dockerすげえええええええーーーーーーーーー！！！！！何でもできんじゃん！すげえええええええええーーーーーひゃっほいーーーーー！！１hぃさfyいうぇｂ」
となります。皆なります。まあ、しかしOS環境ができても、肝心なのはAPIのファイルであったり、機械学習だとネットワークのモデルファイルなどがDocker内に配置されていないと元も子もないので、共有するディレクトリ構造について確認します。
kyoyu/
　├ data/
　├ ganbari/
　├ api_server.py　
　├ ***gomi/***
　├ ***.dockerignore***
　├ ***requirements.txt***
　└ ***Dockerfile***
例としてこの様なディレクトリ構造を考え、githubなどでこのkyoyuディレクトリを相手と共有するとします。共有するディレクトリの内部にDockerfileを置きます。dataとganbariとapi_sever.pyが環境で利用したいデータやコードのファイル・フォルダとします。gomiディレクトリはDocker環境には不要なもの(.mdでの仕様書など)とします。
#### .dockerignoreについて
後ほど説明しますが、DockerfileでDocker内でディレクトリを作る命令文を書き、kyoyuディレクトリ内のディレクトリをDocker内に配置します。その時、Docker内に不要なディレクトリをわざわざ配置しないように命令することができます。それが.dockerignoreです。テキストのファイルでこのように書きます。

```.dockerignore
gomi/*
```
これでgomiディレクトリ以下全てがDocker内に配置されません。

#### requirements.txtについて
環境で使いたいPythonパッケージをまとめます。
(例)

```例.py
absl-py==0.9.0
alembic==1.4.2
astor==0.8.1
attrs==19.3.0
backcall==0.2.0
bcolz==1.2.1
bleach==3.1.5
Bottleneck==1.3.2
certifi==2020.6.20
chardet==3.0.4
click==7.1.2
```
こんな感じにひたすらパッケージとそのバージョンが書かれたテキストファイルです。作るのが面倒臭そうですが、

```bash
/kyoyu$ pip freeze > requirements.txt
```
で一発でファイルができます。

## Dockerfileの心
Dockerfileの書き方について詳しいことはネットに転がっているので調べて下さい。今回はDockerfileのお気持ちを解説します。
Dockerfileは拡張子の無いテキストのファイルです(windowsでは.dockerfileで認識されるらしい)。

```Dockerfile
FROM ubuntu:18.04

# install wget, cmake
RUN apt-get update
RUN apt-get install -y python3 python3-pip

# setup directory
RUN mkdir /api
ADD . /api
WORKDIR /api

# install py
RUN pip3 install -r requirements.txt
```
お気持ちとしてはFROMで基となるOSを決め、以降は一捻りしたlinuxコマンドで環境を整えていく感じです。


（***追記***
コメントにて@Naughieさんから詳しい書き方について教えて頂いたので、一通り理解した後にコード成型の参考にして下さい。特にRUNコマンドは一つにまとめないと重くなります）

#### FROMコマンド
FROMコマンドで基本となるDockerイメージを指定します。「ちょ！？待て、Dockerイメージの設計図がDockerfileでしょ！？」と思うかもしれませんが、ここではDockerhubというサービスに登録されたDockerイメージを利用することができます。様々なDockerイメージが登録されており、大抵わざわざ自分でDockerfileを書かなくてもここにあります。
(実は、Python3系のDockerイメージも既にありますが、今回は解説用にわざわざコマンドで書きました。)

#### RUNコマンド
普通にlinuxのターミナルで書くようなコードを書くことができます。ただし、cdだけは後で説明するWORKDIRで書きます。

#### WORKDIRコマンド
linuxのcdコマンドの用に作業するディレクトリを変更します。

#### ADDコマンド
このDockerfileをdocker build ~ とビルドしてコンテナを作成しますが、このbuildしている環境のファイルをDocker内に送ることができます。

```Dockerfile
ADD [実行している環境のファイルのパス] [送りたいdocker内のディレクトリのパス]
```
今回は、kyoyuディレクトリ内でビルドすると仮定し、"." (kyoyuディレクトリ内の全てのディレクトリ・ファイル) を/apiに送っています。***この時、.dockerignoreで指定したgomiディレクトリはdocker内に配置されません。***

#### install -r requirements.txt
docker内のapiディレクトリにkyoyuディレクトリの中身を配置しました。docker内のディレクトリ構造はこの様になります。
api
　├ data/
　├ ganbari/
　├ api_server.py　
　└ ***requirements.txt***
ここで、WORKDIR /api のコマンドでapiディレクトリ内に入りrequirements.txtを参照してライブラリをインストールします。

```bash
RUN pip3 install -r requirements.txt
```

以上がDockerfileの書き方となります。他にも沢山便利なコマンドがあるのでググってみて下さい。

## ビルド
Dockerfileができたので早速イメージを作ります。DockerfileからDockerイメージを作ることをビルドと言います。

```bash
/kyoyu$ docker build -t [イメージの名前(名付ける)] [Dockerfileが存在するディレクトリのパス]
```
(-tは今は呪文と思っていて下さい)
今回、testという名前でイメージを作成し、"." (カレントディレクトリ(kyoyu))内にDockerfileが存在するので、実行するコードは次の様になります。

```bash
/kyoyu$ docker build -t test .
```

## ラン。コンテナを作る
Dockerイメージからコンテナを作ることをランと言います。

```bash
$ docker run -p [外部からアクセスされるポート番号]:[コンテナ側のポート番号を指定] -it [イメージ名] --name [コンテナの名前(名付ける)]
```
(-itは後ほどDockerをstartした時に環境を維持するコマンドなので今は呪文だと思って下さい)
例えば、今回の例でDocker側の5000番ポートでAPIなどを実行する予定で、それをパソコン実機の8888番ポートに送りたいとする。コンテナ名をtestconとすると

```bash
$ docker run -p 8888:5000 -it test --name testcon
```
となる。

## コンテナの起動
最後に、作ったDockerコンテナの中に入る。

```bash
$ docker ps -a
```
このコマンドでコンテナの一覧を見られるが、まず、ここにtestconがあることを確認する。

```bash 
$ docker start testcon
```
これで、停止しているtestconコンテナを動かすことができる。お気持ちとしては、dockerコンテナは通常停止している状態で存在し、それをstartすることで動かす。動いたコンテナにattach(触る)ことで、仮想OS内に入ることができる。

```bash 
$ docker attach testcon
```
```bash
root@hogehoge:/api＃
```
先頭が上の様な文字列になり、仮想OS内に入ることができた。ここで最初から/apiとなっているのはDockerfileでWORKDIR /apiと指定されているからである。

## 最後に
Dockerのお気持ち分かりましたか？Dockerって聞いたことある！くらいの層を対象にしているので、細かいところはググって下さい。

## 最後の最後に
今回のDockerfileを@Naughieさんのご指摘の通り改善してみますと
```Dockerfile
FROM ubuntu:18.04

# install Python
RUN apt-get update && apt-get install -y \
    python3 \
    python3-pip

# setup directory
COPY . /api
WORKDIR /api

# install py
RUN pip3 install -r requirements.txt
```