04.開発環境構築 - 画像変換サービス開発2  - 実践大規模Webアプリ -

# 目次

* 【講義】 コンテナ技術を用いる Docker
* 【実装】 Docker のインストール
* 【実装】 Docker を実行してみよう
* 【講義】 MySQL というデータベースについて
* 【実装】 MySQL のインストール
* 【実装】 mojipic のデータモデリング
* 【講義】 Redis という key-value ストアについて
* 【実装】 Redis のインストール
* 【講義】 ImageMagick という画像変換ツールについて
* 【実装】 ImageMagick のインストール
* 【実装】 ImageMagick のコマンドラインを利用した操作

# 【講義】コンテナ技術を用いる Docker

**Docker**とは、コンテナ技術を使ったアプリケーションのデプロイ実行ツール。
Apache 2.0 ライセンスで提供されている。

Dockerはコンテナ技術という、OS内に名前空間の異なる隔離領域を確保する技術を利用している。
そのためハードウェアを仮想化するのではなく、コンテナ技術を使ってOSの仮想化をするため、非常に高速。

Dockerは、用意されている仮想イメージを利用したインスタンスである「コンテナ」を利用できる。
「コンテナ」内は名前空間でホスト側のOSと区切られており、利用したいソフトウェアのプロセスをすぐに利用できる。
つまり、OS立上げ等の時間を節約して、仮想化されているソフトウェアを利用できる。

このコンテナの「イメージ」は、差分イメージとして管理されているため、更新時も丸々大きなバイナリを取得する必要はない。
差分ファイルだけの更新で、コンテナのイメージ全体をバージョンアップする仕組みになっている。

Dockerの用語としては

* イメージとは：　特定のアプリケーションのイメージのこと
* コンテナとは：　イメージより作り出されるホストOSから隔離された実行環境
* プロセスとは：　コンテナ内の領域で実行されているプロセス

となる。

Dockerで特定のイメージ名を実行すると、

* イメージをダウンロードし
* コンテナを作り
* プロセスを立ち上げる

という流れを高速で行う。

この開発環境構築をDockerで行う場合、イメージ・コンテナ・プロセスは1対1対1の関係で運用する。

# 【実装】 Docker のインストール

Docker for Macをインストール済みのため、省略。

# Docker を実行してみよう

以下のコマンドを実行してみる。

```
$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
b8dfde127a29: Pull complete 
Digest: sha256:df5f5184104426b65967e016ff2ac0bfcd44ad7899ca3bbcf8e44e4461491a9e
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/
```

ここには、

* DockerがDockerデーモン(バックグラウンドプロセス)に接続し
* hello-worldというイメージをDocker Hubから取得し
* コンテナを作成して実行した後、コンテナの出力をターミナルに表示した

と書いてある。

Dockerは、`docker run ${イメージ名}`とすると自動的にDocker Hubと呼ばれるイメージを管理しているリポジトリから指定されたイメージを検索して最新のイメージを取得し、実行する。
その後、イメージからコンテナを作成し、その中でプロセスを実行してソフトウェアを起動するという動きをする。

なお、全てのイメージとコンテナの状態は、

```
docker ps -a
```

というdocker ps コマンドを実行すると確認できる。
-aオプションを利用すると、起動中でない全てのコンテナを確認できる。

```
CONTAINER ID   IMAGE         COMMAND                  CREATED         STATUS                      PORTS                                                    NAMES
35b522adc9e2   hello-world   "/hello"                 7 minutes ago   Exited (0) 7 minutes ago                                                             exciting_bohr
0a0ba849433f   mysql:5.7     "docker-entrypoint.s…"   47 hours ago    Exited (255) 32 hours ago   33060/tcp, 0.0.0.0:33306->3306/tcp, :::33306->3306/tcp   to_do_sample_container
```

以上のように表示される。

docker runは

* イメージの取得
* コンテナの作成
* コンテナの起動

までを一気にやってくれる便利なコマンド。

