マルチコンテナアプリケーション
ここまで、単一コンテナのアプリケーションを扱ってきました。しかし、これからはMySQLをアプリケーションスタックに追加します。次のような質問がよく出てきます。「MySQLはどこで動かすべきか?同じコンテナにインストールするか、別々に動かすか?」一般的に、各コンテナは1つのことを行い、それをうまくやるべきです。以下は、MySQLを別のコンテナで実行する理由のいくつかです。
- APIやフロントエンドは、データベースとは異なるスケーリングが必要になることが多い。
- コンテナを分けることで、バージョン管理や更新を独立して行うことができる。
- ローカルでデータベース用のコンテナを使っても、本番環境ではデータベースにマネージドサービスを使う場合がある。その場合、アプリケーションにデータベースエンジンを含めたくない。
- 複数のプロセスを実行するにはプロセスマネージャが必要となり、コンテナの起動やシャットダウンの複雑さが増す。
他にも理由があります。そのため、以下の図のようにアプリを複数のコンテナで実行するのがベストです。
コンテナネットワーキング
コンテナはデフォルトで隔離されており、同じマシン上の他のプロセスやコンテナについて何も知りません。では、1つのコンテナが別のコンテナと通信するにはどうすればよいでしょうか?その答えはネットワーキングです。2つのコンテナを同じネットワークに配置すれば、互いに通信することができます。
MySQLを起動する
コンテナをネットワークに接続する方法は2つあります。
- コンテナを起動するときにネットワークを割り当てる。
- 既に実行中のコンテナをネットワークに接続する。
次のステップでは、まずネットワークを作成し、MySQLコンテナを起動時に接続します。
-
ネットワークを作成します。
$ docker network create todo-app
-
MySQLコンテナを起動し、ネットワークに接続します。また、データベースの初期化に使用するいくつかの環境変数を定義します。MySQLの環境変数について詳しくは、MySQL Docker Hub リストの「Environment Variables」セクションを参照してください。
$ 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:8.0
このコマンドでは、--network-alias
フラグが使用されています。後のセクションでこのフラグについて詳しく学びます。
上記のコマンドには、MySQLのデータが保存される場所である /var/lib/mysql にマウントされた todo-mysql-data という名前のボリュームが含まれています。しかし、docker volume create コマンドを実行していません。Dockerは名前付きボリュームを使用したいことを認識し、自動的に作成してくれます。
-
データベースが正常に起動していることを確認するために、データベースに接続し、接続を確認します。
$ docker exec -it <mysql-container-id> mysql -u root -p
パスワードの入力を求められたら、secret と入力します。MySQLシェルでデータベースを一覧表示し、todos データベースが表示されていることを確認します。
mysql> SHOW DATABASES;
以下のような出力が表示されるはずです。
+--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | | todos | +--------------------+ 5 rows in set (0.00 sec)
-
MySQLシェルを終了して、マシンのシェルに戻ります。
mysql> exit
これで
todos
データベースが作成され、使用する準備が整いました。
MySQLに接続する
MySQLが正常に起動していることを確認したので、次はそれを使います。しかし、どうやって使うのでしょうか?同じネットワーク上で別のコンテナを実行する場合、コンテナの場所をどうやって見つけるのでしょうか?それぞれのコンテナには独自のIPアドレスがあることを忘れないでください。
上記の質問に答え、コンテナネットワーキングをよりよく理解するために、nicolaka/netshoot コンテナを使用します。このコンテナには、ネットワーキングのトラブルシューティングやデバッグに便利なツールが多数含まれています。
-
nicolaka/netshootイメージを使用して新しいコンテナを起動します。同じネットワークに接続されていることを確認してください。
$ docker run -it --network todo-app nicolaka/netshoot
-
コンテナ内で、DNSツールとして便利な
dig
コマンドを使用します。mysql
というホスト名のIPアドレスを調べます。$ dig mysql
以下のような出力が表示されるはずです。
; <<>> DiG 9.18.8 <<>> mysql ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32162 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;mysql. IN A ;; ANSWER SECTION: mysql. 600 IN A 172.23.0.2 ;; Query time: 0 msec ;; SERVER: 127.0.0.11#53(127.0.0.11) ;; WHEN: Tue Oct 01 23:47:24 UTC 2019 ;; MSG SIZE rcvd: 44
“ANSWER SECTION” に、
mysql
のA
レコードが172.23.0.2
に解決されているのがわかります(おそらく異なる値が表示されるでしょう)。mysql
は通常有効なホスト名ではありませんが、Dockerはそのネットワークエイリアスを持つコンテナのIPアドレスに解決しました。先ほど--network-alias
を使用したことを思い出してください。つまり、アプリケーションは
mysql
というホスト名に接続するだけで、データベースと通信できるということです。
アプリをMySQLで実行する
Todoアプリでは、MySQL接続設定を指定するためにいくつかの環境変数を設定できます。これらの変数は次の通りです。
MYSQL_HOST
- 実行中のMySQLサーバーのホスト名MYSQL_USER
- 接続に使用するユーザー名MYSQL_PASSWORD
- 接続に使用するパスワードMYSQL_DB
- 接続後に使用するデータベース
環境変数を使って接続設定を行うことは開発時には一般的に認められていますが、本番環境での使用は強く推奨されません。DockerのセキュリティリーダーであったDiogo Monicaが、なぜ環境変数を使って秘密データを扱うべきではないのかについての素晴らしいブログ記事を書いています。
より安全な方法は、コンテナオーケストレーションフレームワークが提供するシークレットサポートを使用することです。ほとんどの場合、これらのシークレットは実行中のコンテナにファイルとしてマウントされます。
多くのアプリ(MySQLイメージやTodoアプリを含む)では、ファイルに格納された変数を指す _FILE
サフィックス付きの環境変数もサポートしています。
例えば、MYSQL_PASSWORD_FILE
変数を設定すると、アプリは指定されたファイルの内容を接続パスワードとして使用します。Docker自体はこれらの環境変数をサポートしていません。アプリケーションが変数を探してファイルの内容を取得する必要があります。
これで開発用のコンテナを起動できます。
- 先ほどの環境変数をそれぞれ指定し、コンテナをアプリネットワークに接続します。このコマンドを実行するときは、
getting-started-app
ディレクトリにいることを確認してください。
$ docker run -dp 127.0.0.1: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:18-alpine \
sh -c "yarn install && yarn run dev"
-
コンテナのログを確認すると (
docker logs -f <container-id>
)、以下のようなメッセージが表示され、MySQLデータベースが使用されていることがわかります。$ nodemon src/index.js [nodemon] 2.0.20 [nodemon] to restart at any time, enter `rs` [nodemon] watching dir(s): *.* [nodemon] starting `node src/index.js` Connected to mysql db at host mysql Listening on port 3000
-
ブラウザでアプリを開き、todoリストにいくつかアイテムを追加します。
-
MySQLデータベースに接続し、アイテムがデータベースに書き込まれていることを確認します。パスワードは
secret
です。$ docker exec -it <mysql-container-id> mysql -p todos
MySQLシェルで、次のコマンドを実行します。
mysql> select * from todo_items; +--------------------------------------+--------------------+-----------+ | id | name | completed | +--------------------------------------+--------------------+-----------+ | c906ff08-60e6-44e6-8f49-ed56a0853e85 | Do amazing things! | 0 | | 2912a79e-8486-4bc3-a4c5-460793a575ab | Be awesome! | 0 | +--------------------------------------+--------------------+-----------+
あなたのテーブルには、自分で追加したアイテムが表示されますが、それらがデータベースに保存されていることが確認できます。
まとめ
ここまでで、データを別のコンテナで実行している外部データベースに保存するアプリケーションを作成しました。また、コンテナネットワーキングとDNSを使用したサービスディスカバリーについても学びました。
関連情報:
次のステップ
このアプリケーションを起動するために行うべき手順の多さに、少し圧倒されているかもしれません。ネットワークを作成し、コンテナを起動し、すべての環境変数を指定し、ポートを公開し、他にもさまざまなことを覚えておく必要があります。これらを誰かに共有するのは簡単ではありません。
次のセクションでは、Docker Composeについて学びます。Docker Composeを使えば、アプリケーションのスタックをもっと簡単に共有でき、他の人も1つのシンプルなコマンドでそれらを起動できるようになります。