Docker Compose无法等待MySQL数据库

3

我在尝试使用docker-compose脚本启动mysql数据库和Django项目时遇到了问题,但是要让Django项目等待mysql数据库准备就绪。

我有两个文件,一个是Dockerfile,一个是docker-compose.yml,下面是它们的内容。

当我运行docker-compose.yml,并检查Web容器的日志时,它会说无法连接到数据库mydb。 但是第二次运行它(不清除容器和镜像),它可以正确连接并且Django应用程序正常工作。

我花了一整天尝试各种方法,如脚本,健康检查等,但是我无法使其正常工作。

Dockerfile

FROM python:3.6

ENV PYTHONUNBUFFERED 1

RUN mkdir /code
WORKDIR /code

COPY ./ /code/

RUN pip install -r requirements.txt
RUN python manage.py collectstatic --noinput

docker-compose.yml

version: '3'

services:
  mydb:
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_USER=django
      - MYSQL_PASSWORD=secret
      - MYSQL_DATABASE=dbMarksWebsite
    image: mysql:5.7
    ports:
      # Map default mysql port 3306 to 3308 on outside so that I can connect
      # to mysql using workbench localhost with port 3308
      - "3308:3306"
  web:
    environment:
      - DJANGO_DEBUG=1
      - DOCKER_PASSWORD=secret
      - DOCKER_USER=django
      - DOCKER_DB=dbMarksWebsite
      - DOCKER_HOST=mydb
      - DOCKER_PORT=3306
    build: .
    command: >
      sh -c "sleep 10 &&
             python manage.py migrate &&
             python manage.py loaddata myprojects_testdata.json &&
             python manage.py runserver 0.0.0.0:8080"
    ports:
      - "8080:8080"
    depends_on:
      - mydb

第一次运行(没有现有映像或容器):

...
  File "/usr/local/lib/python3.6/site-packages/MySQLdb/__init__.py", line 84, in Connect
    return Connection(*args, **kwargs)
  File "/usr/local/lib/python3.6/site-packages/MySQLdb/connections.py", line 179, in __init__
    super(Connection, self).__init__(*args, **kwargs2)
django.db.utils.OperationalError: (2002, "Can't connect to MySQL server on 'mydb' (115)")

第二次运行:

System check identified no issues (0 silenced).
March 27, 2020 - 16:44:57
Django version 2.2.11, using settings 'ebdjango.settings'
Starting development server at http://0.0.0.0:8080/
Quit the server with CONTROL-C.

请查看Compose中的控制启动和关闭顺序depends_on等待mydb服务启动后再启动web服务,但此时mydb服务可能还没有准备好。您需要在Web应用程序中处理这种情况。在生产环境中,数据库可能会在某些时候不可用,应用程序应该能够从这种情况中_恢复_。 - Stefan Golubović
嗨,Stefan,感谢你的回答。因为这是一个Django项目,在启动服务器之前我需要运行迁移等操作。不幸的是,我尝试了健康检查,但在版本3中似乎存在问题,还有一些等待脚本,但它们似乎并没有等待数据库准备好。我想我可以编写一个Python脚本,只是为了检查数据库是否准备就绪,但那感觉像是一个可怕的解决方案。 - MarkyMark1000
4个回答

3

我在entrypoint.sh中使用了以下函数来解决它:

function wait_for_db()
{
  while ! ./manage.py sqlflush > /dev/null 2>&1 ;do
    echo "Waiting for the db to be ready."
    sleep 1
  done
}

2
对于任何感兴趣的人,我找到了一个解决方案:

1 - 我编写了一个Python脚本,每秒连接一次数据库,但设置了超时时间。我将这个超时时间设置得相当高,为60秒,但在我的电脑上似乎可以正常工作。

2 - 我将等待命令添加到我的compose文件中。

这应该意味着我可以启动一组测试容器来测试我的网站,其中我可以指定使用的Python和MySQL的确切版本。
下面列出了相关文件:
Dockerfile:
FROM python:3.6

ENV PYTHONUNBUFFERED 1

RUN mkdir /code
WORKDIR /code

COPY ./ /code/

RUN pip install -r requirements.txt
RUN python manage.py collectstatic --noinput

docker-compose.yml

version: '3'

