我怎么知道我的docker mysql容器何时启动并且mysql准备好进行查询?


78

我正在部署几个不同的Docker容器,第一个是mysql。我想在数据库启动后立即运行脚本,然后继续构建其他容器。该脚本已失败,因为它试图在设置mysql(从此正式mysql容器)的入口点脚本仍在运行时运行。

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容器内的进入MySQL设置脚本完成的信号?重击睡眠似乎是次佳的解决方案。

编辑:像这样的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 2014年

1
谢谢@fabrizioM。我采用了这种方法。
哈伦2014年

这个问题已经在这里回答
退出

Answers:


70

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

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

22
这是一件美丽的事。对于Docker健康检查也非常有用:docker run --health-cmd='mysqladmin ping --silent' -d mysql
Nathan Arthur

4
为了等待单位容器健康,我使用了脚本while [ $(docker inspect --format "{{json .State.Health.Status }}" <container-name>) != "\"healthy\"" ]; do printf "."; sleep 1; done
ronkot

5
这在这里不起作用;mysqladmin ping在我可以实际使用数据库之前就成功了-我猜容器仍在运行的是初始化.sql脚本。
cweiske

3
我没有在docker映像中安装mysqladmin,但是,wget也完成了这项工作:while ! wget mysql:3306; do sleep 1 done
engin

如果您在localhost上尝试使用它,则不起作用,请尝试“ 127.0.0.1”。当使用“本地主机”,中mysqladmin正在试图使用一个插座上,而不是默认的TCP端口3306连接
卢克

38

这个小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

2
好东西。我将其用作1衬板:直到nc -z $ CFG_MYSQL_HOST 3306; 睡1 回显“正在等待数据库出现...”;已完成
user2707671 2016年

8
仅因为端口可用并不意味着服务器已准备好接受连接。mysqladmin ping是正确的答案。
Quolonel问题,

31

在其他答案的评论中或多或少提到了这一点,但我认为它应该自己输入。

首先,您可以按照以下方式运行容器:

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

Dockerfile中也有一个等效项。

使用该命令,您的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无法为其自身分配足够的内存。


4
我发现这是最好的方法,因为它仅取决于Docker,因此应该具有较少的跨平台问题。我唯一遇到的是映像入口点使mysqlserver旋转了两次-裸露并初始化。mysqladmin ping赶上第一次旋转,这可能是您不想要的。在我的情况下,使用您期望存在的模式运行虚拟查询效果最佳,即将health命令更改为mysql -u root -e "use your_schema;"
最大

如果您有一个healthcheck用于容器的方法,则此方法有效。但是,.State.Health.Status如果不存在,则不存在。您可能不得不满足于此.State.Status。但这running对于满足该OP的需求来说还为时过早。
杰西·奇斯霍尔姆

1
@JesseChisholm是吗?您认为--health-cmd是为了什么?
安德鲁·萨维尼赫

您也可以将其healthcheckdocker
am70

10

有时,端口的问题是该端口可能是open,但数据库尚未准备好。

其它解决方案要求您已经安装了MySQL的OA MySQL客户端在你的主机,但实际上你已经拥有它的多克尔容器内,所以我更喜欢使用这样的事情:

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

6
这似乎是最好的和最短的选项,但只是执行ping不工作对我来说,它仍然没有准备好之后,所以我用命令:mysql -u root -proot -e 'status' &> /dev/null代替mysqladmin ping
VinGarcia

7

我发现使用 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答案的评论中提出了类似的建议。


5

以下运行状况检查适用于我所有的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

5

因此,我不确定是否有人发布了此内容。它看起来不像任何人,所以... 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容器中运行该命令,但是我不想过多地修改原始的posters命令。

在我的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

3

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

./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来说,这三个变量都适用。


1

如果等待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. 将此代码添加到wait-for-mysql-db.py应用程序源代码中的python文件中(例如)。
  2. 编写另一个python脚本(startup.py例如),该首先执行上述代码,然后启动您的应用程序。
  3. 确保应用程序容器的Dockerfile将这两个python脚本以及应用程序的源代码打包到Docker映像中。
  4. 在您的码头工人,撰写文件,用配置的应用程序容器:command: ["python3", "startup.py"]

请注意,此解决方案是针对MySQL数据库制作的。您需要对其稍作调整以适合另一个数据库。


1

我基于新方法开发了针对此问题的新解决方案。我发现的所有方法都依赖于一个脚本,该脚本反复尝试连接数据库,或尝试与容器建立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:完整示例(测试用例)

可以在存储库的测试案例中找到完整的示例。该测试用例将启动一个新的MySQL,创建一个虚拟数据库,等待一切启动,然后触发选择以检查一切是否正常。之后,它将重新启动容器并等待其启动,然后触发新的选择以检查其是否已准备好进行连接。


1

这是我注册亚当斯的方式解决方案合并到基于docker-compose的项目中的方法:

db-ready.sh在我的server容器文件夹中创建了一个名为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 && docker-compose run server yarn run migrate确保在我运行migrate任务时server容器中,我知道数据库将接受连接。

我喜欢这种方法,因为bash文件与我要运行的任何命令都是分开的。db-ready.sh使用运行的任务,我可以轻松地先运行DB。



0

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

该解决方案不需要您在容器上安装MySQL客户端,并且在运行容器的情况下php:7.0-fpm运行nc是不可取的,因为还必须安装它。另外,检查端口是否打开不一定意味着服务正在运行并且正确公开。[更多]

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

文件 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 Connection。


0

我使用以下代码;

导出COMPOSE_PROJECT_NAME =网络;

导出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);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.