我如何知道我的docker mysql容器何时启动并且mysql已准备好接受查询?

问题描述 投票:44回答:13

我正在部署一些不同的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容器内完成入门的mysql安装脚本的信号? Bash睡眠似乎是次优解决方案。

编辑:去找这样的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
mysql bash shell docker
13个回答
41
投票

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

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

0
投票

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

我想你可以修改docker-entrypoint.sh来合并你的sql,这样一旦mysql实例准备就可以执行它。


0
投票

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连接。


0
投票

如果等待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数据库。您需要稍微调整它以适应另一个数据库。


0
投票

我使用以下代码;

export COMPOSE_PROJECT_NAME = web;

export 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);

31
投票

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

22
投票

在其他答案的评论中或多或少地提到了这一点,但我认为它值得进入。

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

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 "{{json .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无法为自己分配足够的内容。


8
投票

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

其他解决方案要求你已经在主机上安装了mysql客户端的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

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答案时提出了类似的建议。


3
投票

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

2
投票

当我的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使用这三个变量。


2
投票

所以我不确定是否有人发布了这个。它看起来不像任何人,所以...在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

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本身中使用。我希望下面的例子清楚地说明用法:

Example 01: Using with Docker Compose

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

Example 02: Using with 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

Example 3: A full example (the test-case)

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

© www.soinside.com 2019 - 2024. All rights reserved.