【问题标题】:How do I know when my docker mysql container is up and mysql is ready for taking queries?我如何知道我的 docker mysql 容器何时启动并且 mysql 已准备好接受查询?
【发布时间】:2014-10-19 15:16:49
【问题描述】:

我正在部署几个不同的 docker 容器,mysql 是第一个。我想在数据库启动后立即运行脚本并继续构建其他容器。该脚本一直失败,因为它试图在设置 mysql(来自this official mysql container)的入口点脚本仍在运行时运行。

sudo docker run --name mysql -e MYSQL_ROOT_PASSWORD=MY_ROOT_PASS -p 3306:3306 -d mysql
[..] wait for mysql to be ready [..]
mysql -h 127.0.0.1 -P 3306 -u root --password=MY_ROOT_PASS < MY_SQL_SCRIPT.sql

有没有办法等待 docker 容器内的 entrypoiny mysql 设置脚本完成的信号? Bash sleep 似乎不是最佳解决方案。

编辑:使用这样的 bash 脚本。不是最优雅和最野蛮的力量,但就像一种魅力。也许有人会觉得这很有用。

OUTPUT="Can't connect"
while [[ $OUTPUT == *"Can't connect"* ]]
do
    OUTPUT=$(mysql -h $APP_IP -P :$APP_PORT -u yyy --password=xxx <       ./my_script.sql 2>&1)
done

【问题讨论】:

  • 添加一个bash循环来检查mysql服务状态?我认为这是最好的。
  • 谢谢@fabrizioM。我采用了这种方法。
  • 这个问题已经回答here

标签: mysql bash shell docker


【解决方案1】:

您可以安装 mysql-client 软件包并使用 mysqladmin ping 目标服务器。在使用多个 docker 容器时很有用。结合 sleep 并创建一个简单的等待循环:

while ! mysqladmin ping -h"$DB_HOST" --silent; do
    sleep 1
done

【讨论】:

  • 这是一件美好的事情。也适用于 Docker 健康检查:docker run --health-cmd='mysqladmin ping --silent' -d mysql
  • 为了等待单元容器健康,我使用了脚本while [ $(docker inspect --format "{{json .State.Health.Status }}" &lt;container-name&gt;) != "\"healthy\"" ]; do printf "."; sleep 1; done
  • 这在这里不起作用; mysqladmin ping 在我可以实际使用数据库之前成功 - 我猜容器仍在运行是初始化 .sql 脚本。
  • 我没有在 docker 镜像中安装 mysqladmin,但是 wget 也完成了这项工作:while ! wget mysql:3306; do sleep 1 done
  • 如果您在 localhost 上尝试它并不起作用,请尝试“127.0.0.1”。当使用“localhost”时,mysqladmin 会尝试使用套接字而不是默认 TCP 端口 3306 进行连接。
【解决方案2】:

这个小 bash 循环等待 mysql 打开,不需要安装任何额外的包:

until nc -z -v -w30 $CFG_MYSQL_HOST 3306
do
  echo "Waiting for database connection..."
  # wait for 5 seconds before check again
  sleep 5
done

【讨论】:

  • 不错的一个。我将它用作 1 班轮:直到 nc -z $CFG_MYSQL_HOST 3306;睡一觉; echo "等待 DB 出现...";完成
  • 端口可用并不意味着服务器已准备好接受连接。 mysqladmin ping 是这里的正确答案。
  • 通常容器尽可能小,mysql不可用。 @QuolonelQuestions 对于一些罕见的情况,您可能是正确的。即使对于可预测的 docker 环境,nc 解决方案也足够精确。我投票支持轻量级 nc。
【解决方案3】:

这在 cmets 中或多或少地提到了其他答案,但我认为它值得拥有自己的条目。

首先你可以通过以下方式运行你的容器:

docker run --name mysql --health-cmd='mysqladmin ping --silent' -d mysql

Dockerfile 中还有一个equivalent

使用该命令,您的docker psdocker inspect 将向您显示容器的健康状态。特别是对于 mysql,此方法的优点是 mysqladmin 在容器内可用,因此您无需将其安装在 docker 主机上。

然后您可以简单地循环输入 bash 脚本以等待状态变为健康状态。以下 bash 脚本由 Dennis 创建。

