Docker ComposeでMySQLがスタンバイになる前に他コンテナが接続しエラーとなってしまう場合の対処

2023-04-02Docker

はじめに

Web アプリケーションでは全てと言っていいほど Web サーバーと DB サーバーを利用することとなります。

単一の物理サーバーに Web サーバー、DB サーバーの機能を同居していたのも今は昔。

最近では Docker を使った環境構築が主流となっています。

物理的には従来同様単一の物理サーバー上で稼働していたとしても、仮想的には複数のコンテナが稼働しているというイメージです。

そのような環境では、Docker Compose を使って両サービスを稼働させることがあります。

しかし、DB サーバー、例えば MySQL がスタンバイとなる前に他コンテナが接続しようとしてエラーとなってしまう場合があります。

このような場合の対処法を説明します。

検証環境

$ uname -moi
arm64 unknown Darwin

$ bash -version | head -n 1
GNU bash, バージョン 5.2.15(1)-release (aarch64-apple-darwin22.1.0)

$ docker-compose version
Docker Compose version v2.13.0

Docker Compose を使って DB コンテナと DB クライアントを立ち上げた場合に遭遇する問題

再現方法

Docker Compose を使って DB コンテナと DB クライアントを立ち上げた場合、DB クライアントから DB コンテナに接続できない場合があります。

例えば、以下のような MySQL コンテナとそれを利用するクライアントコンテナを立ち上げる docker-compose.yaml を用意したとします。

YAML ファイルの内容をかんたんに説明しておきます。

  • 2 つのコンテナを立ち上げる
    • server コンテナは MySQL のサーバーを立ち上げる
    • client コンテナは server コンテナに接続してデータベースを表示 ( show databases ) する
version: "3"
services:
  server:
    container_name: server
    image: mysql/mysql-server:5.7
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: test
      MYSQL_USER: test_user
      MYSQL_PASSWORD: test_password

  client:
    container_name: client
    image: mysql/mysql-server:5.7
    command: mysql -h server -u test_user -ptest_password -e "show databases;"

この docker-compose.yaml を実行してコンテナを立ち上げます。

$ docker-compose up
[+] Running 2/0
 ⠿ Container server                Created 0.0s
 ⠿ Container client                Recreated 0.0s
 ⠋ server The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested  0.0s
 ⠋ client The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested  0.0s
Attaching to client, server
server  | [Entrypoint] MySQL Docker Image 5.7.40-1.2.10-server
client  | [Entrypoint] MySQL Docker Image 5.7.40-1.2.10-server
client  | mysql: [Warning] Using a password on the command line interface can be insecure.
client  | ERROR 2003 (HY000): Can't connect to MySQL server on 'server' (111)
client exited with code 1
server  | [Entrypoint] Initializing database
server  | 2023-03-22T10:00:16.786716Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).

server コンテナはこの後無事に起動しましたが、 client コンテナの立ち上げに失敗していることがわかります。

原因

client コンテナはなぜエラーとなるのでしょうか?

これは server コンテナが起動を待たずに client コンテナが DB アクセスをしているためです。

client と server のコンテナの起動は以下の順序となる必要があります。

  1. server コンテナを起動する
  2. server コンテナが起動を完了したら、client コンテナを起動する

このように、server コンテナの起動の完了を待ってから client コンテナを起動する必要があります。

Docker Compose の "depends_on" を使った場合に遭遇する問題

再現方法

起動順序が正しくなるように 先程の YAML ファイルを修正してみましょう。

client コンテナに depends_on という属性を追加します。

version: "3"
services:
  server:
    container_name: server
    image: mysql/mysql-server:5.7
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: test
      MYSQL_USER: test_user
      MYSQL_PASSWORD: test_password

  client:
    container_name: client
    image: mysql/mysql-server:5.7
    command: mysql -h server -u test_user -ptest_password -e "show databases;"
    depends_on:
      - server

このように修正することで、server コンテナが起動の開始を待ってから client コンテナが起動するようになります。

docker-compose down -v で先程起動していたコンテナたちを一度終了させてから、再度 docker-compose up を実行してみましょう。

$ docker-compose up
[+] Running 2/0
 ⠿ Container server                                                                                                                                      Created 0.0s
 ⠿ Container client                                                                                                                                      Recreated 0.0s
 ⠋ server The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested  0.0s
 ⠋ client The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested  0.0s
