Woher weiß ich, wann mein Docker-MySQL-Container aktiv ist und MySQL für Anfragen bereit ist?

78

Ich stelle einige verschiedene Docker-Container bereit, wobei MySQL der erste ist. Ich möchte Skripte ausführen, sobald die Datenbank verfügbar ist, und andere Container erstellen. Das Skript ist fehlgeschlagen, weil versucht wurde, es auszuführen, als das Einstiegspunkt-Skript, das MySQL (aus diesem offiziellen MySQL-Container ) einrichtet , noch ausgeführt wurde.

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

Gibt es eine Möglichkeit, auf das Signal zu warten, dass ein MySQL-Setup-Skript von entrypoiny im Docker-Container beendet wird? Bash Sleep scheint eine suboptimale Lösung zu sein.

EDIT: Ging für ein Bash-Skript wie dieses. Nicht die eleganteste und etwas brutalste Kraft, aber sie wirkt wie ein Zauber. Vielleicht findet das jemand nützlich.

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
haren
quelle
eine Bash-Schleife hinzufügen, um den Status des MySQL-Dienstes zu überprüfen? Ich denke das ist so gut wie es nur geht.
FabrizioM
1
Danke @fabrizioM. Ich habe diesen Ansatz gewählt.
Karen
Diese Frage wurde beantwortet hier .
Eexit

Antworten:

70

Sie können das mysql-client-Paket installieren und mysqladmin verwenden, um den Zielserver zu pingen. Nützlich bei der Arbeit mit mehreren Docker-Containern. Kombinieren Sie mit Schlaf und erstellen Sie eine einfache Warteschleife:

while ! mysqladmin ping -h"$DB_HOST" --silent; do
    sleep 1
done
Flammenemyst
quelle
22
Das ist eine schöne Sache. Funktioniert auch hervorragend für einen Docker-Gesundheitscheck:docker run --health-cmd='mysqladmin ping --silent' -d mysql
Nathan Arthur
4
Um zu warten, while [ $(docker inspect --format "{{json .State.Health.Status }}" <container-name>) != "\"healthy\"" ]; do printf "."; sleep 1; done
ob der
5
Dies funktioniert hier nicht; mysqladmin pingDies ist erfolgreich, bevor ich die Datenbank tatsächlich verwenden kann. Ich denke, der Container wird noch ausgeführt. Es handelt sich um Initialisierungsskripte .sql.
Cweiske
3
Ich hatte mysqladmin nicht im Docker-Image installiert, aber wget hat auch den Job gemacht: while ! wget mysql:3306; do sleep 1 done
Engin
Wenn Sie es auf localhost versuchen und es nicht funktioniert, versuchen Sie "127.0.0.1". Wenn "localhost" verwendet wird, versucht mysqladmin, eine Verbindung über einen Socket über den Standard-TCP-Port 3306 herzustellen.
Luke
38

Diese kleine Bash-Schleife wartet darauf, dass MySQL geöffnet ist. Es sollten keine zusätzlichen Pakete installiert werden:

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
Adam
quelle
2
Schön. Ich habe es als 1-Liner verwendet: bis nc -z $ CFG_MYSQL_HOST 3306; schlafe 1; echo "Warten auf DB ..."; erledigt
user2707671
8
Nur weil der Port verfügbar ist, bedeutet dies nicht, dass der Server bereit ist, Verbindungen zu akzeptieren. mysqladmin pingist hier die richtige Antwort.
Quolonel Fragen
31

Dies wurde mehr oder weniger in Kommentaren zu anderen Antworten erwähnt, aber ich denke, es verdient einen eigenen Eintrag.

Zunächst können Sie Ihren Container folgendermaßen ausführen:

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

Es gibt auch ein Äquivalent in der Docker-Datei.

Mit diesem Befehl wird Ihr docker psund der docker inspectGesundheitszustand Ihres Containers angezeigt. Insbesondere für MySQL hat diese Methode den Vorteil, mysqladmindass sie im Container verfügbar ist , sodass Sie sie nicht auf dem Docker-Host installieren müssen.

Dann können Sie einfach ein Bash-Skript durchlaufen, um zu warten, bis der Status fehlerfrei ist. Das folgende Bash-Skript wurde von Dennis erstellt .

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"
}

Jetzt können Sie dies in Ihrem Skript tun:

waitContainer mysql

und Ihr Skript wartet, bis der Container betriebsbereit ist. Das Skript wird beendet, wenn der Container fehlerhaft wird. Dies ist möglich, wenn beispielsweise der Docker-Host nicht über genügend Speicher verfügt, sodass die MySQL nicht genug davon für sich selbst zuweisen kann.

