Docker-Compose prüft, ob die MySQL-Verbindung bereit ist

91

Ich versuche sicherzustellen, dass mein App-Container keine Migrationen / Starts ausführt, bis der Datenbankcontainer gestartet wird und BEREIT ist, Verbindungen zu akzeptieren.

Also habe ich mich für den Healthcheck entschieden und bin abhängig von der Option in Docker Compose File v2.

In der App habe ich folgendes

app:
    ...
    depends_on:
      db:
      condition: service_healthy

Die Datenbank hat dagegen den folgenden Healthcheck

db:
  ...
  healthcheck:
    test: TEST_GOES_HERE
    timeout: 20s
    retries: 10

Ich habe einige Ansätze ausprobiert wie:

  1. Stellen Sie sicher, dass die Datenbank DIR erstellt wurde test: ["CMD", "test -f var/lib/mysql/db"]
  2. Erhalten der MySQL-Version: test: ["CMD", "echo 'SELECT version();'| mysql"]
  3. Pingen Sie den Administrator an (markiert den Datenbankcontainer als fehlerfrei, scheint aber kein gültiger Test zu sein) test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]

Hat jemand eine Lösung dafür?

John Kariuki
quelle
Sie haben ein Docker für eine DB erstellt? Bitte sagen Sie mir, dass sich Ihre Daten aus Gründen Ihrer Anwendungsgesundheit außerhalb dieses Containers befinden
Jorge Campos
Oder zumindest ist dies ein Testcontainer.
Jorge Campos
Dies ist eigentlich NUR zu Entwicklungs- / Testzwecken.
John Kariuki
2
Ich denke, Sie sollten einen Befehl verwenden, um eine Verbindung herzustellen und eine Abfrage in MySQL auszuführen. Keines der von Ihnen bereitgestellten Beispiele tut dies: so etwas wie:mysql -u USER -p PASSWORD -h MYSQLSERVERNAME -e 'select * from foo...' database-name
Jorge Campos
1
@JorgeCampos Okay, danke. Normalerweise habe ich einen Datenbankcontainer, aber ordne Volumes für das Datenverzeichnis zu. Wenn der Container ausfällt, bleiben die Daten bis zur nächsten Instanziierung erhalten.
S ..

Antworten:

80
version: "2.1"
services:
    api:
        build: .
        container_name: api
        ports:
            - "8080:8080"
        depends_on:
            db:
                condition: service_healthy
    db:
        container_name: db
        image: mysql
        ports:
            - "3306"
        environment:
            MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
            MYSQL_USER: "user"
            MYSQL_PASSWORD: "password"
            MYSQL_DATABASE: "database"
        healthcheck:
            test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
            timeout: 20s
            retries: 10

Der API-Container wird erst gestartet, wenn der DB-Container fehlerfrei ist (im Grunde genommen, bis mysqladmin aktiv ist und Verbindungen akzeptiert).

John Kariuki
quelle
12
mysqladmin pinggibt ein falsches Positiv zurück, wenn der Server ausgeführt wird, aber noch keine Verbindungen akzeptiert.
halfpastfour.am
53
Nur zu Ihrer Information für Leute von 2017: conditionunter depends_onwird in Version 3+ nicht unterstützt
Mint
@ BobKruithof Ich stehe vor dem gleichen Problem ... gibt es irgendwelche Probleme, so etwas wie Schlaf oder Exit-Status für einen erneuten Versuch
Mukesh Agarwal
1
@dKen siehe meine Antwort unten stackoverflow.com/a/45058879/279272 , ich hoffe, es wird auch für Sie funktionieren.
Mukesh Agarwal
1
So überprüfen Sie dies mit einem Passwort: test: ["CMD", 'mysqladmin', 'ping', '-h', 'localhost', '-u', 'root', '-p$$MYSQL_ROOT_PASSWORD' ]- Wenn Sie MYSQL_ROOT_PASSWORDim environmentsAbschnitt definiert haben .
Laimison
22

Wenn Sie mit Docker-compose v3 + , conditionals Option depends_onwurde entfernt .

Der empfohlene Weg ist eher zu verwenden wait-for-it, dockerizeoder wait-for. docker-compose.ymlÄndern Sie in Ihrer Datei Ihren Befehl wie folgt:

command: sh -c 'bin/wait-for db:3306 -- bundle exec rails s'

