我正在部署一些不同的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
有没有办法等待dockpoiny mysql安装脚本在docker容器内完成的信号? 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
答案 0 :(得分:41)
您可以安装mysql-client软件包并使用mysqladmin来ping目标服务器。使用多个docker容器时很有用。结合sleep并创建一个简单的等待循环:
while ! mysqladmin ping -h"$DB_HOST" --silent; do
sleep 1
done
答案 1 :(得分: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
答案 2 :(得分:21)
在其他答案的评论中或多或少地提到了这一点,但我认为它值得进入。
首先,您可以按以下方式运行容器:
docker run --name mysql --health-cmd='mysqladmin ping --silent' -d mysql
Dockerfile中还有一个equivalent。
使用该命令,docker ps
和docker 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无法为自己分配足够的内容。
答案 3 :(得分:7)
有时端口的问题是端口可能打开,但数据库还没有准备好。
其他解决方案要求您已在主机中安装了 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
答案 4 :(得分: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
答案之一的评论中提出了类似的建议。
答案 5 :(得分: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
答案 6 :(得分:3)
一个使用 curl 的 liner,可在所有 Linux 发行版中找到:
while ! curl -o - db-host:3306; do sleep 1; done
答案 7 :(得分: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端口)中等待最多90秒(它准备好后会正常运行),并且由docker为我的MYSQL_CONTAINER_NAME设置主机。该脚本有更多的变量,但是mw使用这三个。
答案 8 :(得分:1)
这是我将Adams解决方案合并到基于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
。
答案 9 :(得分:0)
https://github.com/docker-library/mysql/blob/master/5.7/docker-entrypoint.sh docker-entrypoint.sh还不支持合并自定义的.sql。
我认为你可以修改docker-entrypoint.sh来合并你的sql,这样一旦mysql实例准备就可以执行它。
答案 10 :(得分: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连接。
答案 11 :(得分:0)
如果等待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"]
。请注意,此解决方案适用于MySQL数据库。您需要稍微调整它以适应另一个数据库。
答案 12 :(得分:0)
我使用以下代码;
导出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);
答案 13 :(得分:0)
我基于新方法开发了针对此问题的新解决方案。我发现的所有方法都依赖一个脚本,该脚本反复尝试连接数据库,或尝试与容器建立TCP连接。完整的详细信息可以在waitdb存储库中找到,但是,我的解决方案是依靠从容器中检索到的日志。该脚本将一直等待,直到日志触发消息准备连接。该脚本可以识别容器是否是第一次启动。在这种情况下,脚本将一直等到执行初始数据库脚本并重新启动数据库后,再等待一条新的准备连接消息。我在MySQL 5.7和MySQL 8.0上测试了该解决方案。
脚本本身( wait_db.sh ):
execution_date - 0
该脚本可以在Docker Compose或Docker本身中使用。我希望下面的示例可以清楚地说明用法:
#Airflow imports
from airflow import DAG
from airflow.operators.python_operator import PythonOperator
from airflow.operators.dummy_operator import DummyOperator
from airflow.operators.python_operator import BranchPythonOperator
# General imports
from datetime import datetime
DAG_ID = 'stackoverflow_exampledag'
args = {
'owner': 'you',
'email': ['you@yourwork.com'],
'depends_on_past': False,
'email_on_retry': False,
'email_on_failure': True,
'start_date': datetime(2019, 4, 14)
}
dag = DAG(
dag_id=DAG_ID,
default_args=args,
schedule_interval="0 0 * * 1-5"
)
#################################
######## Python Script ##########
#################################
def checktheday(**kwargs):
weekday = datetime.today().weekday()
if weekday == 1:
return 'monday_only_task'
else:
return 'tuesday_through_friday_task'
####################################
########## TASKS ###################
####################################
# BranchPythonOperator is the entry point for this DAG.
# The python callable will return the task id of the appriorate subdag/task that it's supposed to run.
checktheday_task = BranchPythonOperator(
task_id='checktheday_task',
python_callable=checktheday,
dag=dag,
provide_context=True
)
monday_only_task = DummyOperator(
task_id='monday_only_task',
dag=dag
)
tuesday_through_friday_task = DummyOperator(
task_id='tuesday_through_friday_task',
dag=dag
#################################
########## ORCHESTRATION ########
#################################
monday_only_task.set_upstream(checktheday_task)
tuesday_through_friday_task.set_upstream(checktheday_task)
#!/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!"
可以找到完整的示例on the test case of the repository。这个测试用例将启动一个新的MySQL,创建一个虚拟数据库,等待一切启动,然后触发 select 来检查一切是否正常。之后,它将重新启动容器并等待其启动,然后触发新的 select 来检查其是否已准备好进行连接。
答案 14 :(得分:0)
因此,我不确定是否有人发布了此内容。它看起来不像任何人,所以... 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
答案 15 :(得分:0)
我可以建议您在健康检查脚本中使用 /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```