Andrew Savinykh
quelle
4
Ich fand, dass dies der beste Ansatz ist, da er nur von Docker abhängt und daher weniger plattformübergreifende Probleme haben sollte. Das einzige, was mir aufgefallen ist, ist, dass der Bildeintrittspunkt mysqlserver zweimal hochfährt - einmal nackt und einmal initialisiert. mysqladmin pingfängt auch den ersten Spin-up, was wahrscheinlich nicht der Fall ist. In meinem Fall funktionierte das Ausführen einer Dummy-Abfrage mit dem Schema, von dem Sie erwarten, dass es dort vorhanden ist, am besten, dh ändern Sie den Befehl zum Ändern des Zustands mysql -u root -e "use your_schema;".
Max
Dies funktioniert, wenn Sie eine healthcheckfür Ihren Container haben. Aber das .State.Health.Statusgibt es nicht, wenn du es nicht tust. Möglicherweise müssen Sie sich .State.Statusstattdessen damit zufrieden geben. aber das sagt runningzu früh für die Bedürfnisse dieses OP.
Jesse Chisholm
1
@ JesseChisholm nicht wahr? Wofür denkst du --health-cmdist?
Andrew Savinykh
Sie können es healthcheckauch mit docker
am70
10

Manchmal besteht das Problem mit dem Port darin, dass der Port möglicherweise geöffnet ist, die Datenbank jedoch noch nicht bereit ist.

Andere Lösungen erfordern , dass Sie die installiert haben mysql oa MySQL - Client in Ihrem Host - Rechner, aber wirklich haben Sie es bereits in den Docker Behälter, so dass ich auf die Verwendung so etwas wie dies bevorzugen:

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
Alejandropg
quelle
6
Dies scheint die beste und kürzeste Option zu sein, aber nur Ping hat bei mir nicht funktioniert, es war danach immer noch nicht fertig, also habe ich den Befehl verwendet: mysql -u root -proot -e 'status' &> /dev/nullanstelle vonmysqladmin ping
VinGarcia
7

Ich habe festgestellt, dass die Verwendung des mysqladmin pingAnsatzes nicht immer zuverlässig ist, insbesondere wenn Sie eine neue Datenbank aufrufen. In diesem Fall können Sie möglicherweise keine Verbindung herstellen, selbst wenn Sie den Server anpingen können, wenn die Benutzer- / Berechtigungstabellen noch initialisiert werden. Stattdessen mache ich so etwas wie das Folgende:

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

Bisher habe ich keine Probleme mit dieser Methode festgestellt. Ich sehe, dass VinGarcia in einem Kommentar zu einer der mysqladmin pingAntworten etwas Ähnliches vorgeschlagen hat .

Matt Kramer
quelle
5

Der folgende Health-Check funktioniert für alle meine MySQL-Container:

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
Entwickler10214
quelle
5

Ich bin mir also nicht sicher, ob jemand dies gepostet hat. Es sieht nicht so aus, wie es jemand getan hat, also ... gibt es in mysqladmin einen Befehl, der eine Wartezeit enthält, das Testen der Verbindung übernimmt, dann intern erneut versucht und nach Abschluss einen Erfolg zurückgibt.

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

Der wichtige Teil ist mysqladmin ping -h 127.0.0.1 -u root --password=MY_ROOT_PASS --wait=30 -vmit dem --waitWesen des Flag zu warten , bis die Verbindung erfolgreich ist , und die Zahl ist die Menge an Versuchen , erneut zu versuchen.

Idealerweise würden Sie diesen Befehl im Docker-Container ausführen, aber ich wollte den ursprünglichen Poster-Befehl nicht zu stark ändern.

Bei Verwendung in meiner make-Datei zur Initialisierung

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
ctatro85
quelle
3

Ich hatte das gleiche Problem, als mein Django-Container kurz nach dem Start versuchte, den MySQL-Container zu verbinden. Ich habe es mit dem Skript wait-for.it.sh von vishnubob gelöst . Es ist ein Shell-Skript, das darauf wartet, dass eine IP und ein Host bereit sind, bevor es fortfährt. Hier ist das Beispiel, das ich für meine Anwendung verwende.

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

In diesem Skript fordere ich den MySQL-Container auf, maximal 90 Sekunden (er wird normal ausgeführt, wenn er fertig ist) in Port 3306 (Standard-MySQL-Port) und dem vom Docker für meinen MYSQL_CONTAINER_NAME zugewiesenen Host zu warten. Das Skript hat mehr Variablen, aber für mw hat mit diesen drei gearbeitet.

Fabian Andres Merchan Jimenez
quelle
1

Wenn der Docker-Container, der auf einen MySQL-Container wartet, auf einem Python-Image basiert (z. B. für eine Django-Anwendung) , können Sie den folgenden Code verwenden.

Vorteile sind:

  • Es basiert nicht auf wait-for-it.sh , das darauf wartet, dass die IP und der Port von MySQL bereit sind, aber dies bedeutet nicht automatisch auch, dass die MySQL-Initialisierung abgeschlossen ist.
  • Es handelt sich nicht um ein Shell-Skript, das auf einer ausführbaren MySQL- oder MySQLladmin-Datei basiert, die in Ihrem Container vorhanden sein muss. Da Ihr Container auf einem Python-Image basiert, muss MySQL über diesem Image installiert werden. Mit der folgenden Lösung verwenden Sie die Technologie, die bereits im Container vorhanden ist: 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()