Ich persönlich bevorzuge, wait-forda es in einem alpinen Container laufen kann ( shkompatibel, keine Abhängigkeit von bash). Der Nachteil ist, dass es davon abhängt. netcatWenn Sie sich also für die Verwendung entscheiden, stellen Sie sicher, dass Sie netcatim Container installiert haben , oder installieren Sie ihn in Ihrer Docker-Datei, zum Beispiel mit:

RUN apt-get -q update && apt-get -qy install netcat

Ich habe daswait-for Projekt auch gegabelt , damit es auf einen fehlerfreien HTTP-Status prüfen kann (es wird verwendet wget). Dann können Sie so etwas tun:

command: sh -c 'bin/wait-for http://api/ping -- jest test'

PS: Ein PR kann auch zusammengeführt werden, um diese Kapazität zum wait-forProjekt hinzuzufügen .

Capripot
quelle
12

Das sollte reichen

version: '2.1'
services:
  mysql:
    image: mysql
    ports: ['3306:3306']
    environment:
      MYSQL_USER: myuser
      MYSQL_PASSWORD: mypassword
    healthcheck:
      test: mysqladmin ping -h 127.0.0.1 -u $$MYSQL_USER --password=$$MYSQL_PASSWORD
Maksim Kostromin
quelle
2
Wofür ist das Doppel $?
InsOp
5
@InsOp spezielle Syntax, die Sie im Health Check Test-Befehl zum Escapen von env-Variablen verwenden müssen, beginnt mit $, dh $$ MYSQL_PASSWORD führt zu $ ​​MYSQL_PASSWORD, was in diesem konkreten Beispiel selbst zu mypassword führt
Maksim Kostromin
Also greife ich auf die env-Variable im Container zu? mit einem einzelnen $Im, der vom Host auf die env-Variable zugreift, nehme ich an? das ist schön danke!
InsOp
10

Wenn Sie den Container ändern können, um darauf zu warten, dass MySQL bereit ist, tun Sie es.

Wenn Sie nicht die Kontrolle über den Container haben, mit dem Sie die Datenbank verbinden möchten, können Sie versuchen, auf den bestimmten Port zu warten.

Zu diesem Zweck verwende ich ein kleines Skript, um auf einen bestimmten Port zu warten, der von einem anderen Container verfügbar gemacht wird.

In diesem Beispiel wartet myserver darauf, dass Port 3306 des mydb- Containers erreichbar ist.

# Your database
mydb:
  image: mysql
  ports:
    - "3306:3306"
  volumes:
    - yourDataDir:/var/lib/mysql

# Your server
myserver:
  image: myserver
  ports:
    - "....:...."
  entrypoint: ./wait-for-it.sh mydb:3306 -- ./yourEntryPoint.sh

Die Dokumentation zum Warten auf das Skript finden Sie hier

nono
quelle
Ich habe es wait-for-it.sh früher versucht, aber es überschreibt die Standard-Docker-Datei, oder? Wie sieht die entrypoint.sh aus?
John Kariuki
Der Einstiegspunkt hängt von Ihrem Bild ab. Sie können dies mit Docker inspect <Bild-ID> überprüfen. Dies sollte warten, bis der Dienst verfügbar ist, und Ihren Einstiegspunkt anrufen.
Nono
Ist es o.k ? Verstehst du?
Nono
Sinn ergeben. Ja.
John Kariuki
6
Warnung: MySQL 5.5 (möglicherweise auch neuere Versionen) kann während der Initialisierung reagieren.
Blaise
8

Hallo für einen einfachen Healthcheck mit Docker-Compose v2.1 , ich habe verwendet:

/usr/bin/mysql --user=root --password=rootpasswd --execute \"SHOW DATABASES;\"

Grundsätzlich wird ein einfacher mysqlBefehl ausgeführt SHOW DATABASES;, der als Beispiel den Benutzer rootmit dem Kennwort rootpasswd in der Datenbank verwendet.

Wenn der Befehl erfolgreich ist, ist die Datenbank aktiv und bereit, also der Healthcheck-Pfad. Sie können verwenden, intervaldamit es in Intervallen getestet wird.

Wenn Sie das andere Feld zur besseren Sichtbarkeit entfernen, sehen Sie hier, wie es in Ihrem Feld aussehen würde docker-compose.yaml.

version: '2.1'

  services:
    db:
      ... # Other db configuration (image, port, volumes, ...)
      healthcheck:
        test: "/usr/bin/mysql --user=root --password=rootpasswd --execute \"SHOW DATABASES;\""
        interval: 2s
        timeout: 20s
        retries: 10

     app:
       ... # Other app configuration
       depends_on:
         db:
         condition: service_healthy
