可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am deploying a few different docker containers, mysql being the first one. I want to run scripts as soon as database is up and proceed to building other containers. The script has been failing because it was trying to run when the entrypoint script, which sets up mysql (from this official mysql container), was still running.
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
Is there a way to wait for a signal of an entrypoiny mysql setup script finishing inside the docker container? Bash sleep seems like a suboptimal solution.
EDIT: Went for a bash script like this. Not the most elegant and kinda brute force but works like a charm. Maybe someone will find that useful.
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
回答1:
You can install mysql-client package and use mysqladmin to ping target server. Useful when working with multiple docker container. Combine with sleep and create a simple wait-loop:
while ! mysqladmin ping -h"$DB_HOST" --silent; do
sleep 1
done
回答2:
This little bash loop waits for mysql to be open, shouldn't require any extra packages to be installed:
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
回答3:
This was more or less mentioned in comments to other answers, but I think it deserves it's own entry.
First of all you can run your container in the following manner:
docker run --name mysql --health-cmd='mysqladmin ping --silent' -d mysql
There is also an equivalent in the Dockerfile.
With that command your docker ps
and docker inspect
will show you health status of your container. For mysql in particular this method has the advantage of mysqladmin
being available inside the container, so you do not need to install it on the docker host.
Then you can simply loop in a bash script to wait on the status to become healthy. The following bash script is created by 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"
}
Now you can do this in your script:
waitContainer mysql
and your script will wait until the container is up and running. The script will exit if the container becomes unhealthy, which is possible, if for example docker host is out of memory, so that the mysql cannot allocate enough of it for itself.
回答4:
Some times the problem with the port is that the port could be open, but the database is not ready yet.
Other solutions require that you have installed the mysql o a mysql client in your host machine, but really you already have it inside the Docker container, so I prefer to use something like this:
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:
The following health-check works for all my mysql containers:
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:
https://github.com/docker-library/mysql/blob/master/5.7/docker-entrypoint.sh
docker-entrypoint.sh doesn't support merging customized .sql yet.
I think you can modify docker-entrypoint.sh to merge your sql so it can be executed once mysql instance is ready.
回答7:
I had the same issue when my Django container tried to connect the mysql container just after it started. I solved it using the vishnubob's wait-for.it.sh script. Its a shell script which waits for an IP and a host to be ready before continuing. Here is the example I use for my applicaction.
./wait-for-it.sh \
-h $(docker inspect --format '{{ .NetworkSettings.IPAddress }}' $MYSQL_CONTAINER_NAME) \
-p 3306 \
-t 90
In that script I'm asking to the mysql container to wait maximum 90 seconds (it will run normally when ready) in the port 3306 (default mysql port) and the host asigned by docker for my MYSQL_CONTAINER_NAME. The script have more variables but for mw worked with these three.
回答8:
On your ENTRYPOINT
script, you have to check if you have a valid MySQL connection or not.
This solution does not require you to install a MySQL Client on the container and while running the container with php:7.0-fpm
running nc
was not an option, because it had to be installed as well. Also, checking if the port is open does not necessarily mean that the service is running and exposed correctly. [more of this]
So in this solution, I will show you how to run a PHP script to check if a MySQL Container is able to take connection. If you want to know why I think this is a better approach check my comment here.
File 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
By running this, you are essentially blocking any bootstrapping logic of your container UNTIL you have a valid MySQL Connection.
回答9:
If the docker container waiting for a mysql container is based on a python image (for instance for a Django application), you can use the code below.
Advantages are:
- It's not based on wait-for-it.sh, which does wait for the IP and port of mysql to be ready, but this doesn't automatically mean also that the mysql initialization has finished.
- It's not a shell script based on a mysql or mysqladmin executable that must be present in your container: since your container is based on a python image, this would require installing mysql on top of that image. With the below solution, you use the technology that is already present in the container: pure python.
Code:
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()
Usage:
- Add this code into a python file (
wait-for-mysql-db.py
for instance) inside your application's source code.
- Write another python script (
startup.py
for instance) that first executes the above code, and afterwards starts up your application.
- Make sure your application container's Dockerfile packs these two python scripts together with the application's source code into a Docker image.
- In your docker-compose file, configure your application container with:
command: ["python3", "startup.py"]
.
Note that this solution is made for a MySQL database. You'll need to adapt it slightly for another database.
回答10:
I use the following code ;
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);
回答11:
I've found that using the mysqladmin ping
approach isn't always reliable, especially if you're bringing up a new DB. In that case, even if you're able to ping the server, you might be unable to connect if the user/privilege tables are still being initialized. Instead I do something like the following:
while ! docker exec db-container mysql --user=foo --password=bar -e "SELECT 1" >/dev/null 2>&1; do
sleep 1
done
So far I haven't encountered any problems with this method. I see that something similar was suggested by VinGarcia in a comment to one of the mysqladmin ping
answers.