Verwendung:

  1. Fügen Sie diesen Code beispielsweise in eine Python-Datei wait-for-mysql-db.pyim Quellcode Ihrer Anwendung ein.
  2. Schreiben Sie ( startup.pyzum Beispiel) ein anderes Python-Skript , das zuerst den obigen Code ausführt und anschließend Ihre Anwendung startet.
  3. Stellen Sie sicher, dass das Dockerfile Ihres Anwendungscontainers diese beiden Python-Skripte zusammen mit dem Quellcode der Anwendung in ein Docker-Image packt.
  4. Konfigurieren Sie in Ihrer Docker-Compose-Datei Ihren Anwendungscontainer mit : command: ["python3", "startup.py"].

Beachten Sie, dass diese Lösung für eine MySQL-Datenbank entwickelt wurde. Sie müssen es leicht für eine andere Datenbank anpassen.

Sander Vanden Hautte
quelle
1

Ich habe eine neue Lösung für dieses Problem entwickelt, die auf einem neuen Ansatz basiert. Alle Ansätze, die ich gefunden habe, basieren auf einem Skript, das immer wieder versucht, eine Verbindung zur Datenbank herzustellen oder eine TCP-Verbindung mit dem Container herzustellen. Die vollständigen Details finden Sie im waitdb- Repository. Meine Lösung besteht jedoch darin, sich auf das aus dem Container abgerufene Protokoll zu verlassen. Das Skript wartet, bis das Protokoll die Nachricht auslöst, die für Verbindungen bereit ist . Das Skript kann feststellen, ob der Container zum ersten Mal gestartet wird. In diesem Fall wartet das Skript, bis das erste Datenbankskript ausgeführt und die Datenbank neu gestartet wurde, und wartet erneut auf eine neue verbindungsbereite Nachricht. Ich habe diese Lösung unter MySQL 5.7 und MySQL 8.0 getestet.

Das Skript selbst ( 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!"

Das Skript kann in Docker Compose oder in Docker selbst verwendet werden. Ich hoffe, die folgenden Beispiele machen die Verwendung klar:

Beispiel 01: Verwenden mit Docker Compose

SERVICE_NAME="mysql" && \
docker-compose up -d $SERVICE_NAME && \
./wait_db.sh docker-compose --no-color $SERVICE_NAME

Beispiel 02: Verwenden mit 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

Beispiel 3: Ein vollständiges Beispiel (der Testfall)

Ein vollständiges Beispiel finden Sie im Testfall des Repositorys . Dieser Testfall startet ein neues MySQL, erstellt eine Dummy-Datenbank, wartet, bis alles gestartet ist, und löst dann eine Auswahl aus , um zu überprüfen, ob alles in Ordnung ist. Danach wird der Container neu gestartet und auf den Start gewartet. Anschließend wird eine neue Auswahl ausgelöst, um zu überprüfen, ob die Verbindung hergestellt werden kann.

Marcelo Barros
quelle
1

So habe ich die Adams- Lösung in mein Docker-Compose-basiertes Projekt integriert:

Erstellt eine Bash-Datei mit dem Titel db-ready.shin meinem serverContainer-Ordner (deren Inhalt in meinen Container kopiert wird - 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

Ich kann dann ausführen, docker-compose run server sh ./db-ready.sh && docker-compose run server yarn run migrateum sicherzustellen, dass die Datenbank Verbindungen akzeptiert , wenn ich meine migrateAufgabe in meinem serverContainer ausführe .

Ich mag diesen Ansatz, da die Bash-Datei von jedem Befehl getrennt ist, den ich ausführen möchte. Ich könnte die db-ready.shvor jeder anderen Datenbank mit der von mir ausgeführten Aufgabe leicht ausführen.

S ..
quelle
0

In Ihrem ENTRYPOINTSkript müssen Sie überprüfen, ob Sie eine gültige MySQL-Verbindung haben oder nicht.

Diese Lösung erfordert nicht, dass Sie einen MySQL-Client auf dem Container installieren, und während der Ausführung des Containers mit php:7.0-fpmAusführen ncwar dies keine Option, da er ebenfalls installiert werden musste. Die Überprüfung, ob der Port geöffnet ist, bedeutet nicht unbedingt, dass der Dienst ordnungsgemäß ausgeführt und verfügbar gemacht wird. [mehr davon]

In dieser Lösung werde ich Ihnen zeigen, wie Sie ein PHP-Skript ausführen, um zu überprüfen, ob ein MySQL-Container eine Verbindung herstellen kann. Wenn Sie wissen möchten, warum ich dies für einen besseren Ansatz halte, lesen Sie meinen Kommentar hier .

Datei 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

Auf diese Weise blockieren Sie im Wesentlichen jede Bootstrapping-Logik Ihres Containers, BIS Sie eine gültige MySQL-Verbindung haben.

Starx
quelle
0

Ich benutze den folgenden 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);
Erdinç Çorbacı
quelle