# 【講義】 MySQL というデータベースについて

ここでは、 MySQL というデータベースの MySQL Community Edition という GPL ライセンスのフリーのバージョンを利用する。

**MySQL** は、Oracle 社によって現在管理されている RDBMS　。
様々なストレージエンジンが利用でき、大規模 Web のための運用に合わせた機能を多数持ち合わせている。
非常に多くの大規模 Web アプリケーションでの運用実績がある。

特に大規模で利用される機能として、

* レプリケーション
* パーティショニング

という2つの機能がある。
まずはこの2つの機能について紹介する。

レプリケーションとは、1つのデータベースのコピーを複数用意するための機能。
コピー元のことをマスター、コピー先のことをスレープ(レプリカ)という。
書き込みはマスターに対して行う。
そのためパフォーマンスの限界はあるが、スレープという読み込みだけが可能なデータベースは、何十台も用意してスケールできる。
レプリケーションの遅延の問題はあるが、利用者のほとんどが CRUD の READ をするようなサービスでは、この MySQL のレプリケーション機能は非常に重要な役割を果たす。
情報の利用をスケールさせることも可能。

もう1つ重要な機能は、パーティショニング。
MySQL はデフォルトでは1テーブルが1ファイルに対応するデータベース。
しかし、パーティショニング機能を用いると、任意の分割方法で1つのテーブルの情報を複数のファイルに対して保存できる。
この際にディスクIOが異なるファイルの選択も可能。
例えば、

* ユーザーAのデータはファイル1に
* ユーザーBのデータはファイル2に

というように保存先を変えることができる。
これにより、まずはデータの処理の高速化ができる。
また日時で分割しておくと、不要なデータをファイルごと削除してデータベース上での検索速度を向上できる。

なお、今回の mojipic の開発ではこれらの大規模 Web アプリケーション向けの機能は利用しない。
しかし、こういった機能の充実生や運用実績から大規模 Web アプリケーションではこの MySQL がよく利用されることを覚えておこう。

# 【実装】 MySQL のインストール

MySQL をインストールして、動作確認をしてみよう。
Docker さえインストールされていれば、 MySQL の起動は一瞬で終わる。

```
$ docker run --name mojipic-mysql -v /Users/glaciermelt/environment/workspace/mojipic-mysql:/var/lib/mysql --platform linux/amd64 -e MYSQL_ROOT_PASSWORD=mysql -d -p 3306:3306 mysql@sha256:7d4df6c491e9b844b8c594c5607cea7465b42bd3d51db3d93359b3e8d06f1518 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
Unable to find image 'mysql@sha256:7d4df6c491e9b844b8c594c5607cea7465b42bd3d51db3d93359b3e8d06f1518' locally
docker.io/library/mysql@sha256:7d4df6c491e9b844b8c594c5607cea7465b42bd3d51db3d93359b3e8d06f1518: Pulling from library/mysql
b248fa9f6d2a: Pull complete 
0058702edba7: Pull complete 
84e63f6f2297: Pull complete 
81ad333a3403: Pull complete 
3708a6b9545b: Pull complete 
442342ef4996: Pull complete 
ecf24ffdea0f: Pull complete 
a02b8b4c83b0: Pull complete 
7b1a4d0969f1: Pull complete 
8d715f261d93: Pull complete 
140640d3c0e3: Pull complete 
Digest: sha256:7d4df6c491e9b844b8c594c5607cea7465b42bd3d51db3d93359b3e8d06f1518
Status: Downloaded newer image for mysql@sha256:7d4df6c491e9b844b8c594c5607cea7465b42bd3d51db3d93359b3e8d06f1518
ac58d387f1549b724249e166a1d1a355acb0f37c48b957b4d2f3503ed53bfeca
```

のように表示される。