Attaching to client, server
server  | [Entrypoint] MySQL Docker Image 5.7.40-1.2.10-server
client  | [Entrypoint] MySQL Docker Image 5.7.40-1.2.10-server
client  | mysql: [Warning] Using a password on the command line interface can be insecure.
client  | ERROR 2003 (HY000): Can't connect to MySQL server on 'server' (111)
client exited with code 1
server  | [Entrypoint] Initializing database
server  | 2023-03-22T10:00:16.786716Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).

実行した結果、またもやエラーが発生しました。

原因

depends_on の挙動に注意が必要です。

depends_on を使えば起動順序を制御できます。しかし、 depends_on 属性を最もシンプルな記述で設定しただけでは問題が解消しません。

先程の設定の意味は、 server コンテナの起動が 「開始」 されたら、 client コンテナを起動する、という意味になります。

ここまで「コンテナの起動の開始」「コンテナの起動の完了」と表現を分けたのには理由があります。

コンテナの起動の開始とは、コンテナが起動した時点です。この時点では、コンテナのサービスがまだ起動していない可能性があります。MySQL サーバーは起動開始直後には、データベースの初期化が行われるため、 client コンテナはまだ server コンテナに接続できません。

コンテナの起動の完了とは、コンテナのサービスが完全に起動した時点です。この時点でようやく、MySQL サーバーが完全に起動しているため、 client コンテナは server コンテナに接続できるようになります。

解決方法

先に触れたように、 「コンテナの起動の開始」 を待つのではなく、 「コンテナの起動の完了」 を待つように設定します。

depends_on 属性のサブ属性である condition 属性を設定することでこの問題を解消できます。
condition 属性のデフォルト値は service_startd です。これを service_healthy という値に変更します。

condition: service_healthy を設定する場合、依存先のコンテナの設定に追加しなければいけない属性があります。

healthcheck:
  test: mysql -h server -u test_user -ptest_password -e "show databases;"
  interval: 5s
  timeout: 5s
  retries: 3
  start_period: 5s

この設定を追加することで、 server コンテナが 「完全に起動した」 状態を待つことができます。
各属性についてもかんたんに触れておきます。

  • test: ヘルスチェックを行うコマンド
  • interval: ヘルスチェックを行う間隔
  • timeout: ヘルスチェックを行う時間
  • retries: ヘルスチェックを行う回数
  • start_period: 最初のヘルスチェックを開始するまでの時間

修正後の "docker-compose.yaml" 設定

version: "3"
services:
  server:
    container_name: server
    image: mysql/mysql-server:5.7
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: test
      MYSQL_USER: test_user
      MYSQL_PASSWORD: test_password
    healthcheck:
      # test: mysql -u root -proot -e "show databases;" でも良いが、いずれにしてもこれらで外部からの通信が完全に準備完了かというと厳密ではない
      test: mysql -h server -u test_user -ptest_password -e "show databases;"
      interval: 5s
      timeout: 5s
      retries: 3
      start_period: 5s

  client:
    container_name: client
    image: mysql/mysql-server:5.7
    command: mysql -h server -u test_user -ptest_password -e "show databases;"
    depends_on:
      server:
        condition: service_healthy

この設定では、 client コンテナは server コンテナが 「完全に起動した」 状態を待つようになります。これで、 client コンテナが server コンテナに接続できるようになります。

★厳密には内部からの接続ができる状態となったが、外部から接続できる状態かはまだ疑問の余地が残る判定にはなっています。

まとめ

Docker Compose を使用すると、複数のコンテナを簡単に管理できます。
また、コンテナ間の依存関係を設定することで、コンテナ間の連携を簡単に行えます。
これらの機能を活用することで、複数のコンテナを効率的に管理できます。

ただし、複数のコンテナが連携し合うため、起動順序を正しく設定する必要があります。

そのための属性として、 depends_oncondition があります。これらを使用することで、コンテナ間の依存関係を設定し、コンテナ間の連携することができます。

ひとこと

コンテナ技術は、今後もさらなる発展を続けていくでしょう。
Docker Compose を使用することで、複数のコンテナを効率的に管理できます。
コンテナ間の依存関係を設定することで、コンテナ間の連携を簡単に行えます。

今後も、コンテナ技術を活用して、より効率的な開発を行っていきましょう!

2023-04-02Docker