dockerに入門してみる(その7)

前回の記事はこちら↓ ren-opdev.hatenablog.com

チュートリアルを超える内容が少しあったので、目次を付けてみますm(__)m

今回は、「MySQLをApplication Stackに加える」ためにアプリケーションのマルチコンテナ化に挑戦します。
ちなみにApplication Stackとは、「ある目的を達成するために用いるソフトウェア群」を指すようです。www.techopedia.com

マルチコンテナ化をするには幾つか理由があります。

  • フロントエンドとデータベースの実装を切り離せる。
  • コンテナという単位でバージョン管理が出来る。
  • development環境ではローカルを使い、production環境ではマネージドサービスを使う、といった変更が容易である。
  • そもそも一つのコンテナで複数プロセスを回すことが煩雑さの原因となる。

上記の理由から、今回はToDoアプリとデータベースをそれぞれコンテナにパッケージングして実装していきます。

ちなみに、コンテナはデフォルトでは他のコンテナと独立していてやり取りができないため、コンテナ同士を同じネットワークに配置することで、コンテナ同士の疎通を実現します。
コンテナをネットワーク上に配置するには、1)起動時に配置する方法 と、2)既にあるコンテナを接続する方法 があります。今回は、1)の手法を試していきます。

MySQLコンテナを用意する

まず、ネットワークを作ります。

PS C:\Users\530lo\Documents\docker\tutorial\app\app> docker network create todo-app
02cbb30fa67ef761135ee9ebe21b21ac649f6ba4b2652c72b6f3fff99fe5ea4a

特にログはなく、ネットワークに固有の値っぽいものが吐き出されました。
次に、下記コマンドでMySQLコンテナを起動し、ネットワークに配置します。ここで、データベース初期化に必要な環境変数も定義します。

