【Docker】チュートリアル（Python+php）

## はじめに
Dockerを勉強するにあたり、素晴らしいチュートリアル動画があったのでそれを元に自分で手を動かして見ました。リンク先の動画は英語ですので、日本語で少し補足をしながら進めていきます。初心者のため認識の誤りがあるかもしれませんので、その際はコメント頂けると幸いです。

事前にdocker、docker-composeのインストールが必要です。

元動画

> [Docker Compose in 12 Minutes](https://www.youtube.com/watch?v=Qw9zlE3t8Ko&feature=youtu.be)


最終的なフォルダ構成は以下のようになります。
<img width="256" alt="スクリーンショット 2020-02-25 21.50.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/557038/3e4f0a09-5b4a-df45-7b7b-246211177035.png">

productディレクトリにはjsonを返すAPIをpythonで用意します。
websiteディレクトリには画面を描画するphpを用意します。

## product

まずはdocker_tutorial配下にproductディレクトリを作成します。

```
$ mkdir product
```

そしてシンプルなPythonコードを作成します。flaskとflask_restfulというライブラリを使用します。

```py:api.py

from flask import Flask
from flask_restful import Resource, Api

app = Flask(__name__)
api = Api(app)

class Product(Resource):
    def get(self):
        return{
            'products': [
                'Ice cream',
                'Chocolate',
                'Fruit'
            ]
        }

api.add_resource(Product, '/')

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=80, debug=True)

```


ローカル環境にpythonをインストールしている場合は、pip installなどでライブラリをインストールしますが、Dockerを使用した場合は個別にインストールする必要はありません。その代わりにコンテナ起動時に必要となるライブラリなどを以下のテキストファイルに記載するか、後述のDockerfile内にpip installのコマンドを記載します。バージョンはライブラリの公式サイトなどを参照して使用したいバージョンを記載します。

```requirements.txt
Flask==0.12
flask-restful==0.3.5
```


次にDockerfileを作成します。
DockerfileはDocker上で動作させるコンテナの構成情報を記述するためのファイルです。

```Dockerfile
FROM python:3-onbuild
COPY . /usr/src/app
CMD ["python", "api.py"]
```

*FROM*
FROMにはベースとするDockerイメージを指定します。また「onbuild」を記載することで、Dockerfile を使って docker build をするとき、Dockerfile 内で pip install を書かなくても Dockerfile と同じディレクトリに requirements.txt があると自動的にインストールをするようになっています。

*COPY*
COPYにはローカルファイルをDockerイメージのどこにコピーするかを記載します。今回の場合はproductディレクトリをDocker上の/usr/src/app内にコピーします。

*CMD*
CMDにはコンテナ起動時に実行するコマンドを記載します。


次にdocker-compose.ymlを作成します。docker-composeには
>- Dockerイメージをビルドするための情報（使用するDockerfile、イメージ名など）
- コンテナ起動するための情報（ホストとの共有ディレクトリ設定やポートフォワードなどの起動オプションなど）
- 使用するDockerネットワーク

などを記載します。

```yml:docker-compose.yml
version: '3'
services:
    product-service:
        build: ./product
        volumes: 
            - ./product:/usr/src/app
        ports: 
            - 5001:80

```

*version*
docker-composeで使用するバージョンを記載します。現在はバージョン3が最新ですので、3を指定します。

*services*
アプリケーションを動かすための各要素をServiceと読んでいます。各サービスをネストしてこちらに記載します。今回の場合は「product-service」というサービスを起動します。

*build*
指定したディレクトリにあるDockerfileでコンテナを起動します。今回の場合はproductディレクトリのDockerfileでコンテナを起動します。

*volumes*
ローカリのディレクトをコンテナのディレクトにマウントします。今回の場合はproductディレクトリをコンテナ上の/usr/src/appディレクトリにマウントします。

*ports*
Dockerを立ち上げるポート番号を記載します。今回は5001番ポートで立ち上げます。

それではDockerを起動

```
$ docker-compose up
```

起動が完了したら、localhost:5001にアクセスします。
以下のような画面が表示されたら問題なく起動しています。
ここまでで、簡単にPython、Flaskの環境ができることが分かります。

<img width="290" alt="スクリーンショット 2020-02-25 21.18.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/557038/f865f095-e3f3-d7cf-ad57-7bddbf130d9d.png">


この状態でapi.pyを以下のように変更し、保存します。

```py:api.py
class Product(Resource):
    def get(self):
        return{
            'products': [
                'Ice cream',
                'Chocolate',
                'Fruit',
                'Eggs'
            ]
        }
```

再読み込みを行うと、表示内容も更新されます。コンテナを再起動しなくてもマウントしてくれます。

<img width="301" alt="スクリーンショット 2020-02-25 21.21.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/557038/88df8747-0949-5eeb-530e-cd70cdc78e7f.png">


ここまできたらコンソール上で「control+C」を押して、dockerを一旦停止します。


## website
次はdocker_tutorial配下にwebsiteというディレクトリを作成します。こちらにはphpのファイルを作成します。

http\://product-serviceの結果をループして表示する処理です。
ここでいうproduct-serviceはdocker-composeに記載したサービス名が該当します。

```php:index.php
<html>
    <head>
        <title>My Shop</title>
    </head>

    <body>
        <h1>Welcome to my shop</h1>
        <ul>
            <?php
                $json = file_get_contents('http://product-service');
                $obj = json_decode($json);

                $products = $obj->products;
                foreach($products as $product) {
                    echo "<li>$product</li>";
                }
            ?>
        </ul>
    </body>
```


次にdocker-compose.ymlを以下のように変更します。

```yml:docker-compose.yml
version: '3'

services:
    product-service:
        build: ./product
        volumes: 
            - ./product:/usr/src/app
        ports: 
            - 5001:80

    website:
        image: php:apache
        volumes: 
            - ./website:/var/www/html
        ports: 
            - 5000:80
        depends_on: 
            - product-service

```


*website*
servicesにwebsiteというサービスを追加します。

*image*
imageを記載することで、Docker Hub上に用意されている既存のイメージを使用することができます。buildとimageは同時に指定することはできません。

*depends_on*
サービス同士の依存関係を記載します。今回の場合、websiteが実行されるより前にproduct-serviceが実行されるようになります。docker-compose.ymlにproduct-serviceがない場合はエラーになります。

再度Dockerを起動します。

```
$ docker-compose up
```
起動が完了したらlocalhost:5000にアクセスします。
以下のような画面が表示されていれば成功です。

<img width="441" alt="スクリーンショット 2020-02-25 21.38.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/557038/3e75c073-41f5-04ce-c096-af12ae341dde.png">

## さいごに
Dockerを使用することで、簡単にPython+phpの環境を作ることができました。私もDocker初心者のためこれからも引き続き学習していきたいと思います。

## おまけ

-dをつけることでバックグラウンドでコンテナを立ち上げることができます。

```
$ docker-compose up -d

Starting docker_tutorial_product-service_1 ... done
Starting docker_tutorial_website_1         ... done
```


psコマンドで起動中のコンテナの一覧が確認できます。

```
$ docker ps

CONTAINER ID        IMAGE                             COMMAND                  CREATED             STATUS              PORTS                  NAMES
24de043f94d9        php:apache                        "docker-php-entrypoi…"   10 minutes ago      Up About a minute   0.0.0.0:5000->80/tcp   docker_tutorial_website_1
da62924c1154        docker_tutorial_product-service   "python api.py"          32 minutes ago      Up About a minute   0.0.0.0:5001->80/tcp   docker_tutorial_product-service_1
```

stopでコンテナを停止することができます。

```
$ docker-compose stop
Stopping docker_tutorial_website_1         ... done
Stopping docker_tutorial_product-service_1 ... done

$ docker ps
ONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
```


## 参考

- [Docker Compose in 12 Minutes](https://www.youtube.com/watch?v=Qw9zlE3t8Ko&feature=youtu.be)
- [Docker を使う（python のイメージで色々確認してみる）](https://qiita.com/landwarrior/items/fd918da9ebae20486b81)
- [Docker入門（第四回）～Dockerfileについて～](https://knowledge.sakura.ad.jp/15253/)
- [Compose file version3のリファレンス](https://qiita.com/goforbroke/items/b4a071af71f457960e59)