マルチコンテナーアプリ

これまでは 1 つのコンテナーからなるアプリを使って作業を進めてきました。 ここからはアプリケーションに MySQL を加えることにします。 ところが以下の質問があがってきます。 「MySQL はどこで動かすの?」 「同じコンテナー内にインストール? それとも別に動かす?」 原則を言います。 1 つのコンテナーでは 1 つのことだけを行います。 そうするのが一番うまくいきます。 その理由はいくつかあります。

  • データベースとは別にして、API やフロントエンドをスケーリングしなければならないなら、ちょうど良い機会です。
  • コンテナーを別々にすれば、バージョン管理や更新操作を個別に行うことができます。
  • 1 つのコンテナーをデータベース用としてローカルで利用し、本番環境ではサービスを利用してデータベースを管理することができます。 そうすればデータベースをアプリとともに提供する必要がありません。
  • プロセスを複数実行するにはプロセスマネージャーが必要です(コンテナーが起動するプロセスは 1 つです)。 そうなるとコンテナーの起動や停止が複雑になります。

理由はもっとあります。 では以下の図を見ましょう。 こういうときのアプリ実行を実現するにはマルチコンテナーです。

Todo アプリから MySQL コンテナーへの接続

コンテナーのネットワーク

コンテナーについてもう一度確認します。 デフォルトでコンテナーは分離された状態で動作するので、それが動作するマシン上の他のプロセスや他のコンテナーのことなどまったく知りません。 それならどうやってコンテナーどうしを互いにやりとりできるようにすればよいのでしょう。 その答えはネットワークです。 2 つのコンテナーを同一のネットワーク上に置けば、両者は互いに通信することができます。

MySQL の起動

コンテナーをネットワークに加えるには 2 つの方法があります。

  • そのコンテナーの起動時にネットワークを割り当てる。
  • 既存のコンテナーをネットワークに接続する。

以下の手順では、はじめにネットワークを生成して、起動時に MySQL コンテナーをアタッチします。

  1. ネットワークを生成します。

    $ docker network create todo-app
    
  2. 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
    
    $ 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
    $ 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 フラグを利用しています。 このフラグについては後の節で説明します。

    情報

    上ではボリューム名として todo-mysql-data を指定し /var/lib/mysql にマウントしています。 このディレクトリは MySQL がデータを保存する場所です。 だからといって docker volume create コマンドは実行していません。 Docker は名前つきボリュームが指定されたことを認識して、これを自動的に生成してくれます。

  3. データベースが起動され実行していることを確認するため、データベースに接続して接続確認を行います。

    $ 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)
  4. MySQL シェルを終了して、マシン上のシェルに戻ります。

    mysql> exit
    

    todos データベースが生成できたので、これを利用していくことにします。

MySQL への接続

MySQL が起動し実行されていることが確認できたので、さっそく使ってみます。 だけど、どうやって? 同一ネットワーク上に別のコンテナーを実行したとき、そのコンテナーをどうやって認識したらよいのでしょう? 各コンテナーにはそれぞれに IP アドレスがありますが。

To answer the questions above and better understand container networking, you're going to make use of the nicolaka/netshoot container, which ships with a lot of tools that are useful for troubleshooting or debugging networking issues.

  1. nicolaka/netshoot というイメージを使ったコンテナーを新たに起動します。 ネットワークは同一のものに接続するようにします。

    $ docker run -it --network todo-app nicolaka/netshoot
    
  2. コンテナー内部にて dig コマンドを実行することにします。 これは便利な DNS ツールです。 ホスト名 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 という 1 つのレコードが 172.23.0.2 を持っているのがわかります(この IP アドレスはお手元では、まず間違いなく別の値になっているはずです)。 mysql というのは、普通は適正なホスト名ではないので、Docker はこの名前から IP アドレスを解決しています。 この IP アドレスはこのコンテナーのものであって、ネットワークエイリアスを持っていたからです。 前の手順において --network-alias フラグを用いたことを思い出してください。

    これがどういうことかと言えば、アプリとしては単にホスト名 mysql に接続できて、データベースとやりとりができさえすればよいということです。

MySQL を使ったアプリ実行

todo アプリは環境変数をいくつか設定することによって MySQL への接続をサポートしています。 それは以下のようなものです。

  • MYSQL_HOST - 実行している MySQL サーバーのホスト名。
  • MYSQL_USER - 接続に利用するユーザー名。
  • MYSQL_PASSWORD - 接続に利用するパスワード。
  • MYSQL_DB - 接続後の利用データベース。

メモ

While using env vars to set connection settings is generally accepted for development, it's highly discouraged when running applications in production. Diogo Monica, a former lead of security at Docker, wrote a fantastic blog post explaining why.

A more secure mechanism is to use the secret support provided by your container orchestration framework. In most cases, these secrets are mounted as files in the running container. You'll see many apps (including the MySQL image and the todo app) also support env vars with a _FILE suffix to point to a file containing the variable.

As an example, setting the MYSQL_PASSWORD_FILE var will cause the app to use the contents of the referenced file as the connection password. Docker doesn't do anything to support these env vars. Your app will need to know to look for the variable and get the file contents.

You can now start your dev-ready container.

  1. Specify each of the previous environment variables, as well as connect the container to your app network. Make sure that you are in the getting-started-app directory when you run this command.


    $ 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"
    

    In Windows, run this command in PowerShell.

    $ 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"

    In Windows, run this command in Command Prompt.

    $ docker run -dp 127.0.0.1:3000:3000 ^
      -w /app -v "%cd%:/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 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"
    

  2. If you look at the logs for the container (docker logs -f <container-id>), you should see a message similar to the following, which indicates it's using the mysql database.

    $ 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
    
  3. Open the app in your browser and add a few items to your todo list.

  4. Connect to the mysql database and prove that the items are being written to the database. Remember, the password is secret.

    $ docker exec -it <mysql-container-id> mysql -p todos
    

    And in the mysql shell, run the following:

    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 |
    +--------------------------------------+--------------------+-----------+
    

    Your table will look different because it has your items. But, you should see them stored there.

まとめ

At this point, you have an application that now stores its data in an external database running in a separate container. You learned a little bit about container networking and service discovery using DNS.

関連情報

次のステップ

There's a good chance you are starting to feel a little overwhelmed with everything you need to do to start up this application. You have to create a network, start containers, specify all of the environment variables, expose ports, and more. That's a lot to remember and it's certainly making things harder to pass along to someone else.

In the next section, you'll learn about Docker Compose. With Docker Compose, you can share your application stacks in a much easier way and let others spin them up with a single, simple command.