docker run -d `
    --network todo-app --network-alias mysql `
    -v todo-mysql-data:/var/lib/mysql `
    -e MYSQL_ROOT_PASSWORD=secret `
    -e MYSQL_DATABASE=todos `
    mysql:5.7

-v todo-mysql-data:/var/lib/mysqlで、まだ定義していないボリュームをマウントしようとしていますが、このように書くだけでDockerが自動的にボリュームを作り、マウントしてくれるようです。スゴイですね。

データベースが構成され立ち上がったのかどうかを確かめるため、コンテナ内でmysqlコマンドを叩きます。パスワードを求められるので、上記で指定したsecretを入力します。

PS C:\Users\530lo\Documents\docker\tutorial\app\app> docker exec -it 18d797997125 mysql -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.32 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> 

続いて、データベースを確認してみます。

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| todos              |
+--------------------+
5 rows in set (0.00 sec)

データベースが構成されていることが確認できましたね。

MySQLコンテナと接続する

ネットワーク内のコンテナにアクセスするには、そのコンテナのIPアドレスを知る必要があります。
そのために、nicolaka/netshootコンテナを用います。これは、ネットワーク関連の便利ツールがまとまっているコンテナです。READMEにめちゃくちゃ使い方が書いてあってスゴイですね。

下記の通り、実行してみました。

PS C:\Users\530lo\Documents\docker\tutorial\app\app> docker run -it --network todo-app nicolaka/netshoot
Unable to find image 'nicolaka/netshoot:latest' locally
latest: Pulling from nicolaka/netshoot
cbdbe7a5bc2a: Already exists                                                                                            fa7edde5704a: Pull complete                                                                                             d142e371ed28: Pull complete                                                                                             db6c3597a95e: Pull complete                                                                                             468b5d8bd548: Pull complete                                                                                             1540e3cf45e1: Pull complete                                                                                             676bb9b891dd: Pull complete                                                                                             bc4557056759: Pull complete                                                                                             Digest: sha256:52dcf922fc8d419c23f677f07cb0f2467f2157b92448f35bda15f471026ad476
Status: Downloaded newer image for nicolaka/netshoot:latest
                    dP            dP                           dP
                    88            88                           88
88d888b. .d8888b. d8888P .d8888b. 88d888b. .d8888b. .d8888b. d8888P
88'  `88 88ooood8   88   Y8ooooo. 88'  `88 88'  `88 88'  `88   88
88    88 88.  ...   88         88 88    88 88.  .88 88.  .88   88
dP    dP `88888P'   dP   `88888P' dP    dP `88888P' `88888P'   dP

Welcome to Netshoot! (github.com/nicolaka/netshoot)
root @ /
 [1] 🐳  →     

クジラマークのプロンプト! 🐳 こんなこともできるんですね~。
ここで、ネットワーク作成時に--network-alias mysqlと設定していたmysqlエイリアスを用いて、IPアドレスを調べてみます。

root @ /
 [1] 🐳  → dig mysql

; <<>> DiG 9.14.12 <<>> mysql
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 18996
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;mysql.                         IN      A

;; ANSWER SECTION:
mysql.                  600     IN      A       172.18.0.2

;; Query time: 1 msec
;; SERVER: 127.0.0.11#53(127.0.0.11)
;; WHEN: Tue Dec 29 07:26:37 UTC 2020
;; MSG SIZE  rcvd: 44

ANSWER SECTIONにある172.18.0.2が、目当てのIPアドレスのようです。
IPアドレスを知ることができましたが、今回は上述の通りmysqlというエイリアスを被せているため、このIPアドレス自体を打ち込むのではなくmysqlというホストネームを使用すればデータベースに接続することが可能です。

MySQLと共にアプリケーションを実行する

では、いよいよToDoアプリコンテナとMySQLコンテナを紐づけていきます。
まず、以下で使用していく環境変数について説明します。

環境変数 意味
MYSQL_HOST 起動中のMySQLサーバーのホストネーム
MYSQL_USER MySQLサーバーへの接続に使うユーザーネーム
MYSQL_PASSWORD MySQLサーバーへの接続に使うパスワード
MYSQL_DB 接続後に使用するデータベース

下記コマンドより、コンテナを実行します。

PS C:\Users\530lo\Documents\docker\tutorial\app\app> docker run -dp 3000:3000 `
>>   -w /app -v "$(pwd):/app" `
>>   --network todo-app `
>>   -e MYSQL_HOST=mysql `
>>   -e MYSQL_USER=root `
>>   -e MYSQL_PASSWORD=secret `
>>   -e MYSQL_DB=todos `
>>   node:12-alpine `
>>   sh -c "yarn install && yarn run dev"
10c150eded9cd36d707ef2e65d47dcc1909ed805f8a0f24b727311706dc31717

コンテナログを見てみるとConnected to mysql db at host mysqlとあり、実際に接続されたことが分かります。

PS C:\Users\530lo\Documents\docker\tutorial\app\app> docker logs 10c150eded9c
yarn install v1.22.5
[1/4] Resolving packages...
success Already up-to-date.
Done in 0.46s.
yarn run v1.22.5
$ nodemon src/index.js
[nodemon] 1.19.2
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): *.*
[nodemon] starting `node src/index.js`
Waiting for mysql:3306.
Connected!
Connected to mysql db at host mysql
Listening on port 3000

http://localhost:3000/ にアクセスし、データを入れてみます。

f:id:renkataoka:20201229170346p:plain

そして、先ほどと同様にMySQLコンテナに入り、正しくレコードが登録されたかを確認します。

mysql> select * from todo_items;
+--------------------------------------+-----------+-----------+
| id                                   | name      | completed |
+--------------------------------------+-----------+-----------+
| a7ef25b8-11e3-456d-9770-f329c90a7b4b | sample!   |         0 |
| 786c96d1-b708-4905-b03f-72527a5fce5e | CHAPTER 7 |         0 |
+--------------------------------------+-----------+-----------+
2 rows in set (0.01 sec)

アプリケーションコンテナからMySQLコンテナに接続し、データを保存できたことを確認できました。

日本語を入力できない問題

チュートリアルの通り進めていくと、MySQLが日本語をIncorrect string valueだ!と言って受け付けてくれません。実際、MySQLの状態を見てみると

mysql> status
--------------
mysql  Ver 14.14 Distrib 5.7.32, for Linux (x86_64) using  EditLine wrapper

Connection id:          8
Current database:       todos
Current user:           root@localhost
SSL:                    Not in use
Current pager:          stdout
Using outfile:          ''
Using delimiter:        ;
Server version:         5.7.32 MySQL Community Server (GPL)
Protocol version:       10
Connection:             Localhost via UNIX socket
Server characterset:    latin1
Db     characterset:    latin1
Client characterset:    latin1
Conn.  characterset:    latin1
UNIX socket:            /var/run/mysqld/mysqld.sock
Uptime:                 1 hour 16 min 4 sec

Threads: 2  Questions: 68  Slow queries: 0  Opens: 109  Flush tables: 1  Open tables: 102  Queries per second avg: 0.014
--------------

Server characterset: latin1のように、文字コードlatin1になっています。
文字コードを変えれば良さそうなのですが、cnfファイルをいじらなきゃいけない…??

qiita.com

と思ったら、Docker公式のMySQLリポジトリ内にConfiguration without a cnf fileというパラグラフが見つかりました。

公式が「cnfファイルを使わなくてもこのオプション使えばUTF-8にできるよ」というオプションを用意してくれているようです。感謝。。
というわけで、新しいコンテナを起動していきます。

PS C:\Users\530lo\Documents\docker\tutorial\app\app> docker run -d `
>> --network todo-app --network-alias mysql_v2 `
>> -e MYSQL_ROOT_PASSWORD=secret `
>> -e MYSQL_DATABASE=todos_ja `
>> mysql:5.7 `
>> --character-set-server=utf8mb4 `
>> --collation-server=utf8mb4_unicode_ci
ff39d7d156c04fdf37d020891b483df44537ba8ae00d8810bd42b276d193a4f2

無事起動できているようなので、細かい確認手順は省いて、アプリケーションコンテナと再度結び付けてみます。

PS C:\Users\530lo\Documents\docker\tutorial\app\app> docker run -dp 3000:3000 `
>>   -w /app -v "$(pwd):/app" `
>>   --network todo-app `
>>   -e MYSQL_HOST=mysql_v2 `
>>   -e MYSQL_USER=root `
>>   -e MYSQL_PASSWORD=secret `
>>   -e MYSQL_DB=todos_ja `
>>   node:12-alpine `
>>   sh -c "yarn install && yarn run dev"
9e1a01749ef688a05fdf43e30ad7efcb32403c987e76ee94fbd8cce8cb9de88b

無事コンテナを実行できたようです。

f:id:renkataoka:20201229173732p:plain

データベースの状態↓

mysql> select * from todo_items;
+--------------------------------------+------------------------------+-----------+
| id                                   | name                         | completed |
+--------------------------------------+------------------------------+-----------+
| c719ab12-0f2b-4ced-985b-abfd0d3982e0 | ??????????????               |         0 |
| f4948b12-3dea-4e34-bc5c-25b9cbef7af3 | ????????                     |         0 |
| 71b71092-42c5-4d04-88d0-512d280f7891 | Happy New Year!              |         0 |
| f11a222b-71ce-4438-8648-53e6862eb09f |  You can also enter English! |         0 |
+--------------------------------------+------------------------------+-----------+
4 rows in set (0.00 sec)

部分的にutf-8に変更したため、完全に対応できたとは言えませんが、一旦は日本語入力もいけましたね。。!
(また違うタイミングでデータベースのことをちゃんと勉強したいです)


データ保持専用のコンテナを作りコンテナ同士を結びつけられたところで、このチャプターは終わりです。
今はまだコンテナごとに立ち上げていますが、次のチャプターではDocker Composeを勉強して、Application Stackという単位で操作する手段を身に着けられるそうです。

マルチコンテナをググった際に日本語版のページが出てきてビックリしましたが、翻訳が機械っぽいし、このチュートリアルもあともう少しなので、
感想を交えたこの一連のブログが101 Tutorialの日本人向け資料として誰かの役に立つことを信じてやり進めます。。