この記事について
この記事は、**Django + MySQLの環境構築を通じてDockerの使い方を学ぶ**に関連する記事群の Part.5 にあたります。
- venvを利用してPythonの仮想環境を構築する
- Dockerfileの記述を考える
- docker-compose.ymlの記述を考える
- 設定ファイルを編集し docker-compose up を実行する
- 依存関係にあるサービス間のコンテナ起動タイミングを調整する ( 当記事 )
はじめに
この記事では、docker-compose
を利用して依存関係にある複数のサービスを動かすにあたって起こることのある、起動順序による接続の失敗に関する問題の解決を目指していきます。
前回までの記事にて、docker-composeを利用したDjango
+ MySQL
の環境構築設定を行いました。
基本的な設定が終わり$ docker-compose up
でDjangoサーバを起動しようとすると、
MySQL
側の準備が終わっていないのにもかかわらず、Django
がDB
への接続を試みてしまうことで、接続エラーを吐いたまま状況が進行しない状態に陥ってしまうことがあります。
現時点では、初期実行時のみに起こる問題で、MySQL
側の準備を待ってから一度手動でキャンセルしてもう一度$ docker-compose up
を実行すれば接続には成功するのですが、今後作業の過程で変更を加える際に同じ問題が起こり煩わされることのないように、予め対応策を組み込めないか考えてみます。
エラーの内容
DBとの接続に失敗すると、以下のようなエラーが送りだされ、Django側のコンテナが硬直してしまいます。
# (抜粋)
djst_django | MySQLdb._exceptions.OperationalError: (2002, "Can't connect to MySQL server on 'db' (115)")
djst_django | django.db.utils.OperationalError: (2002, "Can't connect to MySQL server on 'db' (115)")
存在しないDBへの接続を試みた場合に送出されるエラーのようです。
つまり、今回の場合はまだDBの準備が出来ていないことが問題なので、何かしらの手段で出来るまで待つことによって順番を調整出来ないか考えてみます。
ちなみにDockerの公式においてはシェルスクリプトによる解決が推奨されていますが、今回はPython
のコーディングとmysqlclient
の接続練習もかねて、専用のPython
のファイルを準備して解決を試みることにします。
ファイルの内容
全体
以下のようなファイルをconfig
ディレクトリ下に用意しました。
import os
import MySQLdb
from time import sleep
from pathlib import Path
os.chdir(Path(__file__).parent)
from local_settings import DB_NAME, DB_USER, DB_PASSWORD
count_to_try = 0
LIMIT_OF_COUNT = 20 # 値は必要に応じて調整
def check_connection(count, limit):
"""
docker-compose up実行時用、時間調整のための関数。
"""
try:
conn = MySQLdb.connect(
unix_socket = "/var/run/mysqld/mysqld.sock",
user=DB_USER,
passwd=DB_PASSWORD,
host="db",
port=3306,
db=DB_NAME,
)
except MySQLdb._exceptions.OperationalError as e:
count += 1
print("Waiting for MySQL... (", count, "/ 20 )")
sleep(3)
if count < limit:
check_connection(count, limit)
else:
print(e)
print("Failed to connect mySQL.")
else:
print("Connected!\n")
conn.close()
exit()
if __name__ == "__main__":
check_connection(count_to_try, LIMIT_OF_COUNT)
記述について
続いて、コードの内容について書いていきます。
まずは必要なライブラリやパッケージをインストールします。
import os
import MySQLdb
from time import sleep
from pathlib import Path
os.chdir(Path(__file__).parent)
from local_settings import DB_NAME, DB_USER, DB_PASSWORD
local_settings.py
に関してはos.chdir(Path(__file__).parent)
でディレクトリを移動してからimportしています。
次に変数を定義します。
count_to_try = 0
LIMIT_OF_COUNT = 20 # 値は必要に応じて調整
count_to_try
は後述する関数が呼び出された回数をカウントするためのもので、LIMIT_OF_COUNT
はその回数制限です。20回繰り返したのちにファイルを終了するように関数内で条件分岐をします。この値は必要に応じて調整して使います。
メインとなる関数check_connection
の内容については、
-
try
文にてMySQLへの接続を試し、 - 失敗した場合は
except
文にて接続試行回数をカウントして同一関数へ再帰させ、 - 成功した場合は
else
文にて接続を終了しこのファイルを閉じる
といった流れになります。
try:
conn = MySQLdb.connect(
unix_socket = "/var/run/mysqld/mysqld.sock",
user=DB_USER,
passwd=DB_PASSWORD,
host="db",
port=3306,
db=DB_NAME,
)
try
文のunix_socket
については接続に利用するソケットですが、これは$ docker-compose up
実行時のログに表示があるので、それを書き写せばOKです。あとは前回Django
から設定したものと同様です。
.
except MySQLdb._exceptions.OperationalError as e:
count += 1
print("Waiting for MySQL... (", count, "/ 20 )")
sleep(3)
if count < limit:
check_connection(count, limit)
else:
print(e)
print("Failed to connect mySQL.")
except
文については、試行回数をカウント、出力してから3秒待機し、20回に到達したらエラー内容を出力してファイルを終了します。上限を設けたのは、実際に接続エラーがある場合の無限ループを防ぐためです。
.
else:
print("Connected!\n")
conn.close()
exit()
try
文において試行した接続が成功した時 ( => MySQLの準備が出来た時 ) はelse
文で接続成功を宣言し、接続を閉じたあと、ファイルを終了します。
else
文を通過することが出来た場合は、次に控えるrunserver
コマンドが滞りなく実行されるはずです。
.
if __name__ == "__main__":
check_connection(count_to_try, LIMIT_OF_COUNT)
末尾の部分は、if __name__ == "__main__":
、つまり**「このファイルが直接実行された時は」**、関数check_connection
を読み込むという記述です。引数は冒頭で定義した変数です。
最後に、サーバー実行前にこのファイルが実行されるよう、docker-compose.yml
のcommand
内に記述を追加します。
command: >
bash -c "
pip install -r requirements.txt &&
python config/wait_for_db.py &&
python manage.py runserver 0.0.0.0:8000
"
以上で、runserverコマンドの実行をMySQLのセットアップまで待機させるプログラムの準備が出来ました。
実際に$ docker-compose up
を実行してみると・・・
# (抜粋)
djst_django | Waiting for MySQL... ( 1 / 20 )
# (中略...)
djst_mysql | Version: '5.7.31' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL)
djst_django | Connected!
# (中略...)
djst_django | Starting development server at http://0.0.0.0:8000/
djst_django | Quit the server with CONTROL-C.
待機ののち、接続確認後に無事Django
サーバーを起動できました。
( 前回の記事から繰り返しになりますが、設定の都合上、実際の動作確認は表示されているhttp://0.0.0.0:8000/
ではなく、http://127.0.0.1:8000/
かhttp://localhost:8000/
から行うことになります。 )
終わりに
以上で、依存関係にあるサービス間のコンテナ起動タイミングを調整するという当初の目的は達成できました。
何かご指摘などありましたら、コメントいただけますと幸いです。
今回についてはトレーニングもかねてpythonで記述しましたが、シェルスクリプトの方も自在に使いこなせるよう挑戦していきたいです。
(こちら↓から最初のページに戻れます。)
Django + MySQLの環境構築を通じてDockerの使い方を学ぶ
ご覧いただきありがとうございました。