Как узнать, когда контейнер docker mysql вставлен, и mysql готов к выполнению запросов?

Я развертываю несколько различных контейнеров докеров, причем 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-скрипта, который заканчивается внутри контейнера докеров? Спящий сон кажется субоптимальным решением.

EDIT: Пошел для сценария 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

Ответы

Ответ 1

Вы можете установить пакет mysql-client и использовать mysqladmin для целевого сервера ping. Полезно при работе с несколькими контейнерами докеров. Объединитесь со сном и создайте простой цикл ожидания:

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

Ответ 2

Этот небольшой цикл 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

Ответ 3

Это было более или менее упомянуто в комментариях к другим ответам, но я думаю, что это заслуживает отдельной записи.

Прежде всего вы можете запустить свой контейнер следующим образом:

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

В Dockerfile также есть эквивалент.

С помощью этой команды ваш docker ps и docker inspect покажут вам состояние вашего контейнера. В частности, для mysql этот метод имеет то преимущество, что mysqladmin доступен внутри контейнера, поэтому вам не нужно устанавливать его на хост докера.

Затем вы можете просто зациклить сценарий bash, чтобы дождаться состояния, чтобы стать здоровым. Следующий скрипт bash создан Денисом.

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

и ваш скрипт будет ждать, пока контейнер не заработает. Сценарий завершится, если контейнер станет нездоровым, что возможно, если, например, хосту докера не хватает памяти, так что mysql не может выделить достаточно его для себя.

Ответ 4

В некоторых случаях проблема с портом заключается в том, что порт может быть открыт, но база данных еще не готова.

Другие решения требуют, чтобы вы установили 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

Ответ 5

Я обнаружил, что использование подхода mysqladmin ping не всегда надежно, особенно если вы открываете новую БД. В этом случае, даже если вы можете пропинговать сервер, вы не сможете подключиться, если таблицы привилегий/пользователей все еще инициализируются. Вместо этого я делаю что-то вроде следующего:

while ! docker exec db-container mysql --user=foo --password=bar -e "SELECT 1" >/dev/null 2>&1; do
    sleep 1
done

До сих пор я не столкнулся с какими-либо проблемами с этим методом. Я вижу, что нечто подобное было предложено ВинГарсией в комментарии к одному из ответов на mysqladmin ping.

Ответ 6

Следующая проверка работоспособности работает для всех моих контейнеров 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

Ответ 7

У меня была такая же проблема, когда мой контейнер Django попытался подключить контейнер mysql сразу после его запуска. Я решил это с помощью vishnubob wait-for.it.sh script. Его оболочка script, которая ждет, пока IP-адрес и хост будут готовы, прежде чем продолжить. Вот пример, который я использую для моего применения.

./wait-for-it.sh \
    -h $(docker inspect --format '{{ .NetworkSettings.IPAddress }}' $MYSQL_CONTAINER_NAME) \
    -p 3306 \
    -t 90

В этом script я прошу контейнер mysql подождать максимум 90 секунд (он будет нормально работать, когда он будет готов) в порту 3306 (порт по умолчанию mysql), а хост, назначенный докере для моего MYSQL_CONTAINER_NAME. script имеют больше переменных, но для mw работал с этими тремя.

Ответ 8

https://github.com/docker-library/mysql/blob/master/5.7/docker-entrypoint.sh docker -entrypoint.sh еще не поддерживает слияние настроенных .sql.

Я думаю, вы можете изменить docker -entrypoint.sh, чтобы объединить ваш sql, чтобы он мог быть выполнен после того, как экземпляр mysql готов.

Ответ 9

На вашем ENTRYPOINT script вы должны проверить, есть ли у вас действующее соединение с MySQL или нет.

Это решение не требует установки клиента MySQL в контейнере и при запуске контейнера с php:7.0-fpm running nc не было выбора, так как оно также должно быть установлено. Кроме того, проверка открытия порта не означает, что служба работает и отображается правильно. [больше этого]

Итак, в этом решении я покажу вам, как запустить PHP script, чтобы проверить, может ли контейнер 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.

Ответ 10

Если контейнер докеров, ожидающий контейнер mysql, основан на изображении python (например, для приложения Django), вы можете использовать приведенный ниже код.

Преимущества:

  • Он не основан на wait-for-it.sh, который ожидает, что IP и порт mysql будут готовы, но это автоматически не означает также, что инициализация mysql завершена.
  • Это не сценарий оболочки, основанный на исполняемом файле mysql или mysqladmin, который должен присутствовать в вашем контейнере: поскольку ваш контейнер основан на изображении 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. В файле command: ["python3", "startup.py"] настройте контейнер приложения с помощью command: ["python3", "startup.py"].

Обратите внимание, что это решение сделано для базы данных MySQL. Вам нужно будет немного адаптировать его для другой базы данных.

Ответ 11

Я использую следующий код;

export COMPOSE_PROJECT_NAME = web;

export IS_DATA_CONTAINER_EXISTS = $ (объем докеры 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);

Ответ 12

Я разработал новое решение для этой проблемы, основанное на новом подходе. Все найденные мной подходы основаны на сценарии, который снова и снова пытается подключиться к базе данных или пытается установить 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, создаст фиктивную базу данных, подождет, пока все не запустится, а затем сработает select, чтобы проверить, все ли в порядке. После этого он перезапустит контейнер и дождется его запуска, а затем сработает новый выбор, чтобы проверить, готов ли он к соединению.

Ответ 13

Так что я не уверен, что кто-то опубликовал это. Это не похоже ни на кого, поэтому... в 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 - это флаг, --wait успешного подключения, а число - сумма. попыток повторить.

В идеале вы должны выполнить эту команду из контейнера докера, но я не хотел слишком сильно изменять исходную команду постеров.

Когда используется в моем файле 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