This article is ** Part.5 ** of the articles related to ** Learn how to use Docker through Django + MySQL environment construction ** It corresponds to.
In this article, I will aim to solve the problem of ** connection failure due to boot order ** that may occur when running multiple dependent services using docker-compose
.
In the articles up to Last time, I set the environment construction of Django
+ MySQL
using docker-compose.
After basic setup, when I try to start the Django server with $ docker-compose up
,
** Even though the MySQL
side isn't ready, Django
tries to connect to the DB
, and the situation doesn't progress with a connection error **. It may end up.
At the moment, it is a problem that occurs only at the time of initial execution, and if you wait for the preparation on the MySQL
side, cancel it manually once, and execute $ docker-compose up
again, the connection will be successful, but in the future To avoid having to worry about the same problem when making changes in the process of work, ** think about whether you can incorporate countermeasures in advance **.
If the connection with the DB fails, the following error will be sent and the container on the Django side will become rigid.
# (Excerpt)
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)")
It seems to be an error sent when trying to connect to a DB that does not exist. In other words, in this case, the problem is that the DB is not ready yet, so let's think about whether we can adjust the order by waiting until it can be done by some means.
By the way, Docker officially recommends solving with shell script, but this time it is the coding of Python
and the connection of mysqlclient
. For practice, I will prepare a dedicated Python
file and try to solve it **.
I prepared the following files under the config
directory.
config/wait_for_db.py
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 #Adjust the value as needed
def check_connection(count, limit):
"""
docker-Compose up Runtime function for time adjustment.
"""
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)
Next, I will write about the contents of the code.
First, install the required libraries and packages.
config/wait_for_db.py
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
For local_settings.py
, move the directory with ʻos.chdir (Path (__ file__) .parent)` and then import.
Next, define the variables.
config/wait_for_db.py
count_to_try = 0
LIMIT_OF_COUNT = 20 #Adjust the value as needed
count_to_try
is for counting the number of times the function described later is called, and LIMIT_OF_COUNT
is the number limit. Conditionally branch in the function so that the file is terminated after repeating 20 times. Adjust this value as needed.
For the contents of the main function check_connection
,
try
statement,config/wait_for_db.py
try:
conn = MySQLdb.connect(
unix_socket = "/var/run/mysqld/mysqld.sock",
user=DB_USER,
passwd=DB_PASSWORD,
host="db",
port=3306,
db=DB_NAME,
)
The try
statement ʻunix_socket is the socket used for connection, but this is displayed in the log when
$ docker-compose upis executed, so you can copy it. The rest is the same as the one set from
Django` last time.
.
config/wait_for_db.py
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.")
For the ʻexcept` statement, it counts the number of trials, waits for 3 seconds after outputting, and when it reaches 20 times, outputs the error content and terminates the file. The upper limit is set to prevent an infinite loop when there is an actual connection error. .
config/wait_for_db.py
else:
print("Connected!\n")
conn.close()
exit()
When the attempted connection is successful in the try
statement (=> ** when MySQL is ready **), the ʻelse` statement declares the connection successful, closes the connection, and then terminates the file. ..
If you can pass the ʻelsestatement, the next
runserver` command should be executed without delay.
.
config/wait_for_db.py
if __name__ == "__main__":
check_connection(count_to_try, LIMIT_OF_COUNT)
The last part is ʻif name ==" main ":, that is, ** "when this file is executed directly" **, the function
check_connection` is read. The argument is the variable defined at the beginning.
Finally, add a description inside the command
of docker-compose.yml
so that this file will be executed before the server runs.
docker-compose.yml
command: >
bash -c "
pip install -r requirements.txt &&
python config/wait_for_db.py &&
python manage.py runserver 0.0.0.0:8000
"
Now you have a program ** that waits for the execution of the ** runserver command to set up MySQL.
When I actually execute $ docker-compose up
...
# (Excerpt)
djst_django | Waiting for MySQL... ( 1 / 20 )
# (Omission...)
djst_mysql | Version: '5.7.31' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL)
djst_django | Connected!
# (Omission...)
djst_django | Starting development server at http://0.0.0.0:8000/
djst_django | Quit the server with CONTROL-C.
After waiting, I was able to successfully start the Django
server after confirming the connection.
(Repeat from the previous article, due to the setting, the actual operation check is not the displayed http://0.0.0.0:8000/
, but http://172.0.0.1 It will be done from: 8000 /
orhttp: // localhost: 8000 /
.)
With the above, the original purpose of ** adjusting the container startup timing between dependent services ** has been achieved. If you have any suggestions, I would appreciate it if you could comment.
This time, I wrote it in python for training, but I would like to challenge the shell script so that it can be used freely.
(You can return to the first page from here ↓.) Learn how to use Docker through Django + MySQL environment construction
Thank you for visiting.
Recommended Posts