【Docker+Cloud Run勉強会】2. DockerでWordPressをデプロイする

# この記事
先日Docker及びCloud Runについての勉強会を開催しました。
本記事は、その時用意した資料となります。

- [1. Dockerとは？](https://qiita.com/r-dohara/items/403ac95e1f51026e96f1)
- [3. Cloud RunにWordPressをデプロイする](https://qiita.com/r-dohara/items/3d332c4e83e27b4517e7)

[YouTube](https://www.youtube.com/watch?v=7f01IArYqXg)

# 目的
改めて、コンテナとは？というところから説明しつつ

- 代表的なコンテナエンジンであるDocker
- Google CloudのコンテナデプロイサービスであるCloud Run

について学んでもらえたらと考えています

# コンテナでWordPressをデプロイしてみよう
ここからは教材として、有名なコンテンツマネージメントシステム（CMS）であるWordPressを使っていきたいと思います
WordPressはコンテンツの管理にデータベースを利用します

コンテナは基本的に「1コンテナ1サービス」とするのがベストプラクティスです

> 1 つのコンテナーには 1 つのサービスを割り当てるということにすれば、気にかける箇所が絞られるので、一般的にはこれが推奨されます。

（[ドキュメント](https://matsuand.github.io/docs.docker.jp.onthefly/config/containers/multi-service_container/)）

ですので今回は、WordPressとデータベースは別々に稼働させます

流れとしては以下の通りとなります

1. ローカルに、WordPress用とデータベース用のコンテナをデプロイする
2. Cloud RunでWordPressを構築する

※ 注意点
1, 2を通してDockerが、更に2ではgcloudが必要となります
ローカルに準備するのが難しい場合は、GCのコンソールからCloud Shellを起動することで実施することも可能です

# ローカルに、WordPress用とデータベース用のコンテナをデプロイする
まずはローカルでWordPress、データベースの両方をデプロイし、コンテナ間通信について確認してみましょう

構成としては以下のようになります

![Docker+Cloud Run-wp+db.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2697194/60ec7112-46cf-13dd-2727-a7fb19feeb57.png)

- コンテナ
    - wp : WordPressが稼働
    - db : MySQLが稼働
- db_data (ボリューム) : データベースのデータを保存する領域
- my_net (Dockerネットワーク) : Docker内のネットワーク
    - コンテナ間通信、コンテナ-ホスト間通信を実現

では上記の構成に基づいて、順に構築をしていきましょう

早速コンテナを、と行きたいのですが、まずはコンテナ起動時に必要となる「ボリューム」と「Dockerネットワーク」を作成します

## ボリュームの作成
「コンテナの特徴 : 揮発性」で話しましたが、コンテナは破棄される時に中身をすべて失います
しかしそれではデータベースとして機能しません

そのような場合、「コンテナから参照できる、データを永続的に保存する場所」をホストに作成することで対処します
これをボリュームといいます

ボリュームは`docker volume create ${ボリューム名}`で作成可能です

## Dockerネットワークの作成
次にDockerネットワークを作成します

コマンドは`docker network create ${ネットワーク名}`となります

※ 細かい話
Dockerネットワークにはいくつか種類がありますが、指定しなければ[ブリッジ・ネットワーク](https://docs.docker.jp/v19.03/engine/userguide/networking/dockernetworks.html#id5)が作成されます
ブリッジ・ネットワークの特徴は

- 同じネットワークに所属しているコンテナ同士は通信が可能
- 仮想ブリッジを通して、ホストと通信が可能
- コンテナ名で名前解決が可能
    - ただし、初期から存在するブリッジ・ネットワーク「bridge」では名前解決不可能（[ドキュメント](https://docs.docker.com/network/bridge/#differences-between-user-defined-bridges-and-the-default-bridge)）

## データベース用のコンテナデプロイ
今回使用するMySQLコンテナは、「hello-world」同様既に存在するDockerイメージをそのままデプロイします
コマンドは以下のとおりです

    docker run -d --name db --net my_net -v db_data:/var/lib/mysql --env-file .env mysql:5.7


- `-d` : コンテナをバックグラウンドで実行
- `--name db` : コンテナに名前を付与
- `--net my_net` : コンテナが所属するネットワークを指定
- `-v db_data:/var/lib/mysql` : ボリュームの割り当て
    - ${ボリューム名}:${コンテナ側のパス}
    - ちなみにホスト側のディレクトリをバインドさせることも可能
- `--env-file .env` : 環境変数をファイルで指定
    - .envファイルについては後述してます
- `mysql:5.7` : Dockerイメージを指定

## WordPress用のコンテナデプロイ
WordPressに関してもDockerイメージが公開されており、それをそのままデプロイしても十分起動します
が、今回はあえて一手間加えて、WordPressのプラグインを1つ事前にダウンロードしておきます

その場合、まずはオリジナルのDockerイメージを作成しておく必要があります
Dockerイメージの作成に用いるDockerfileは後述しています（既に説明した`RUN`命令を使ってます）

このDockerfileからDockerイメージを作成するコマンドは以下のとおりです

```
docker build -t my_db:test .
```

- `-t my_db:test` : Dockerイメージのリポジトリとタグを指定する（[ドキュメント](https://matsuand.github.io/docs.docker.jp.onthefly/docker-hub/repos/#pushing-a-docker-container-image-to-docker-hub)）
- `.` : Dockerfileが存在するディレクトリを指定

※ 細かい話

- Dockerfileのファイル名が「Dockerfile」以外の場合は、`-f`でファイル名を指定する必要があります
- `-t`が無いとリポジトリ、タグ共に<none>となります
    - 一応イメージIDを指定することでコンテナの起動は可能です

Dockerイメージ「my_db:test」がこれで作成されたので、あとはコンテナをデプロイしましょう

    docker run -d --name wp --net my_net -e WORDPRESS_DB_HOST=db:3306 --env-file .env -p 8000:80 my_wp:test


- `-e WORDPRESS_DB_HOST=db:3306` : このようにして環境変数を指定することも可能です
- `-p 8000:80` : ホストにおいて、localhostの8000番ポートにアクセスすると、コンテナの80番ポートに繋がります
    - 厳密には `${IPアドレス}:${ホスト側のポート}:${コンテナ側のポート}`という指定になります

## コマンド&各種ファイル
**コマンド**

```
# ボリューム及びネットワークの作成
~/wordpress$ docker volume create db_data
~/wordpress$ docker network create my_net

# データベース用コンテナのデプロイ
~/wordpress$ cd db
~/wordpress/db$ ls -a
.  ..  .env
~/wordpress/db$ docker run -d --name db --net my_net -v db_data:/var/lib/mysql --env-file .env mysql:5.7

# WordPress用コンテナのデプロイ
~/wordpress/db$ cd ../wp/
~/wordpress/wp$ ls -a
.  ..  .dockerignore  .env  Dockerfile
~/wordpress/wp$ docker build -t my_wp:test .
~/wordpress/wp$ docker run -d --name wp --net my_net -e WORDPRESS_DB_HOST=db:3306 --env-file .env -p 8000:80 my_wp:test
```

**Dockerfile**

```
FROM wordpress:latest
RUN apt-get update -y && apt-get install -y unzip
RUN curl -o /tmp/custom-post-type-ui.1.12.1.zip -L https://downloads.wordpress.org/plugin/custom-post-type-ui.1.12.1.zip \
  && unzip /tmp/custom-post-type-ui.1.12.1.zip -d /tmp/ \
  && mv /tmp/custom-post-type-ui /usr/src/wordpress/wp-content/plugins/
```

**.env**
データベース用

```
MYSQL_ROOT_PASSWORD=${MySQL管理者ユーザ「root」用パスワード}
MYSQL_DATABASE=wordpress
MYSQL_USER=wordpress
MYSQL_PASSWORD=${MySQLユーザ「wordpress」用パスワード}
```

WordPress用

```
WORDPRESS_DB_USER=wordpress
WORDPRESS_DB_PASSWORD=${MySQLユーザ「wordpress」用パスワード}
```

## 各環境変数
- [wordpress](https://hub.docker.com/_/wordpress)
    - WORDPRESS_DB_HOST : データベースのホスト名
    - WORDPRESS_DB_USER : データベースのユーザ名
    - WORDPRESS_DB_PASSWORD : 「WORDPRESS_DB_USER」で指定したユーザのパスワード
    - WORDPRESS_DB_NAME : 使用するデータベース
- [mysql](https://hub.docker.com/_/mysql)
    - MYSQL_ROOT_PASSWORD : MySQL管理者ユーザ「root」用パスワード
    - MYSQL_DATABASE : 起動時点で作成するデータベース
    - MYSQL_USER : 起動時点で作成するユーザ
    - MYSQL_PASSWORD : 「MYSQL_USER」で指定したユーザのパスワード

## docker execコマンド
Dockerfileの一例を紹介しましたが、いざ自分で作成しようとすると

- ベースイメージにはどんなパッケージが入っているのか確かめたい
- 思ったように動作せず、トラブルシューティングがしたい

などといった考えが出てくると思います

そんな時は`docker exec`コマンドが便利です
こちらを使うと、実行中のコンテナ内でコマンドを実行することができます

基本的には以下のように入力します

```
docker exec -it ${コンテナ名} ${コンテナで実行したいコマンド}
```

`-it`についてはちょっとややこしいのですが、このオプションが無いと正しく標準入力、標準出力がおこなわれなくなります
初めのうちは一旦つけるようにし、ある程度Dockerの理解が進んできたら詳しく勉強すればいいと思います

このコマンドを使うと、コンテナのシェルで作業することも可能になります

```
$ docker exec -it wp bash
root@7b979b065660:/var/www/html# ps x
  PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:00 apache2 -DFOREGROUND
   28 pts/0    Ss     0:00 bash
   38 pts/0    R+     0:00 ps x
root@7b979b065660:/var/www/html#
```

## Docker Compose
先程までは都度コマンドを叩き、ボリューム、ネットワーク及びコンテナの作成を行っていました
ただ、`docker run`などは必要なオプションも多く、毎回記載するのは大変です
更に作成順なども考慮しないといけません（WordPressはデータベースのあとに作成したいなど）

Docker ComposeはそのようなDocker管理を楽にしてくれるツールです

Docker Composeでは、「docker-compose.yml」ファイルを用いて構成を定義します
今回の場合は以下のようになります

```
version: '3'
services:
  db:
    image: mysql:5.7
    volumes:
      - db_data:/var/lib/mysql
    networks:
      - my_net
    env_file: ./db/.env
    restart: always
  wordpress:
    depends_on:
      - db
    build: ./wp/
    networks:
      - my_net
    ports:
      - "8000:80"
    environment:
      WORDPRESS_DB_HOST: db:3306
    env_file: ./wp/.env
    restart: always
volumes:
  db_data:
networks:
  my_net:
```

- `restart: always` : コンテナ終了時、常に再起動を行う（明示的な停止は例外）

ディレクトリ構成

```
wordpress/
├── db
│   └── .env
├── docker-compose.yml
└── wp
    ├── .dockerignore
    ├── .env
    └── Dockerfile
```

上記のようなファイルを用意した上で

```
docker-compose up -d --build
```

を実行すると、先程作成したのと同じ環境が作成できます

# 参考
- [コンテナー内での複数サービス起動](https://matsuand.github.io/docs.docker.jp.onthefly/config/containers/multi-service_container/)
- [Docker コンテナ・ネットワークの理解](https://docs.docker.jp/v19.03/engine/userguide/networking/dockernetworks.html)
- [Use bridge networks](https://docs.docker.com/network/bridge/)
- [リポジトリ](https://matsuand.github.io/docs.docker.jp.onthefly/docker-hub/repos/)
- [wordpress](https://hub.docker.com/_/wordpress)
- [mysql](https://hub.docker.com/_/mysql)