複数コンテナのアプリ

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

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

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

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

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

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

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

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

コンテナについて思い出しましょう。デフォルトでは、 孤立した状態(in isolation) で実行するため、同じマシン上の他のプロセスやコンテナを一切知りません。それでは、どのようにして他のコンテナと通信できるのでしょうか? 答えは ネットワーク機能( networking ) です。これであなたがネットワークエンジニアになる必要はありません(やったね!)。単純にこのルールを忘れないでください……。

注釈

2つのコンテナが同じネットワーク上にあれば、お互いに通信できます。そうでなければ、どちらも通信できません。

MySQL の起動

コンテナをネットワークに加えるには、2つの方法があります。1つは、起動する前に割り当てるか、もう1つは、既存のコンテナに接続します。さしあたり、まずはネットワークを作成し、それから MySQL コンテナの起動時に接続します。

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

    $ docker network create todo-app
    
  1. 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:5.7
    

    Macbook M1 チップ / Apple Silicon のような ARM ベースのチップを使う場合は、こちらのコマンドを使います。

    $ docker run -d \
        --network todo-app --network-alias mysql \
        --platform "linux/amd64" \
        -v todo-mysql-data:/var/lib/mysql \
        -e MYSQL_ROOT_PASSWORD=secret \
        -e MYSQL_DATABASE=todos \
        mysql:5.7
    

    Windows を使う場合は、 PowerShell でこちらのコマンドを使います。

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

    また、 --network-alias フラグも指定したのが見えるでしょう。こちらについては、後で触れます。

    ちなみに

    ここでは 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)
    

    MySQL シェルを終了し、マシン上のシェルに戻ります。

    $ exit
    

    やった! todo データベースが手に入りましたので、使う準備が調いました!

MySQL に接続

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

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

    $ docker run -it --network todo-app nicolaka/netshoot
    
  1. コンテナの中で、便利な DNS ツールの dig コマンドを使います。ホスト名 mysql の IP アドレスを調べましょう。

    $ dig mysql
    

    そうすると、次のような出力があります。

    ; <<>> DiG 9.14.1 <<>> 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 とアプリを動かす

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

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

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

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

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

注釈

環境変数を通した接続設定

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

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

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

説明を全て終えたところで、開発に対応したコンテナを起動しましょう!

  1. 注意 : MySQL 8.0 以上では、 mysql の中で以下のコマンドを実行する必要があります。

    mysql> ALTER USER 'root' IDENTIFIED WITH mysql_native_password BY 'secret';
    mysql> flush privileges;
    
  1. 先ほど環境変数をそれぞれ指定したのと同様に、コンテナをアプリのネットワークに接続します。

    $ 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:18-alpine \
      sh -c "yarn install && yarn run dev"
    

    Windows を使っている場合は、PowerShell でこちらのコマンドを使います。

    PS> 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:18-alpine `
      sh -c "yarn install && yarn run dev"
    
  1. コンテナのログを確認すると( docker logs <container-id> )、mysql データベースの使用を示すメッセージが表示されるでしょう。

    $ 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`
    Connected to mysql db at host mysql
    Listening on port 3000
    
  1. ブラウザでアプリを開き、todo リストにいくつかのアイテムを追加します。

  1. mysql データベースに接続し、アイテムがデータベースに書き込まれているのを確認します。思い出してください、パスワードは secret です。

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

    おそらく、アイテムが異なるため、表の見た目は違うでしょう。ですが、そこに保管されているのが見えます!

Docker ダッシュボードをさっと見ると、2つのアプリ用コンテナが動いているのが見えます。ですが、1つのアプリとして一緒のグループだとは分かりません。近いうちに改善する方法を見ていきます!

ダッシュボードには、グループ化されていない2つのコンテナが表示

まとめ

これで、別のコンテナで実行中の外部データベースに、アプリケーションは新しいデータを保管できるようになりました。コンテナのネットワーク機能を少々学び、それから、DNS を使った処理で、サービス ディスカバリをどのようにして行うのかを見てきました。

しかし、このアプリケーションを起動するための全てに対し、少々の圧倒を感じ始めているのではないでしょうか。行ったのは、ネットワークを作成し、コンテナを起動し、全ての環境変数を指定し、ポートを公開する等々です! 覚えることが多すぎますし、誰かに正確に伝えるのは大変です。

次のセクションでは、 Docker Compose について説明します。 Docker Compose があれば、より簡単な方法でアプリケーション スタックを共有でき、他の人もコマンドを1つ(かつシンプルに)実行するだけで、アプリを速攻で立ち上げられます!