Як я можу дізнатись, коли мій контейнер docker mysql запущений, і mysql готовий до прийому запитів?


83

Я розгортаю кілька різних контейнерів докерів, 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

Чи є спосіб дочекатися сигналу сценарію налаштування enterpoiny 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

додати цикл bash для перевірки статусу служби MySQL? Я думаю, що це настільки добре, наскільки це може отримати.
fabrizioM

1
Дякую @fabrizioM. Я пішов із таким підходом.
haren

на це запитання дано відповідь тут .
вийти

Відповіді:


73

Ви можете встановити пакет mysql-client і використовувати mysqladmin для пінгу цільового сервера. Корисно при роботі з декількома контейнерами докера. Поєднайте зі сном і створіть простий цикл очікування:

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

22
Це прекрасна річ. Також чудово працює для перевірки стану здоров'я Docker:docker run --health-cmd='mysqladmin ping --silent' -d mysql
Натан Артур

4
Щоб зачекати одиничний контейнер здоровий, я використав сценарійwhile [ $(docker inspect --format "{{json .State.Health.Status }}" <container-name>) != "\"healthy\"" ]; do printf "."; sleep 1; done
Ронкот,

5
Тут це не працює; mysqladmin pingвдається, перш ніж я зможу фактично використовувати базу даних - я думаю, контейнер все ще працює, це .sqlсценарії ініціалізації .
cweiske

3
У мене не було встановлено mysqladmin в образі докера, однак wget теж зробив свою роботу: while ! wget mysql:3306; do sleep 1 done
engin

Якщо ви спробуєте його на localhost і він не працює, спробуйте "127.0.0.1". Коли використовується "localhost", mysqladmin намагається підключитися за допомогою сокета замість TCP-порту за замовчуванням 3306.
Лука

38

Цей маленький цикл 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
Хороший. Я використовував його як 1 вкладиш: до nc -z $ CFG_MYSQL_HOST 3306; спати 1; луна "Чекаючи, поки БД з’явиться ..."; готово
user2707671

8
Те, що порт доступний, не означає, що сервер готовий приймати з'єднання. mysqladmin pingправильна відповідь тут.
Quolonel питання

33

Про це більш-менш згадувалося в коментарях до інших відповідей, але я думаю, що це заслуговує на власний запис.

Перш за все, ви можете запустити свій контейнер наступним чином:

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 "{{.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
Я виявив, що це найкращий підхід, оскільки він залежить лише від Docker і, отже, повинен мати менше проблем на різних платформах. Єдине, з чим я зіткнувся, так це те, що точка входу зображення двічі обертає mysqlserver - один раз оголеним і один раз ініціалізованим. mysqladmin pingвловлює і перший спін, що, швидше за все, ви не хочете. У моєму випадку найкраще працював запуск фіктивного запиту зі схемою, яку ви очікуєте там знаходитись, тобто змінити команду здоров'я на mysql -u root -e "use your_schema;".
Макс

Це працює, якщо у вас є healthcheckдля вашого контейнера. Але .State.Health.Statusне існує, якщо ви цього не робите. Можливо, вам доведеться погодитися .State.Statusнатомість; але це говорить runningзанадто рано для потреб цього OP.
Jesse Chisholm

1
@JesseChisholm так? Як ви думаєте --health-cmd, для чого?
Андрій Савіних

Ви також можете використовувати dockerhealthcheck -compose docs.docker.com/compose/compose-file/#healthcheck
am70

12

Іноді проблема з портом полягає в тому, що порт може бути відкритим , але база даних ще не готова.

Інші рішення вимагають, щоб ви встановили клієнт 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

6
Це здається найкращим і найкоротшим варіантом, однак просто пінг не працював для мене, він все ще не був готовий згодом, тому я використав команду: mysql -u root -proot -e 'status' &> /dev/nullзамістьmysqladmin ping
VinGarcia

9

Я виявив, що використання mysqladmin 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

Наступна перевірка працездатності працює для всіх моїх контейнерів 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

5

Тому я не впевнений, що хтось це опублікував. Це не схоже на те, що є у когось, тому ... у 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будучи прапором чекати , поки з'єднання не буде успішним і числом є кількістю спроб повторної спроби.

В ідеалі ви б запускали цю команду зсередини контейнера докера, але я не хотів занадто змінювати початкову команду плакатів.

При використанні у моєму файлі 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

3

У мене була та сама проблема, коли мій контейнер Django намагався підключити контейнер mysql відразу після його запуску. Я вирішив це за допомогою сценарію vishnubob wait-for.it.sh . Це сценарій оболонки, який очікує на готовність IP та хоста перед продовженням. Ось приклад, який я використовую для свого застосування.

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

У цьому сценарії я прошу контейнер mysql зачекати максимум 90 секунд (він буде працювати нормально, коли буде готовий) у порту 3306 (порт mysql за замовчуванням) та хості, призначеному docker для мого MYSQL_CONTAINER_NAME. Сценарій має більше змінних, але для mw працював із цими трьома.


1

Якщо контейнер докера, який чекає на контейнер mysql, базується на зображенні python (наприклад, для програми Django) , ви можете використовувати код нижче.

Переваги:

  • Він не заснований на wait-for-it.sh , який дійсно чекає, поки будуть готові IP та порт mysql, але це не означає автоматично також, що ініціалізація mysql закінчена.
  • Це не скрипт оболонки, заснований на виконуваному файлі mysql або mysqladmin, який повинен бути присутнім у вашому контейнері: оскільки ваш контейнер базується на зображенні python, для цього потрібно встановити mysql поверх цього образу. З наведеним нижче рішенням ви використовуєте технологію, яка вже присутня в контейнері: чистий пітон.

Код:

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. У вашому Докер-Compose файл, налаштувати контейнер додатки з: command: ["python3", "startup.py"].

Зверніть увагу, що це рішення створено для бази даних MySQL. Вам потрібно буде дещо адаптувати його для іншої бази даних.


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. Я сподіваюся, що наведені нижче приклади дозволяють зрозуміти використання:

Приклад 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, щоб перевірити, чи все в порядку. Після цього він перезапустить контейнер і зачекає його запуску, а потім запускає новий вибір, щоб перевірити, чи готовий він до підключення.


1

Ось як я включив рішення Адамса у свій проект на основі docker-compose:

Створив файл bash з назвою db-ready.shв моїй serverпапці контейнера (вміст якого копіюється в мій контейнер - 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будь-яку іншу БД за допомогою запущеного завдання.



0

У своєму 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.


0

Я використовую наступний код;

експортувати COMPOSE_PROJECT_NAME = веб;

експортувати 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);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.