[Docker] Adjust container startup timing between dependent services [python]

About this article

This article is ** Part.5 ** of the articles related to ** Learn how to use Docker through Django + MySQL environment construction ** It corresponds to.

  1. Build a Python virtual environment using venv
  2. Consider the description of Dockerfile
  3. Consider the description of docker-compose.yml
  4. Edit the configuration file and execute docker-compose up
  5. ** Adjust container startup timing between dependent services (this article) **

Introduction

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 **.

Details of the error

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 **.

File contents

The entire

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)

About the description

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,

  1. Try connecting to MySQL with the try statement,
  2. If it fails, count the number of connection attempts with the ʻexcept` statement and recurse to the same function.
  3. If successful, terminate the connection with the ʻelse` statement and close this file. It will be the flow.

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 fromDjango` 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 nextrunserver` 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 /.)

At the end

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

[Docker] Adjust container startup timing between dependent services [python]
Adjust font differences between Qt for Python OS
Put Python3 in Docker container of Amazon Linux2
Use python in Docker container as Pycharm interpreter
From easy git installation to docker startup python
Try Python interactive mode inside a Docker container