このコマンドでは、`mojipic-mysql`という名前で mysql:5.7.19 というイメージのインスタンスをサーバー起動し、3306ポートを開けて通信させている。
コマンドにある`mysql@sha256:7d4df6c491e9b844b8c594c5607cea7465b42bd3d51db3d93359b3e8d06f1518`は、`mysql:5.7.19`のハッシュ表現。
多くのOSではその部分を`mysql:5.7.19`で機能するが、 Apple Chip が入っているパソコンでは機能しない。
Apple Chip で機能させるべく、ハッシュ表現を使っている。

なお、MySQL におけるルートユーザーのパスワードを`mysql`とし、`~/workspace/mojipic-mysql`というパスにデータベースが作られる設定や、その他、日本語が利用できる設定などをしていく。

```
docker ps
```

と、-a オプションを付けずにサブコマンドの ps を実行すると、起動中のコンテナんが表示される。

```
CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS                                       NAMES
ac58d387f154   mysql     "docker-entrypoint.s…"   7 minutes ago   Up 7 minutes   0.0.0.0:3306->3306/tcp, :::3306->3306/tcp   mojipic-mysql
```

のように、mojipic-mysql という名称のコンテナが表示されているのがわかる。

```
docker start mojipic-mysql
```

でコンテナを起動し、

```
docker stop mojipic-mysql
```

でコンテナを終了させる。
これは2回目移行、起動する際に利用する。

次に対話的に、MySQL にアクセスしよう。

```
$ docker exec -it mojipic-mysql mysql -u root -p mysql
Enter password: 
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 5.6.47 MySQL Community Server (GPL)

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 
```

以上のように表示されれば成功。

ここで、

* データベースを作成し
* できたかどうかを確認し
* そのデータベースに接続して
* テーブル一覧を確認してから終了

以上をしてみる。

```
> create database mojipic;
Query OK, 1 row affected (0.01 sec)
```

以上でまずはデータベースを作成できた。
なお、削除するときは`drop database mojipic;`となる。

```
> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mojipic            |
| mysql              |
| performance_schema |
+--------------------+
4 rows in set (0.00 sec)
```

以上で作成したデータベース一覧を確認できた。
データベースはまだ作ったばかりで空なので、 information_schema という管理情報が含まれるデータベースを見てみる。

```
> use information_schema;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
```

以上で information_schema に接続できた。

```
> show tables;
...
| INNODB_FT_CONFIG                      |
+---------------------------------------+
59 rows in seーぶt (0.00 sec)
```

このコマンドでテーブル一覧が確認できるが、大量のテーブルが表示された。
他の　SQL　も実行してみる。

```
> select * from schemata;
ysql> select * from schemata;
+--------------+--------------------+----------------------------+------------------------+----------+
| CATALOG_NAME | SCHEMA_NAME        | DEFAULT_CHARACTER_SET_NAME | DEFAULT_COLLATION_NAME | SQL_PATH |
+--------------+--------------------+----------------------------+------------------------+----------+
| def          | information_schema | utf8                       | utf8_general_ci        | NULL     |
| def          | mojipic            | utf8mb4                    | utf8mb4_unicode_ci     | NULL     |
| def          | mysql              | utf8mb4                    | utf8mb4_unicode_ci     | NULL     |
| def          | performance_schema | utf8                       | utf8_general_ci        | NULL     |
+--------------+--------------------+----------------------------+------------------------+----------+
4 rows in set (0.00 sec)
```

このように、きちんと mojipic が作られていることが確認できた。

最後に`\q`で mysql の CLI を終了する。

なお、

```
docker exec -it mojipic-mysql bash
```

以上のようにbashを起動すると、コンテナ内のコンソールのbashを通じた実行も可能。

Dockerで利用されているコンテナの内部で、どのように仮想 OS が起動しているのかを確かめることができる。

# 【実装】 mojipic のデータモデリング

mojipic のデータモデリングを行う。
ユースケース図やクラス図、シーケンス図を改めて確認しつつ、データモデリングをしていく。

