DockerでDBの設定が完了するまでアプリケーションのコンテナを立ち上げないようにしようとしたらハマった


はじめに

個人的にDjangoの環境をDockerで一発で整えられるようにしたいので、いろいろ試行錯誤しています。
GitHubリポジトリ

DockerでDBから何からまとめて管理しようとするときにはいくつかハマる箇所があると思いますが、そのひとつにDockerコンテナを立ち上げたときに、データベースの初期設定が完了してないのにアプリケーションコンテナがデータベースに接続しにいって失敗し、一生再起動し続けるというものがあります。

一見、アプリケーションコンテナ側の設定でdepends_onにデータベースを入れとけば良さそうですが、depends_onは起動順序のみを保証し、コンテナ内部の処理は全く感知してくれないので、自力で起動制御する必要があります。
参考:http://docs.docker.jp/compose/startup-order.html

その対策を行う上でいくつかハマったところがあったのでご紹介します。

環境

  • Django
  • MariaDB
  • nginx(今回は関係ない)
  • gunicorn(今回は関係ない)

ハマったこと

  1. Debianのリポジトリからmysql-clientが消え去っていた
  2. bashでmysqlコマンドを打つときにダブルクォーテーションをうまく使えなかった
  3. docker-composeのentrypointで、wait-for-it.shを実行するとき、引数でコマンドを渡そうと思ったらうまく渡らなかった

基本的な戦略

  • Djangoコンテナの起動時にshellscriptでMariaDBに対して疎通確認を行う
  • まだ繋がらないなら待つ
  • 繋がったらDjangoのコマンド実行!

1. Debianのリポジトリからmysql-clientが消え去っていた

文字通りです。最初はDjango用のコンテナにmysql-clientをインストールしてmysqlコマンドで疎通確認をしようとしていました。

Dockerfile
RUN apt-get update && apt-get install -y mysql-client

するとこんなエラーが、、、

E: Package 'mysql-client' has no installation candidate

調べてみるとDebianのリポジトリからmysql-clientが消えていました😭
Debianリポジトリ:https://packages.debian.org/stable/database/
代わりにdefault-mysql-clientとmariadb-clientができていたので、mariadb-clientをインストールすることにしました。

Dockerfile
RUN apt-get update && apt-get install -y mariadb-client

2. bashでmysqlコマンドを打つときにダブルクォーテーションをうまく使えなかった

Dockerの公式サイトをみると、DBの準備が整うまで待つにはこうするといいよ!と書かれていました。

#!/bin/bash

set -e

host="$1"
shift
cmd="$@"

until psql -h "$host" -U "postgres" -c '\l'; do
  >&2 echo "Postgres is unavailable - sleeping"
  sleep 1
done

>&2 echo "Postgres is up - executing command"
exec $cmd

なのでPostgreSQLをMariaDB用に変えて以下のようにしていました。

wait-for-mariadb.sh
#!/bin/bash

set -e
. ./mariadb.env #MariaDB用の環境ファイル
host="$1"
shift
cmd="$@"

until mysql -h "$host" -u "$MYSQL_USER" -p "$MYSQL_PASSWORD" -e "show databases;" "$MYSQL_DATABASE" ; do
    >&2 echo "MariaDB is unavailable -sleeping"
    sleep 1
done

>&2 echo "MariaDB is up -executing command"
exec $cmd

まあこれが普通に間違いなんですが、、、
問題はuntilの行で、実際には以下のように実行されます(変数には適当に入れてます)。

wait-for-mariadb.sh
mysql -h sample -u sampleuser -p samplepassword -e "show databases;" sampledatabase

mysqlのコマンドでは、-optionなら引数をクォーテーションで囲う、--optionなら=でつなぐように書くので、これだと正しく実行されません。
参考:https://mariadb.com/kb/en/mysql-command-line-client/

正しくは以下のように実行されなければいけません。

wait-for-mariadb.sh
mysql -h "sample" -u "sampleuser" -p "samplepassword" -e "show databases;" sampledatabase

これで小一時間くらいハマり、原因がやっとわかったので、ダブルクォーテーションをエスケープすればいいと考えて、以下のように変えてみました。

wait-for-mariadb.sh
~略~
until mysql -h "\"$host\"" -u "\"$MYSQL_USER\"" -p "\"$MYSQL_PASSWORD\"" -e "show databases;" "$MYSQL_DATABASE" ; do
~略~

しかしこれでもうまくいかなかったので(だれか教えてください泣)、結局以下のようにしました。

wait-for-mariadb.sh
~略~
until mysql --host="$host" --user="$MYSQL_USER" --password="$MYSQL_PASSWORD" -e "show databases;"  "$MYSQL_DATABASE" ; do
~略~

3. docker-composeのentrypointで、wait-for-xxx.shを実行するとき、引数でコマンドを渡そうと思ったらうまく渡らなかった

最初、docker-composeの設定を以下のようにしていました。

docker-compose.yml
version: '3'
services:
    django:
        ~略~
        entrypoint: ./wait-for-mariadb.sh mariadb "python manage.py migrate && gunicorn my_docker_django_project.wsgi -b 0.0.0.0:3031"

これで2番目の引数にコマンドが渡って、それをそのまま実行すればいいと思ってました。

wait-for-mariadb.sh
shift
cmd="$@"
exec $cmd

するとこんなエラーが😭

django_1   | python manage.py migrate && gunicorn my_docker_django_project.wsgi -b 0.0.0.0:3031
django_1   | usage: manage.py migrate [-h] [--noinput] [--database DATABASE] [--fake]
django_1   |                          [--fake-initial] [--plan] [--run-syncdb] [--version]
django_1   |                          [-v {0,1,2,3}] [--settings SETTINGS]
django_1   |                          [--pythonpath PYTHONPATH] [--traceback] [--no-color]
django_1   |                          [--force-color] [--skip-checks]
django_1   |                          [app_label] [migration_name]
django_1   | manage.py migrate: error: unrecognized arguments: my_docker_django_project.wsgi -b 0.0.0.0:3031

どうやら&&以降のgunicornコマンドがpython manage.py migrateの引数だと思われてるようです。。。
migrateしないとアプリケーションサーバは起動してほしくないので、結局以下のようにshellscriptにベタ書きすることにしました。

wait-for-mariadb.sh
#!/bin/bash

set -e
. ./mariadb.env

host="$1"
shift
cmd="$@"

until mysql --host="$host" --user="$MYSQL_USER" --password="$MYSQL_PASSWORD" -e "show databases;"  "$MYSQL_DATABASE" ; do
    >&2 echo "MariaDB is unavailable -sleeping"
    sleep 1
done

>&2 echo "MariaDB is up -executing command"
python manage.py migrate && gunicorn my_docker_django_project.wsgi -b 0.0.0.0:3031

exec $cmd

最後に

よかったら定期的にGitHub更新していくので見ていってください:)
https://github.com/keii1111/my_docker_django

参考情報

Compose の起動順番を制御