function getContainerHealth {
  docker inspect --format "{{.State.Health.Status}}" $1
}

function waitContainer {
  while STATUS=$(getContainerHealth $1); [ $STATUS != "healthy" ]; do 
    if [ $STATUS == "unhealthy" ]; then
      echo "Failed!"
      exit -1
    fi
    printf .
    lf=$'\n'
    sleep 1
  done
  printf "$lf"
}

现在您可以在脚本中执行此操作:

waitContainer mysql

您的脚本将等到容器启动并运行。如果容器变得不健康,脚本将退出,这是可能的,例如 docker host 内存不足,因此 mysql 无法为自己分配足够的内存。

【讨论】:

  • 我发现这是最好的方法,因为它只依赖于 Docker,因此跨平台问题应该更少。我遇到的唯一一件事是图像入口点启动了 mysqlserver 两次——一次是裸露的,一次是初始化的。 mysqladmin ping 也捕获了第一个旋转,这可能是您不想要的。在我的情况下,使用您期望的模式运行虚拟查询效果最好,即将运行状况命令更改为 mysql -u root -e "use your_schema;"
  • 如果您的容器有healthcheck,则此方法有效。但如果你不这样做,.State.Health.Status 就不存在。您可能不得不改用.State.Status;但这表示 running 太早满足此 OP 的需求了。
  • @JesseChisholm 嗯?你觉得--health-cmd 是干什么用的?
  • 你也可以使用healthcheck docker-compose docs.docker.com/compose/compose-file/#healthcheck
【解决方案4】:

有时端口的问题是端口可能打开,但数据库还没有准备好。

其他解决方案需要您在主机中安装 mysql oa mysql 客户端,但实际上您已经在 Docker 容器中安装了它,所以我更喜欢使用像这样:

while ! docker exec mysql mysqladmin --user=root --password=root --host "127.0.0.1" ping --silent &> /dev/null ; do
    echo "Waiting for database connection..."
    sleep 2
done

【讨论】:

  • 这似乎是最好和最短的选择,但是只是 ping 对我不起作用,之后仍然没有准备好,所以我使用了命令:mysql -u root -proot -e 'status' &amp;&gt; /dev/null 而不是 mysqladmin ping
【解决方案5】:

我发现使用mysqladmin ping 方法并不总是可靠的,尤其是在您创建新数据库时。在这种情况下,即使您能够 ping 服务器,如果仍在初始化用户/权限表,您也可能无法连接。相反,我会执行以下操作:

while ! docker exec db-container mysql --user=foo --password=bar -e "SELECT 1" >/dev/null 2>&1; do
    sleep 1
done

到目前为止,我还没有遇到这种方法的任何问题。我看到 VinGarcia 在对 mysqladmin ping 答案之一的评论中提出了类似的建议。

【讨论】:

  • 何时和/或如何执行这样的脚本?在 Dockerfile 中?我不明白...
【解决方案6】:

以下运行状况检查适用于我所有的 mysql 容器:

db:
    image: mysql:5.7.16
    healthcheck:
      test: ["CMD-SHELL", 'mysql --database=$$MYSQL_DATABASE --password=$$MYSQL_ROOT_PASSWORD --execute="SELECT count(table_name) > 0 FROM information_schema.tables;" --skip-column-names -B']
      interval: 30s
      timeout: 10s
      retries: 4
    extends:
        file: docker-compose-common-config.yml
        service: common_service