なお、このフェーズでのデータモデリングは、今後の開発をより具体的に考えるためのもの。
この時点の DDL が必ずしも確定情報とする必要はない。

ここでは、以下のように DDL を定義しておく。

```
create table picture_properties(
  picture_id bigint(20) not null auto_increment,
  status varchar(255) not null,
  twitter_id bigint(20) not null,
  file_name varchar(255) not null,
  content_type varchar(255) not null,
  overlay_text varchar(255) not null,
  overlay_text_size int(4) not null,
  original_filepath varchar(255) not null,
  converted_filepath varchar(255),
  created_time datetime not null,
  primary key (picture_id),
  index(twitter_id),
  index(created_time)
) engine=innodb charset=utf8mb4;
```

以上のような内容とした。

* `picture_id`は、写真の ID
* `status`は、変換の状態
* `twitter_id`は、Twitter上でのユーザー ID
* `file_name`は、元のファイル名
* `content_type`は、コンテントタイプ
* `overlay_text`は、オーバーレイするテキスト
* `overlay_text_size`は、オーバーレイするテキストのサイズ
* `original_filepath`は、元画像のファイルパス
* `converted_filepath`は、返還後のファイルパス
* `created_time`は、作成日時

のようにした。
また、利用過程で作成日時順にソートし、ユーザーの投稿内容で絞り込む必要がある。
そのため、`twitter_id`と`created_time`にはインデックスを用意した。

```
docker exec -it mojipic-mysql mysql -u root -p mysql
```

でデータベースに接続し、

```
use mojipic;
```

してから、以上のテーブル定義の DDL を一度流して確認してみよう。

```
mysql> desc picture_properties;
+--------------------+--------------+------+-----+---------+----------------+
| Field              | Type         | Null | Key | Default | Extra          |
+--------------------+--------------+------+-----+---------+----------------+
| picture_id         | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| status             | varchar(255) | NO   |     | NULL    |                |
| twitter_id         | bigint(20)   | NO   | MUL | NULL    |                |
| file_name          | varchar(255) | NO   |     | NULL    |                |
| content_type       | varchar(255) | NO   |     | NULL    |                |
| overlay_text       | varchar(255) | NO   |     | NULL    |                |
| overlay_text_size  | int(4)       | NO   |     | NULL    |                |
| original_filepath  | varchar(255) | NO   |     | NULL    |                |
| converted_filepath | varchar(255) | YES  |     | NULL    |                |
| created_time       | datetime     | NO   | MUL | NULL    |                |
+--------------------+--------------+------+-----+---------+----------------+
10 rows in set (0.01 sec)
```

以上で、作成されたテーブルの定義を確認できた。
`\q`で終了する。

# 【講義】 Redis という key-value ストアについて

次に、Redis という key-value ストアについて学ぶ。

Redis は BSD ライセンスで提供される、インメモリのデータストア。
主に、キャッシュやメッセージングで利用される。

大規模 Web サービスにおいては、キャッシュとして利用される他、複雑なシステムなどでは、Pub/Sub を利用したメッセージブロッカーとして利用されたり、今回のようにタスクキューのように利用される。

今回は利用しないが、Redis には Redis Cluster 機能がある。
大規模 Web サービスではクラスタを作成して、性能と耐障害性を高めた状況で利用する。

Redis はシングルスレッドで、TCP の通信をほとんどそのまま利用するため非常に高速に動作する。

またサポートしているデータ構造は、

* `Strings`という文字列、key の文字列に対して様々な情報を保存できる
* `Lists`という連結リスト、先頭や末尾の操作が O(1) で高速
* `Sets`というセット、重複を排除してくれる
* `Hashes`という連想配列
* `Sorted stes`というソート済みセット

となる。

通常のデータストアとしても使えるが、

* Lists の特性を利用したキューやスタック
* Sets の特性を活かした重複排除
* Sorted sets などを利用したランキング作成

などは、一旦 RDBに保存して処理するよりも数倍早く処理ができる。
そのため、Redis を利用している Web サービスは非常に多い。

