【问题标题】:docker-compose cannot wait for mysql databasedocker-compose 不能等待 mysql 数据库
【发布时间】:2020-07-08 11:14:46
【问题描述】:

我在尝试获取 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.

【问题讨论】:

  • 检查Control startup and shutdown order in Composedepends_on 等待 mydb 服务启动,直到启动 web 服务,但此时 mydb 服务可能还没有准备好。您需要在 Web 应用程序中处理这种情况。在生产中,DB 可能会在某个时候变得不可用,而应用程序应该能够从这种情况中恢复
  • 嗨,斯特凡,感谢您的回答。因为它是一个 Django 项目,所以我需要在启动服务器之前运行迁移等。不幸的是,我已经尝试过健康检查,但在版本 3 和一些等待脚本中似乎存在问题,但它们似乎并没有等待数据库准备好。我想我可以写一个 python 脚本,看看数据库是否准备好了,但这感觉就像一个可怕的解决方案。

标签: mysql django docker docker-compose


【解决方案1】:

我在 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 - 我将等待的命令添加到我的撰写文件中。

    这应该意味着我可以为我的网站调出一组测试容器,我可以在其中指定使用的 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
    

    等待.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,只测试过两次。
    • 另外,我尝试使用类似 sh -c "./bin/wait-for mydb:3306 -t 120...." 的等待脚本,但我不断收到一条消息日志显示“操作超时”。
    【解决方案3】:

    另一种选择是使用脚本来控制启动顺序,并包装网络服务的命令。

    docker-compose's documentation 中的“wait-for-it”是推荐的工具之一,但其他存在。

    【讨论】:

      【解决方案4】:

      出于测试/开发目的,您可以使用具有运行状况检查的 MySQL 映像版本(我相信有一个 healthcheck/mysql 映像),或配置您自己的(参见此处的示例:Docker-compose check if mysql connection is ready)。

      对于生产用途,您不想在启动时升级数据库架构,也不想假设数据库已启动。升级模式会自动鼓励您不要考虑部署错误并需要回滚时会发生什么,并且并行模式升级将不起作用。加长版:https://pythonspeed.com/articles/schema-migrations-server-startup/

      【讨论】:

      • 感谢您的回答,我确实尝试了健康检查,但我认为它们的能力在版本 3 中有所降低,并且等待脚本是推荐的解决方案。我也无法让它们与 MySQL 数据库一起使用。
      猜你喜欢
      • 2019-04-23
      • 1970-01-01
      • 1970-01-01
      • 2021-10-10
      • 2021-04-24
      • 1970-01-01
      • 2023-02-02
      • 2021-06-24
      • 2017-11-18
      相关资源
      最近更新 更多