【讨论】:

    【解决方案7】:

    一个使用 curl 的衬里,在所有 linux 发行版上都可以找到:

    while ! curl -o - db-host:3306; do sleep 1; done
    

    【讨论】:

    • 这是迄今为止最好的解决方案。我想补充一点,容器需要能够访问 curl。 RUN apt-get update &amp;&amp; apt-get upgrade -y &amp;&amp; apt-get install curl -y
    【解决方案8】:

    所以我不确定是否有人发布了这个。它看起来不像任何人都有,所以...... mysqladmin中有一个具有等待功能的命令,它处理连接测试,然后在内部重试并在完成时返回成功。

    sudo docker run --name mysql -e MYSQL_ROOT_PASSWORD=MY_ROOT_PASS -p 3306:3306 -d mysql
    mysqladmin ping -h 127.0.0.1 -u root --password=MY_ROOT_PASS --wait=30 && mysql -h 127.0.0.1 -P 3306 -u root --password=MY_ROOT_PASS < MY_SQL_SCRIPT.sql
    

    重要的部分是mysqladmin ping -h 127.0.0.1 -u root --password=MY_ROOT_PASS --wait=30 -v--wait 是等待连接成功的标志,而数字是重试尝试的次数。

    理想情况下,您会在 docker 容器内运行该命令,但我不想过多地修改原始海报命令。

    在我的 make 文件中用于初始化时

    db.initialize: db.wait db.initialize
    
    
    db.wait:
      docker-compose exec -T db mysqladmin ping -u $(DATABASE_USERNAME) -p$(DATABASE_PASSWORD) --wait=30 --silent
    
    db.initialize:
      docker-compose exec -T db mysql -u $(DATABASE_USERNAME) -p$(DATABASE_PASSWORD) $(DATABASE_NAME) < dev/sql/base_instance.sql
    

    【讨论】:

      【解决方案9】:

      当我的 Django 容器在启动后尝试连接 mysql 容器时,我遇到了同样的问题。我使用 vishnubob 的 wait-for.it.sh 脚本解决了这个问题。它是一个等待 IP 和主机准备就绪的 shell 脚本,然后再继续。这是我用于应用程序的示例。

      ./wait-for-it.sh \
          -h $(docker inspect --format '{{ .NetworkSettings.IPAddress }}' $MYSQL_CONTAINER_NAME) \
          -p 3306 \
          -t 90
      

      在该脚本中,我要求 mysql 容器在端口 3306(默认 mysql 端口)和 docker 为我的 MYSQL_CONTAINER_NAME 分配的主机中等待最多 90 秒(准备好后它将正常运行)。该脚本有更多变量,但 mw 使用这三个变量。

      【讨论】:

        【解决方案10】:

        如果等待 mysql 容器的 docker 容器基于 python 映像(例如用于 Django 应用程序),则可以使用以下代码。

        优点是:

        • 它不是基于wait-for-it.sh,它确实等待mysql的IP和端口准备好,但这并不意味着mysql初始化已经完成。
        • 它不是基于 mysql 或 mysqladmin 可执行文件的 shell 脚本,必须存在于您的容器中:由于您的容器基于 python 映像,因此需要在该映像之上安装 mysql。通过以下解决方案,您可以使用容器中已经存在的技术:纯 python。

        代码:

        import time
        
        import pymysql
        
        
        def database_not_ready_yet(error, checking_interval_seconds):
            print('Database initialization has not yet finished. Retrying over {0} second(s). The encountered error was: {1}.'
                  .format(checking_interval_seconds,
                          repr(error)))
            time.sleep(checking_interval_seconds)
        
        
        def wait_for_database(host, port, db, user, password, checking_interval_seconds):
            """
            Wait until the database is ready to handle connections.
        
            This is necessary to ensure that the application docker container
            only starts working after the MySQL database container has finished initializing.
        
            More info: https://docs.docker.com/compose/startup-order/ and https://docs.docker.com/compose/compose-file/#depends_on .
            """
            print('Waiting until the database is ready to handle connections....')
            database_ready = False
            while not database_ready:
                db_connection = None
                try:
                    db_connection = pymysql.connect(host=host,
                                                    port=port,
                                                    db=db,
                                                    user=user,
                                                    password=password,
                                                    charset='utf8mb4',
                                                    connect_timeout=5)
                    print('Database connection made.')
                    db_connection.ping()
                    print('Database ping successful.')
                    database_ready = True
                    print('The database is ready for handling incoming connections.')
                except pymysql.err.OperationalError as err:
                    database_not_ready_yet(err, checking_interval_seconds)
                except pymysql.err.MySQLError as err:
                    database_not_ready_yet(err, checking_interval_seconds)
                except Exception as err:
                    database_not_ready_yet(err, checking_interval_seconds)
                finally:
                    if db_connection is not None and db_connection.open:
                        db_connection.close()
        

        用法:

        1. 将此代码添加到应用程序源代码中的python 文件(例如wait-for-mysql-db.py)中。
        2. 编写另一个 python 脚本(例如startup.py)首先执行上述代码,然后启动您的应用程序。
        3. 确保您的应用程序容器的 Dockerfile 将这两个 python 脚本与应用程序的源代码一起打包到一个 Docker 映像中。
        4. 在您的 docker-compose 文件中,配置您的应用程序容器:command: ["python3", "startup.py"]

        请注意,此解决方案适用于 MySQL 数据库。您需要稍微调整它以适应另一个数据库。

        【讨论】:

          【解决方案11】:

          我基于一种新方法为这个问题开发了一个新的解决方案。我发现的所有方法都依赖于反复尝试连接到数据库的脚本,或者尝试与容器建立 TCP 连接。完整的详细信息可以在waitdb 存储库中找到,但是,我的解决方案是依赖从容器中检索到的日志。该脚本会一直等待,直到日志触发消息准备连接。该脚本可以识别容器是否是第一次启动。在这种情况下,脚本会一直等待,直到执行初始数据库脚本并重新启动数据库,再次等待新的准备连接消息。我在 MySQL 5.7 和 MySQL 8.0 上测试了这个解决方案。

          脚本本身(wait_db.sh):

          #!/bin/bash
          
          STRING_CONNECT="mysqld: ready for connections"
          
          findString() {
              ($1 logs -f $4 $5 $6 $7 $8 $9 2>&1 | grep -m $3 "$2" &) | grep -m $3 "$2" > /dev/null
          }
          
          echo "Waiting startup..."
          findString $1 "$STRING_CONNECT" 1 $2 $3 $4 $5 $6 $7
          $1 logs $2 $3 $4 $5 2>&1 | grep -q "Initializing database"
          if [ $? -eq 0 ] ; then
              echo "Almost there..."
              findString $1 "$STRING_CONNECT" 2 $2 $3 $4 $5 $6 $7
          fi
          echo "Server is up!"
          

          该脚本可用于 Docker Compose 或 Docker 本身。我希望下面的例子能清楚地说明用法:

          示例 01:使用 Docker Compose

          SERVICE_NAME="mysql" && \
          docker-compose up -d $SERVICE_NAME && \
          ./wait_db.sh docker-compose --no-color $SERVICE_NAME
          

          示例 02:与 Docker 一起使用

          CONTAINER_NAME="wait-db-test" && \
          ISO_NOW=$(date -uIs) && \
            docker run --rm --name $CONTAINER_NAME \
              -e MYSQL_ROOT_PASSWORD=$ROOT_PASSWORD \
              -d mysql:5.7 && \
          ./wait_db.sh docker --since "$ISO_NOW" $CONTAINER_NAME
          

          示例 3:完整示例(测试用例)

          可以在on the test case of the repository 找到完整示例。这个测试用例将启动一个新的 MySQL,创建一个虚拟数据库,等待一切启动,然后触发 select 以检查一切是否正常。之后它将重新启动容器并等待它启动,然后触发一个新的 select 以检查它是否已准备好连接。

          【讨论】:

            【解决方案12】:

            以下是我如何将 Adams 解决方案合并到我的基于 docker-compose 的项目中:

            在我的 server 容器文件夹中创建了一个名为 db-ready.sh 的 bash 文件(其中的内容被复制到我的容器中 - server):

            #!bin/bash
            
            until nc -z -v -w30 $MYSQL_HOST 3306
            do
              echo "Waiting a second until the database is receiving connections..."
              # wait for a second before checking again
              sleep 1
            done
            

            然后我可以运行 docker-compose run server sh ./db-ready.sh &amp;&amp; docker-compose run server yarn run migrate 以确保当我在 server 容器中运行 migrate 任务时,我知道数据库将接受连接。

            我喜欢这种方法,因为 bash 文件独立于我想要运行的任何命令。我可以在使用我运行的任何其他数据库之前轻松运行db-ready.sh

            【讨论】:

              【解决方案13】:

              结合flamemyst的回答和Nathan Arthur的评论,我相信这个回答会是最方便的:

              CONTAINER_MYSQL='' # name of the MySQL container
              CONTAINER_DB_HOST='127.0.0.1'
              CONTAINER_DB_PORT=3306
              MYSQL_USER='' # user name if there is, normally 'root'
              MYSQL_PWD='' # password you set
              
              is_mysql_alive() {
                docker exec -it ${CONTAINER_MYSQL} \
                  mysqladmin ping \
                    --user=${MYSQL_USER} \
                    --password=${MYSQL_PWD} \
                    --host=${CONTAINER_DB_HOST} \
                    --port=${CONTAINER_DB_PORT} \
                  > /dev/null
                returned_value=$?
                echo ${returned_value}
              }
              
              until [ "$(is_mysql_alive)" -eq 0 ]
              do
                sleep 2
                echo "Waiting for MySQL to be ready..."
              done
              
              anything_else_to_do
              

              基本上,它会检查mysqladmin 在 MySQL 容器中是否存在,如果存在,MySQL 应该已启动。

              【讨论】:

                【解决方案14】:

                https://github.com/docker-library/mysql/blob/master/5.7/docker-entrypoint.sh docker-entrypoint.sh 暂不支持合并自定义的.sql。

                我认为您可以修改 docker-entrypoint.sh 以合并您的 sql,以便在 mysql 实例准备好后执行它。

                【讨论】:

                  【解决方案15】:

                  在您的ENTRYPOINT 脚本中,您必须检查您是否有有效的 MySQL 连接。

                  此解决方案不需要您在容器上安装 MySQL 客户端,并且在使用 php:7.0-fpm 运行容器时运行 nc 不是一个选项,因为它也必须安装。此外,检查端口是否打开并不一定意味着服务正在运行并正确公开。 [more of this]

                  所以在这个解决方案中,我将向您展示如何运行 PHP 脚本来检查 MySQL 容器是否能够连接。如果您想知道为什么我认为这是一种更好的方法,请查看我的评论 here

                  文件entrypoint.sh

                  #!/bin/bash
                  cat << EOF > /tmp/wait_for_mysql.php
                  <?php
                  \$connected = false;
                  while(!\$connected) {
                      try{
                          \$dbh = new pdo( 
                              'mysql:host=mysql:3306;dbname=db_name', 'db_user', 'db_pass',
                              array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION)
                          );
                          \$connected = true;
                      }
                      catch(PDOException \$ex){
                          error_log("Could not connect to MySQL");
                          error_log(\$ex->getMessage());
                          error_log("Waiting for MySQL Connection.");
                          sleep(5);
                      }
                  }
                  EOF
                  php /tmp/wait_for_mysql.php
                  # Rest of entry point bootstrapping
                  

                  通过运行它,您实际上是在阻止容器的任何引导逻辑,直到您拥有有效的 MySQL 连接。

                  【讨论】:

                    【解决方案16】:

                    我使用以下代码;

                    导出 COMPOSE_PROJECT_NAME=web;

                    导出 IS_DATA_CONTAINER_EXISTS=$(docker volume ls | grep ${COMPOSE_PROJECT_NAME}_sqldata);

                    docker-compose up -d;
                    docker-compose ps;
                    
                    export NETWORK_GATEWAY=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.Gateway}}{{end}}' ${COMPOSE_PROJECT_NAME}_webserver1_con);
                    

                    【讨论】:

                      【解决方案17】:

                      我可以建议您在运行状况检查脚本中使用 /usr/bin/mysql --user=root --password=root --execute "SHOW DATABASE;" 而不是 mysqladmin ping。这等待真正的初始化和服务准备好客户端连接。

                      例子:

                        docker run -d --name "test-mysql-client" -p 0.0.0.0:3306:3306 -e MYSQL_PASSWORD=password -e MYSQL_USER=user -e MYSQL_ROOT_PASSWORD=root --health-cmd="/usr/bin/mysql --user=root --password=root --execute \"SHOW DATABASE;\"" --health-interval=1s --health-retries=60 --health-timeout=10s -e MYSQL_DATABASE=db  mysql:latest```
                      

                      【讨论】:

                        猜你喜欢
                        • 2015-09-07
                        • 1970-01-01
                        • 2013-08-16
                        • 1970-01-01
                        • 2012-12-12
                        • 2017-07-22
                        • 1970-01-01
                        • 1970-01-01
                        • 2011-06-01
                        相关资源
                        最近更新 更多