複数コンテナのアプリ

これまでは1つのコンテナでアプリを動かしていました。しかし、これからはアプリケーション スタックに MySQL を追加しようとしています。そうすると、たいてい次の疑問が沸き上がります。「どこで MySQL を実行するのですか? 同じコンテナにインストールするのかな、それとも別々に実行するの?」と。通常、1つ1つのコンテナが、1つのことをしっかりと実行すべきです 。以下はコンテナを分けて実行する理由です。

  • データベースとは別に、 API とフロントエンドをスケールする良い機会

  • コンテナを分けると、現在のバージョンと更新したバージョンを分離できる

  • 今はローカルにあるデータベースをコンテナが使っているが、プロダクションではデータベースのマネージド サービスを利用したくなるかもしれない

  • 複数のプロセスを実行するにはプロセスマネージャが必要であり(コンテナは1つのプロセスのみ起動するため)、コンテナの起動や停止が複雑になる

ほかにも、いくつかの理由があります。それでは、下図のように動作するようアプリケーションを更新します。

Todo アプリは MySQL コンテナに接続

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

コンテナについて思い出しましょう。デフォルトでは、 孤立した状態(in isolation) で実行するため、同じマシン上の他のプロセスやコンテナを一切知りません。それでは、どのようにして他のコンテナと通信できるのでしょうか? 答えは ネットワーク機能( networking )であり、お互いに通信可能になります。

MySQL の起動

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

  • コンテナの起動時にネットワークを割り当てる

  • 既に実行しているコンテナをネットワークに接続する

以下の手順では、まずネットワークを作成し、それから MySQL コンテナの起動時に接続(アタッチ)します。

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

    $ docker network create todo-app
    
  1. MySQL コンテナを起動し、先ほどのネットワークに接続します。あわせて複数の環境変数を定義します。これは、データベースの初期化に使います。MySQL の環境変数について学ぶには、 MySQL Docker Hub にある「Environment Variables」セクションをご覧ください。

    Mac / Linux

    $ 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
    

    Windows

    Windows では PowerShell 上でコマンドを実行します。

    $ 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 フラグも指定したのが見えるでしょう。こちらについては、後で触れます。

    Tip

    ここでは todo-mysql-data という名前のボリュームを使い、 MySQL が自身のデータを保管する /var/lib/mysql をマウントしているのに気づくでしょう。しかしまだ、 docker volume create コマンドを実行していません。名前付きボリュームを使おうとすると、 Docker が認識し、自動的にボリュームを作成します。

  1. データベースが起動して実行中なのを確認するには、データベースに接続し、つながっているかを確認します。

    $ docker exec -it <mysql-container-id> mysql -u root -p
    

    パスワードのプロンプトが表示されたら、 secret と入力します。 MySQL のシェル内では、データベース一覧を表示すると、 todo データベースの存在が確認できます。

    mysql> SHOW DATABASES;
    

    このような出力が見えるでしょう。

    +--------------------+
    | Database           |
    +--------------------+
    | information_schema |
    | mysql              |
    | performance_schema |
    | sys                |
    | todos              |
    +--------------------+
    5 rows in set (0.00 sec)
    
  2. MySQL シェルを終了し、マシン上のシェルに戻ります。

    mysql> exit
    

todo データベースが手に入りましたので、いつでも使う準備が調いました。

MySQL に接続

MySQL の起動と実行方法が分かりましたので、次は使います。しかし、どのように使うのでしょうか? 同じネットワーク上に他のコンテナを実行したとして、どのようにして MySQL のコンテナを見つけられるのでしょうか? 各コンテナは自身の IP アドレスを持つのを思い出してください。

この疑問に答え、コンテナのネットワーク機能について良く理解するため、 nicolaka/netshoot コンテナを使います。これにはネットワーク機能の問題に対するトラブルシューティング(問題解決)やデバッグ(修正)に役立つツールがたくさん入っています。

  1. nicolaka/netshoot イメージを使う新しいコンテナを起動します。必ず同じネットワークに接続します。

    $ docker run -it --network todo-app nicolaka/netshoot
    
  1. コンテナの中で、便利な 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」に、 mysqlA レコードがあり、 172.23.0.2 (おそらく似たような値が表示されているでしょう)に解決されているのが分かります。 mysql は通常のホスト名としては有効ではありませんが、 Docker はコンテナの IP アドレスをネットワーク エイリアスで調べられます。先ほど --network-alias フラグを使ったのを思い出してください。

    これが意味するのは、アプリはシンプルにホスト名 mysql へ接続できればよいので、これでデータベースと通信できます。

MySQL とアプリを動かす

MySQL への接続する設定を指定するため、todo アプリは環境変数の設定をサポートしています。

  • MYSQL_HOST - MySQL サーバを実行中のホスト名

  • MYSQL_USER - 接続に使うユーザ名

  • MYSQL_PASSWORD - 接続に使うパスワード

  • MYSQL_DB - 接続先として使うデータベース

注釈

環境変数を使った接続設定は、開発環境であれば通常は問題ありませんが、本番環境でアプリケーションの実行時は 極めて推奨されません 。Docker の正式セキュリティ リード(lead) の Diogo Monica は、何故なのかを 素晴らしいブログ投稿を書き 説明しています。

多くのセキュリティ機構は、コンテナ オーケストレーション フレームワークによって シークレット(secret) のサポートを提供しています。ほとんどの場合、これらシークレットは、実行中のコンテナ内にファイルとしてマウントされます。多くのアプリケーションは( MySQL イメージと todo アプリも含みます)、変数を含むファイルを示すため、 _FILE が末尾に付く環境変数もサポートしています。

たとえば、 MYSQL_PASSWORD_FILE で設定した値は、アプリが接続用のパスワードとして、参照するファイルの内容を使いたいとします。ですが、Docker はこれらの環境変数を何らサポートしません。アプリ自身が変数を調べ、ファイル内容を取得する必要があります。

それでは開発に対応したコンテナを起動します。

  1. 先ほどの環境変数に加え、コンテナをアプリのネットワークに接続する指定をします。次のコマンドを実行する時は、 getting-started/app ディレクトリにいるのを確認します。

    Mac / Linux

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

    Windows

    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:18-alpine `
       sh -c "yarn install && yarn run dev"
    
  1. コンテナのログを確認すると( docker logs <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
    
  1. ブラウザでアプリを開き、todo リストにいくつかのアイテムを追加します。

  1. 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つ、かつシンプルに実行するだけで、アプリを速攻で立ち上げられます。