ただし、Redis Clusterを利用しスケールさせて耐障害性を高めたい場合には、復旧の時間に影響が出る。
そのため、基本的には非常に小さいデータの値を預けた利用が薦められる。

mojipic では、基本的に picture_id という整数値だけを Lists に保存して、タスクキューとして利用していく。

# 【実装】 Redis のインストール

Dockerを利用すれば、この Redis の利用も非常に簡単。

```
$ docker run --name mojipic-redis -d -p 6379:6379 redis:4.0.1
Unable to find image 'redis:4.0.1' locally
4.0.1: Pulling from library/redis
065132d9f705: Pull complete 
be9835c27852: Pull complete 
f4a0d1212c38: Pull complete 
ea1f878b621a: Pull complete 
7a838393b4b9: Pull complete 
9f48e489da12: Pull complete 
Digest: sha256:8a54dcc711406447b3631a81ef929f500e6836b43e7d61005fa27057882159da
Status: Downloaded newer image for redis:4.0.1
2930dbfa22bf62bc1250ecc0e3835381d2093654592f206795e7c1419457ed4a
```

以上のように、Redis のコンテナが mojipic-redis という名前で作成され、サーバーとして実行される。なおポートは 6379 が利用される。

Redis は TCP をそのまま利用して通信するため、RDB のように認証をすることはない。
そのため、セキュリティには気をつける必要がある。

たいていの場合、Redis は Web アプリケーションのサーバーと同じサーバー上に配置して、Web アプリケーションからのアクセスだけを許可するようにしておくのが安全。

なお Redis は、redis-cli というコマンドラインツールを利用したデータの操作が可能。

```
$ docker exec -it mojipic-redis redis-cli -h localhost -p 6379
localhost:6379> 
```

以上のように表示され、redis-cli が利用できる状態になる。

これから、利用する Lists の操作をしてみる。
ここでは`tasks`というリストに 1, 2, 3 という写真 ID を挿入し、それを取得するということをしてみる。

* rpush はリストの右側に値を追加するコマンド
* lrange はリストの中身を指定されたインデックスから指定されたインデックスまで表示するコマンド
* lpopはリストの左側から値を1つ取り出すコマンド

となる。

```
> rpush tasks 1
(integer) 1
> rpush tasks 2
(integer) 2
> rpush tasks 3
(integer) 3
> lrange tasks 0 2
1) "1"
2) "2"
3) "3"
> lpop tasks
"1"
> lrange tasks 0 1
1) "2"
2) "3"
> lpop tasks
"2"
> lrange tasks 0 0
1) "3"
> lpop tasks
"3"
> lpop tasks
(nil)
```

以上のように表示される。
これが Redis に利用方法。

実際には、Scala の Redis のライブラリを利用してこれらの操作をしていく。

```
exit
```

以上のコマンドで、redis-cli を終了させる。

また、Docker を再度起動した際には、

```
docker start mojipic-redis
```

で Docker 内の Redis を起動し、

```
docker stop mojipic-redis
```

で終了させることができる。

# 【講義】 ImageMagick という画像変換ツールについて

環境構築の最後に ImageMagick という画像変換ツールを学ぶ。

**Image Magick**　は、画像ファイルを作成したり、編集したり、組み合わせたり、多くの画像フォーマット間の変換をしてくれるツール。
ライセンスは Apach 2.0 が採用されている。

C 言語などから利用できる API もサポートするが、コマンドラインからも利用できる。

画像の編集と言っても、本当にできることは非常に膨大。

* 画像の拡大や縮小
* 画像の回転
* 色調の変更やボカシ
* 切り取りや結合
* 図形画像の生成
* テキストの書き込み
* 画像のデコレーション
* アニメーション GIF の作成

以上以外にもできることが多岐に渡る。

この ImageMagick は非常に高速に動作するため、多くの画像を利用する Web サービスで利用される。

