[英]How do I know when my docker mysql container is up and mysql is ready for taking queries?
我正在部署几个不同的 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
有没有办法等待入口点 mysql 安装脚本在 docker 容器内完成的信号? 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
您可以安装 mysql-client 包并使用 mysqladmin 来 ping 目标服务器。 在使用多个 docker 容器时很有用。 结合 sleep 并创建一个简单的等待循环:
while ! mysqladmin ping -h"$DB_HOST" --silent; do
sleep 1
done
这个小 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
在其他答案的评论中或多或少提到了这一点,但我认为它应该有自己的条目。
首先,您可以通过以下方式运行容器:
docker run --name mysql --health-cmd='mysqladmin ping --silent' -d mysql
Dockerfile 中也有一个 等价物。
使用该命令,您的docker ps
和docker 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 主机内存不足,因此 mysql 无法为自己分配足够的内存。
有时端口的问题是端口可以打开,但数据库还没有准备好。
其他解决方案要求您在主机中安装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
我发现使用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
答案之一的评论中提出了类似的建议。
以下运行状况检查适用于我的所有 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
所以我不确定是否有人发布过这个。 它看起来不像任何人,所以...... 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
一种使用 curl 的衬垫,可在所有 Linux 发行版中找到:
while ! curl -o - db-host:3306; do sleep 1; done
当我的 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 使用了这三个变量。
如果等待 mysql 容器的 docker 容器基于python 映像(例如对于 Django 应用程序) ,则可以使用以下代码。
优点是:
代码:
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()
用法:
wait-for-mysql-db.py
)中。startup.py
),首先执行上述代码,然后启动您的应用程序。command: ["python3", "startup.py"]
配置您的应用程序容器: command: ["python3", "startup.py"]
。请注意,此解决方案适用于 MySQL 数据库。 您需要为另一个数据库稍微调整它。
我基于一种新方法为这个问题开发了一个新的解决方案。 我发现的所有方法都依赖于反复尝试连接到数据库或尝试与容器建立 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 本身。 我希望下面的示例使用法清晰:
SERVICE_NAME="mysql" && \
docker-compose up -d $SERVICE_NAME && \
./wait_db.sh docker-compose --no-color $SERVICE_NAME
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
可以在存储库的测试用例中找到完整示例。 这个测试用例将启动一个新的 MySQL,创建一个虚拟数据库,等待一切启动,然后触发一个选择来检查一切是否正常。 之后它将重新启动容器并等待它启动,然后触发一个新的选择以检查它是否准备好连接。
以下是我如何将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 && docker-compose run server yarn run migrate
以确保当我在我的server
容器中运行我的migrate
任务时,我知道数据库将接受连接。
我喜欢这种方法,因为 bash 文件与我想运行的任何命令都是分开的。 我可以在使用我运行的任务的任何其他数据库之前轻松运行db-ready.sh
。
在上面的 Mihai Crăiță 上构建了一些出色的答案,我在 CURL 选项中添加了启用 0.9(现在默认禁用)并隐藏 output 以减少启动期间的日志“噪音”:
server="MyServerName"
echo "Waiting for MySQL at ${server}"
while ! curl --http0.9 -o - "${server}:3306" &> /dev/null; do sleep 1; done
https://github.com/docker-library/mysql/blob/master/5.7/docker-entrypoint.sh docker-entrypoint.sh 尚不支持合并自定义 .sql。
我认为您可以修改 docker-entrypoint.sh 以合并您的 sql,以便在 mysql 实例准备就绪后执行它。
在您的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 连接。
我使用以下代码;
导出 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);
我可以推荐你使用/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```
结合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
基本上,它会检查 MySQL 容器中的mysqladmin
是否处于活动状态,如果存在,MySQL 应该已启动。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.