マルチコンテナーアプリ

これまでは 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 アドレスがありますが。

上の問いに対する答えを出すには、コンテナーネットワークについてより深く理解しておく必要があります。 そこで nicolaka/netshoot コンテナーというものを用いることにしましょう。 これはネットワークに発生する問題に対して、トラブルシューティングやデバッグを行うために便利なツールをいろいろ用意しています。

  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 - 接続後の利用データベース。
メモ

DB 接続設定を環境変数を通じて行うのは、一般的には開発環境でのみ許容されることです。 本番環境においてアプリケーションを動作させる際に、この方法を用いることは推奨されません。 Docker 社の全セキュリティリーダー Diogo Monica が、なぜそうなのかを 優れたブログポスト において説明しています。

よりセキュアなメカニズムとするには、コンテナーオーケストレーションのフレームワークが提供する secret サポートを用いることです。 この secret というものは、たいていは実行コンテナー内のファイルとしてマウントされます。 この後に見ていくアプリ (MySQL イメージや todo アプリ) では、変数を定義するファイルを _FILE というサフィックスをつけた環境変数に設定する方法もサポートしています。

たとえば MYSQL_PASSWORD_FILE という変数を設定します。 これは DB 接続時のパスワードを含めたファイルをアプリが参照するようにしているものです。 Docker はこういった環境変数の対応は一切行いません。 変数の定義先を探しファイル内容を読み取るのは、実装するアプリが行うべきことと覚えておいてください。

そこで開発向けコンテナーを起動させます。

  1. 先に述べた環境変数を個々に設定します。 そしてコンテナーをアプリケーションネットワークに接続させます。 このコマンドは 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:lts-alpine \
      sh -c "yarn install && yarn run dev"
    

    Windows においてこのコマンドを 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:lts-alpine `
      sh -c "yarn install && yarn run dev"

    Windows においてこのコマンドを 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:lts-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:lts-alpine \
      sh -c "yarn install && yarn run dev"
    
  2. コンテナーのログを確認すると (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
    
  3. ブラウザー上でアプリを開き、todo リストにいくつかアイテムを加えます。

  4. 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 を使うと、アプリケーションを極めて簡単に共有でき、誰にとってもたった一つのシンプルなコマンドでアプリケーションを起動できるようになります。

Docker Compose の利用