services:
  mydb:
    container_name: mydb
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_USER=django
      - MYSQL_PASSWORD=secret
      - MYSQL_DATABASE=dbMarksWebsite
    image: mysql:5.7
    ports:
      # Map default mysql port 3306 to 3308 on outside so that I can connect
      # to mysql using workbench localhost with port 3308
      - "3308:3306"
  web:
    container_name: web
    environment:
      - DJANGO_DEBUG=1
      - DOCKER_PASSWORD=secret
      - DOCKER_USER=django
      - DOCKER_DB=dbMarksWebsite
      - DOCKER_HOST=mydb
      - DOCKER_PORT=3306
    build: .
    command: >
      sh -c "python ./bin/wait-for.py mydb 3306 django secret dbMarksWebsite 60  &&
             python manage.py migrate &&
             python manage.py loaddata myprojects_testdata.json &&
             python manage.py runserver 0.0.0.0:8080"
    ports:
      - "8080:8080"
    depends_on:
      - mydb

wait-for.py

'''
I don't like adding this in here, but I cannot get the typical wait-for scripts
to work with MySQL database in docker, so I hve written a python script that
either times out after ? seconds or successfully connects to the database
The input arguments for the script need to be:

    HOST, PORT, USERNAME, PASSWORD, DATABASE, TIMEOUT

'''
import sys, os
import time
import pymysql

def readCommandLineArgument():
    '''
    Validate the number of command line input arguments and return the
    input filename
    '''

    # Get arguments
    if len(sys.argv)!=7:
        raise ValueError("You must pass in 6 arguments, HOST, PORT, USERNAME, PASSWORD, DATABASE, TIMEOUT")

    # return the arguments as a tuple
    return (sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5], sys.argv[6]) 

def connectToDB(HOST, PORT, USERNAME, PASSWORD, DATABASE):
    '''
    for now, just try to connect to the database.
    '''

    con = pymysql.connect(host=HOST, port=PORT, user=USERNAME, password=PASSWORD, database=DATABASE)

    with con:
        cur = con.cursor()
        cur.execute("SELECT VERSION()")

def runDelay():
    '''
    I don't like passing passwords in, but this is only used for a test docker
    delay script
    '''

    # Get the database connection characteristics.
    (HOST, PORT, USERNAME, PASSWORD, DATABASE, TIMEOUT) = readCommandLineArgument()

    # Ensure timeout is an integer greater than zero, otherwise use 15 secs a default
    try:
        TIMEOUT = int(TIMEOUT)
        if TIMEOUT <= 0:
            raise("Timeout needs to be > 0")
    except:
        TIMEOUT = 60

    # Ensure port is an integer greater than zero, otherwise use 3306 as default
    try:
        PORT = int(PORT)
        if PORT <= 0:
            raise("Port needs to be > 0")
    except:
        PORT = 3306

    # Try to connect to the database TIMEOUT times
    for i in range(0, TIMEOUT):

        try:
            # Try to connect to db
            connectToDB(HOST, PORT, USERNAME, PASSWORD, DATABASE)

            # If an error hasn't been raised, then exit
            return True

        except Exception as Ex:
            strErr=Ex.args[0]
            print(Ex.args)
            # Sleep for 1 second
            time.sleep(1)

    # If I get here, assume a timeout has occurred
    raise("Timeout")


if __name__ == "__main__":

    runDelay()

我忘了提到,这个Python脚本依赖于pymysql,并且只测试过两次。 - MarkyMark1000
另外,我尝试了使用类似 sh -c "./bin/wait-for mydb:3306 -t 120...." 的 wait-for 脚本,但是日志中不断出现“操作超时”的消息。 - MarkyMark1000

1

为了测试/开发目的,您可以使用带有健康检查的MySQL镜像版本(我相信有一个healthcheck/mysql镜像),或者配置自己的(请参见此处的示例:Docker-compose check if mysql connection is ready)。

对于生产用途,您不希望在启动时升级数据库架构,也不希望假定数据库已经启动。自动升级架构会鼓励您不考虑部署错误并需要回滚时会发生什么,并且并行架构升级将无法正常工作。更详细的版本:https://pythonspeed.com/articles/schema-migrations-server-startup/


谢谢你的回答,我确实尝试了健康检查,但我认为它们在版本3中的能力已经降低,而等待脚本是推荐的解决方案。但我也无法让它们与MySQL数据库一起工作。 - MarkyMark1000

0
另一个选择是使用脚本来控制启动顺序,并包装 Web 服务的命令。
docker-compose 的文档 中,"wait-for-it" 是推荐的工具之一,但还有其他工具可用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接