Sylhare
quelle
1
Warnung: Mit "Version 3" der Erstellungsdatei ist die Unterstützung "Bedingung" nicht mehr verfügbar. Siehe docs.docker.com/compose/compose-file/#depends_on
BartoszK
1
Sie sollten die Befehlsfunktion zusammen mit dem Skript wait-for-it.sh verwenden . Ich mache es so:command: ["/home/app/jswebservice/wait-for-it.sh", "maria:3306", "--", "node", "webservice.js"]
BartoszK
@ BartoszKIch verstehe es nicht. Könnten Sie bitte eine vollständige Antwort mit Details hinzufügen? Ich habe genau das gleiche Problem, aber ich kann es nicht zum Laufen bringen.
Thadeu Antonio Ferreira Melo
Stellen Sie sicher, dass Sie Version 2.1 verwenden, andernfalls befolgen Sie die neuen Richtlinien für Version 3.0 und höher.
Sylhare
1
--execute \"SHOW DATABASES;\"
Deshalb habe
6

Ich habe das docker-compose.ymlwie im folgenden Beispiel geändert und es hat funktioniert.

  mysql:
    image: mysql:5.6
    ports:
      - "3306:3306"
    volumes:       
      # Preload files for data
      - ../schemaAndSeedData:/docker-entrypoint-initdb.d
    environment:
      MYSQL_ROOT_PASSWORD: rootPass
      MYSQL_DATABASE: DefaultDB
      MYSQL_USER: usr
      MYSQL_PASSWORD: usr
    healthcheck:
      test:  mysql --user=root --password=rootPass -e 'Design your own check script ' LastSchema

In meinem Fall ../schemaAndSeedDataenthält mehrere Schema- und Data-Seeding-SQL-Dateien. Design your own check scriptkann ähnlich sein wie folgt select * from LastSchema.LastDBInsert.

Während webabhängiger Containercode war

depends_on:
  mysql:
    condition: service_healthy
Mukesh Agarwal
quelle
Dies mag für Sie funktionieren, aber ich bin mir nicht sicher, ob dies in allen MySQL-Engines unterstützt wird oder nicht.
halfpastfour.am
Ich spreche von Datenbank-Engines wie InnoDB, MyISAM usw. Ist LastSchema.LastDBInsertein MySQL-Standard oder ein Datenbank-Engine spezifisch?
halfpastfour.am
Nein, es ist auch keine Standardeinstellung in MySQL. Es war nur eine Probe. eine Dummy-Abfrage.
Mukesh Agarwal
4
Warnung: Mit "Version 3" der Erstellungsdatei ist die Unterstützung "Bedingung" nicht mehr verfügbar. Siehe docs.docker.com/compose/compose-file/#depends_on
BartoszK
4

Ich hatte das gleiche Problem, ich habe ein externes Bash-Skript für diesen Zweck erstellt (es ist inspiriert von Maxim Antwort). Ersetzen Sie mysql-container-namedurch den Namen Ihres MySQL-Containers und es wird auch ein Passwort / Benutzer benötigt:

bin / wait-for-mysql.sh :

#!/bin/sh
until docker container exec -it mysql-container-name mysqladmin ping -P 3306 -proot | grep "mysqld is alive" ; do
  >&2 echo "MySQL is unavailable - waiting for it... 😴"
  sleep 1
done

In meinem MakeFile rufe ich dieses Skript direkt nach meinem docker-composeAufruf auf:

wait-for-mysql: ## Wait for MySQL to be ready
    bin/wait-for-mysql.sh

run: up wait-for-mysql reload serve ## Start everything...

Dann kann ich andere Befehle aufrufen, ohne den Fehler zu haben:

Im Treiber ist eine Ausnahme aufgetreten: SQLSTATE [HY000] [2006] Der MySQL-Server ist verschwunden

Ausgabebeispiel:

docker-compose -f docker-compose.yaml up -d
Creating network "strangebuzzcom_default" with the default driver
Creating sb-elasticsearch ... done
Creating sb-redis              ... done
Creating sb-db                 ... done
Creating sb-app                ... done
Creating sb-kibana             ... done
Creating sb-elasticsearch-head ... done
Creating sb-adminer            ... done
bin/wait-for-mysql.sh
MySQL is unavailable - waiting for it... 😴
MySQL is unavailable - waiting for it... 😴
MySQL is unavailable - waiting for it... 😴
MySQL is unavailable - waiting for it... 😴
MySQL is unavailable - waiting for it... 😴
MySQL is unavailable - waiting for it... 😴
MySQL is unavailable - waiting for it... 😴
MySQL is unavailable - waiting for it... 😴
mysqld is alive
php bin/console doctrine:cache:clear-metadata
// Clearing all Metadata cache entries
[OK] Successfully deleted cache entries.