最近は Amazon Elastic Transcoder などのクラウドサービスを利用して画像を変換し、Amazon S3 や Amazon CloudFront などの配信サービスで画像を配信するという手法も一般的になりつつある。
しかし、ある程度規模が出てきた際に自身でリソースを管理したい場合には、この ImageMagick というツールがまだまだ利用される。

# 【実装】 ImageMagick のインストール

今回は、コマンドラインというインタフェースを使って ImageMagick を利用していきたいため、Docker を利用せずにインストールする。

Mac であれば Homebrew でインストールする。

```
brew install ImageMagick
```

以上でターミナルよりインストールされる。
ImageMagick の 7 系がインストールされればOK。

ターミナルで

```
$ convert --version
Version: ImageMagick 7.1.0-2 Q16 x86_64 2021-06-25 https://imagemagick.org
Copyright: (C) 1999-2021 ImageMagick Studio LLC
License: https://imagemagick.org/script/license.php
Features: Cipher DPC HDRI Modules OpenMP(4.5) 
Delegates (built-in): bzlib fontconfig freetype gslib heic jng jp2 jpeg lcms lqr ltdl lzma openexr png ps tiff webp xml zlib
```

以上のように表示されればOK。

convert コマンドは、実は ImageMagick バージョン 6 までのCLI。
実はバージョン 7 からは、magick コマンドが利用されており、これらは内容としては全く同一のもの。
ただし、Java のライブラリから ImageMagick を利用する際は、この convert コマンドを利用する必要があ利、ここではその動作を確かめている。
ImageMagick を利用するにあたり、今回はCLIを利用する Java のライブラリである im4java という LGPL のライブラリを利用する。
convert コマンドはこの im4java を利用するために必要となる。

# 【実装】 ImageMagick のコマンドラインを利用した操作

無事 ImageMagick がインストールできたので、早速使ってみよう。
ここでは、convert コマンドの元の magick コマンドを利用する。
convert コマンドは magick コマンドのシンボリックリンクではあるが、Windows ではシステムの convert コマンとど干渉しないので便利。

まずは、ImageMagick を利用してロゴ画像を出力してみる。

```
magick logo: logo.gif
```

を実行してみよう。
これで logo.gif が出力される。

次に、mojipic で利用するためのテロップを入れる機能を試してみる。
なお、テロップを入れるためにはフォントを用意する必要がある。
フォントは、瀬戸フォントを利用する。
ライセンスは SIL Open Font License で、フォントそのものを販売しない限りは商用・非商用も問わず改変や再配布も認められている。

sp-setofontをダウンロードしてzipファイルを解凍し、`sp-setofont.ttf`ファイルを先ほどの`logo.gif`と同じフォルダに配置する。

以下のコマンドで`logo.gif`にテロップを入れてみる。

```
magick logo.gif -gravity south -font './sp-setofont.ttf' -pointsize 30 \
 -stroke '#000C' -strokewidth 2 -annotate 0 'プログラムを魔法か何かと勘違いしている' \
 -stroke  none   -fill white    -annotate 0 'プログラムを魔法か何かと勘違いしている' \
 logo_annotate.gif
```

これで`logo_annotate.gif`というファイル名で出力されれば成功。

このコマンドでは、フォントを指定しサイズや色・位置を指定した上で、画像にテロップを入れる処理をする。

これで mojipic を開発するための環境が整ったので、実際に mojipic プロジェクトを　Play Framework を利用して実装していく。

# まとめ

* Docker を使うと、コンテナ技術を利用して仮想 OS 上の隔離空間に高速にアプリケーションを導入して起動できる
* MySQL は太陽のアクセスに強く、レプリケーションやパーティショニングなどの大規模 Web アプリケーション向けの機能が充実した RDBMS である
* Redis はインメモリの key-value ストアで、キャッシュやメッセージングに利用できる。また大規模 Web アプリケーション向けのクラスタリングの機能を持ち合わせている