Ich habe den Gesundheitscheck gelöscht, da er bei diesem Ansatz jetzt unbrauchbar ist.

Spule
quelle
3

NEUSTART BEI AUSFALL

Da v3 condition: service_healthynicht mehr verfügbar ist. Die Idee ist, dass der Entwickler einen Mechanismus für die Wiederherstellung nach einem Absturz in der App selbst implementieren sollte. Für einfache Anwendungsfälle besteht eine einfache Möglichkeit, dieses Problem zu beheben, darin, die restartOption zu verwenden .

Wenn der MySQL-Dienststatus Ihre Anwendung veranlasst, können exited with code 1Sie eine der restartverfügbaren Richtlinienoptionen verwenden. z.B,on-failure

version: "3"

services:

    app:
      ...
      depends_on:
        - db:
      restart: on-failure
Hamid Asghari
quelle
2

Hinzufügen einer aktualisierten Lösung für den Healthcheck-Ansatz. Einfacher Ausschnitt:

healthcheck:
  test: out=$$(mysqladmin ping -h localhost -P 3306 -u foo --password=bar 2>&1); echo $$out | grep 'mysqld is alive' || { echo $$out; exit 1; }

Erläuterung : Da mysqladmin pingfalsch positive Ergebnisse zurückgegeben werden (insbesondere bei falschem Kennwort), speichere ich die Ausgabe in einer temporären Variablen und verwende sie dann grep, um die erwartete Ausgabe zu finden ( mysqld is alive). Wenn es gefunden wird, wird der 0-Fehlercode zurückgegeben. Falls es nicht gefunden wird, drucke ich die gesamte Nachricht und gebe den 1-Fehlercode zurück.

Erweitertes Snippet:

version: "3.8"
services:
  db:
    image: linuxserver/mariadb
    environment:
      - FILE__MYSQL_ROOT_PASSWORD=/run/secrets/mysql_root_password
      - FILE__MYSQL_PASSWORD=/run/secrets/mysql_password
    secrets:
      - mysql_root_password
      - mysql_password
    healthcheck:
      test: out=$$(mysqladmin ping -h localhost -P 3306 -u root --password=$$(cat $${FILE__MYSQL_ROOT_PASSWORD}) 2>&1); echo $$out | grep 'mysqld is alive' || { echo $$out; exit 1; }

secrets:
  mysql_root_password:
    file: ${SECRETSDIR}/mysql_root_password
  mysql_password:
    file: ${SECRETSDIR}/mysql_password

Erläuterung : Ich verwende Docker-Geheimnisse anstelle von env-Variablen (dies kann jedoch auch mit regulären env-Variablen erreicht werden). Die Verwendung von $$ist für ein wörtliches $Zeichen, das beim Übergeben an den Container entfernt wird.

Ausgabe von docker inspect --format "{{json .State.Health }}" db | jqbei verschiedenen Gelegenheiten:

Alles in Ordnung:

{
  "Status": "healthy",
  "FailingStreak": 0,
  "Log": [
    {
    {
      "Start": "2020-07-20T01:03:02.326287492+03:00",
      "End": "2020-07-20T01:03:02.915911035+03:00",
      "ExitCode": 0,
      "Output": "mysqld is alive\n"
    }
  ]
}

DB ist (noch) nicht aktiv:

{
  "Status": "starting",
  "FailingStreak": 1,
  "Log": [
    {
      "Start": "2020-07-20T01:02:58.816483336+03:00",
      "End": "2020-07-20T01:02:59.401765146+03:00",
      "ExitCode": 1,
      "Output": "\u0007mysqladmin: connect to server at 'localhost' failed error: 'Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2 \"No such file or directory\")' Check that mysqld is running and that the socket: '/var/run/mysqld/mysqld.sock' exists!\n"
    }
  ]
}

Falsches Passwort:

{
  "Status": "unhealthy",
  "FailingStreak": 13,
  "Log": [
    {
      "Start": "2020-07-20T00:56:34.303714097+03:00",
      "End": "2020-07-20T00:56:34.845972979+03:00",
      "ExitCode": 1,
      "Output": "\u0007mysqladmin: connect to server at 'localhost' failed error: 'Access denied for user 'root'@'localhost' (using password: YES)'\n"
    }
  ]
}
Maxim_united
quelle