Docker-compose vérifie si la connexion mysql est prête

91

J'essaie de m'assurer que mon conteneur d'application n'exécute pas les migrations / démarre tant que le conteneur DB n'est pas démarré et PRÊT À accepter les connexions.

J'ai donc décidé d'utiliser le contrôle de santé et dépend de l'option dans le fichier docker compose v2.

Dans l'application, j'ai les éléments suivants

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

La base de données, d'autre part, a le contrôle de santé suivant

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

J'ai essayé quelques approches comme:

  1. s'assurer que le db DIR est créé test: ["CMD", "test -f var/lib/mysql/db"]
  2. Obtenir la version mysql: test: ["CMD", "echo 'SELECT version();'| mysql"]
  3. Ping the admin (marque le conteneur db comme sain mais ne semble pas être un test valide) test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]

Quelqu'un at-il une solution à cela?

John Kariuki
la source
Vous avez créé un docker pour une base de données? Veuillez me dire que vos données sont en dehors de ce conteneur pour le bien de la santé de votre application
Jorge Campos
Ou du moins c'est un conteneur de test.
Jorge Campos
C'est uniquement à des fins de développement / test UNIQUEMENT.
John Kariuki
2
Je pense que vous devriez utiliser une commande pour vous connecter et exécuter une requête dans mysql, aucun des exemples que vous avez fournis ne le fait: quelque chose comme:mysql -u USER -p PASSWORD -h MYSQLSERVERNAME -e 'select * from foo...' database-name
Jorge Campos
1
@JorgeCampos D'accord, merci. Habituellement, j'ai un conteneur db, mais mappe les volumes pour le répertoire de données. Ainsi, si le conteneur tombait en panne, les données persisteraient jusqu'à sa prochaine instanciation.
S ..

Réponses:

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

Le conteneur d'API ne démarrera pas tant que le conteneur de base de données ne sera pas sain (essentiellement tant que mysqladmin ne sera pas opérationnel et n'acceptera pas les connexions.)

John Kariuki
la source
12
mysqladmin pingrenverra un faux positif si le serveur est en cours d'exécution mais n'accepte pas encore les connexions.
halfpastfour.am
53
Just FYI to people of 2017: conditionunder depends_onn'est pas pris en charge dans la version 3+
Mint
@BobKruithof Je suis confronté au même problème ... y a-t-il un travail autour, quelque chose comme l'état de sommeil ou de sortie pour réessayer
Mukesh Agarwal
1
@dKen voir ma réponse ci-dessous stackoverflow.com/a/45058879/279272 , j'espère que cela fonctionnera pour vous aussi.
Mukesh Agarwal
1
Pour vérifier cela en utilisant le mot de passe: test: ["CMD", 'mysqladmin', 'ping', '-h', 'localhost', '-u', 'root', '-p$$MYSQL_ROOT_PASSWORD' ]- si vous avez défini MYSQL_ROOT_PASSWORDdans la environmentssection
laimison
22

Si vous utilisez docker-compose v3 + , conditionl'option de depends_ona été supprimée .

Le chemin est recommandé d'utiliser plutôt wait-for-it, dockerizeou wait-for. Dans votre docker-compose.ymlfichier, changez votre commande pour être:

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

Personnellement, je préfère wait-forcar il peut fonctionner dans un conteneur Alpine ( shcompatible, sans dépendance bash). L'inconvénient est que cela dépend denetcat , donc si vous décidez de l'utiliser, assurez-vous de l'avoir netcatinstallé dans le conteneur, ou installez-le dans votre Dockerfile, par exemple avec:

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

J'ai également forké lewait-for projet afin qu'il puisse vérifier l'état HTTP sain (il utilisewget ). Ensuite, vous pouvez faire quelque chose comme ça:

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

PS: Un PR est également prêt à être fusionné pour ajouter cette capacité au wait-forprojet.

Capripot
la source
13

Cela devrait suffire

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
la source
2
à quoi sert le double $?
InsOp
5
@InsOp syntaxe spéciale que vous devez utiliser dans la commande de test de vérification de l'état pour échapper les variables env commence par $, c'est-à-dire que $$ MYSQL_PASSWORD se traduira par $ MYSQL_PASSWORD, qui lui-même se traduira par mypassword dans cet exemple concret
Maksim Kostromin
Donc, avec cela, j'accède à la variable env à l'intérieur du conteneur? avec un seul $Im accédant à la variable env de l'hôte alors je suppose? c'est gentil merci!
InsOp
10

Si vous pouvez changer le conteneur pour attendre que mysql soit prêt, faites-le.

Si vous n'avez pas le contrôle du conteneur auquel vous souhaitez connecter la base de données, vous pouvez essayer d'attendre le port spécifique.

Pour cela, j'utilise un petit script pour attendre un port spécifique exposé par un autre conteneur.

Dans cet exemple, myserver attendra que le port 3306 du conteneur mydb soit accessible.

# 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

Vous pouvez trouver la documentation du script wait-for-it ici

non non
la source
J'ai essayé d'utiliser wait-for-it.sh plus tôt mais cela remplace le Dockerfile par défaut, n'est-ce pas? À quoi ressemble entrypoint.sh?
John Kariuki
Le point d'entrée dépend de votre image. Vous pouvez le vérifier avec docker inspect <image id>. Cela devrait attendre que le service soit disponible et appeler votre point d'entrée.
nono
Est-ce que c'est bon ? Comprenez vous?
nono
Ça a du sens. Oui.
John Kariuki
6
Attention: MySQL 5.5 (peut-être aussi des versions plus récentes) peut répondre pendant l'initialisation.
Blaise du
8

Bonjour pour un simple bilan de santé à l'aide de docker-compose v2.1 , j'ai utilisé:

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

Fondamentalement, il exécute une mysqlcommande simple en SHOW DATABASES;utilisant comme exemple l'utilisateur rootavec le mot rootpasswd de passe dans la base de données.

Si la commande réussit, la base de données est active et prête donc le chemin de vérification de l'état. Vous pouvez l'utiliser intervalpour qu'il teste à intervalle.

En supprimant l'autre champ pour la visibilité, voici à quoi il ressemblerait dans votre 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
la source
1
Attention: Avec la "version 3" du fichier de composition, le support "condition" n'est plus disponible. Voir docs.docker.com/compose/compose-file/#depends_on
BartoszK
1
Vous devez utiliser la fonction de commande avec le script wait-for-it.sh . Moi le faisant de cette façon:command: ["/home/app/jswebservice/wait-for-it.sh", "maria:3306", "--", "node", "webservice.js"]
BartoszK
@BartoszKJe ne comprends pas. Pourriez-vous s'il vous plaît ajouter une réponse complète avec des détails? Je suis confronté au même problème, mais je ne peux pas le faire fonctionner.
Thadeu Antonio Ferreira Melo
Assurez-vous que vous utilisez la version 2.1, sinon suivez les nouvelles directives pour la version 3.0 et supérieure.
Sylhare
1
--execute \"SHOW DATABASES;\"est ce qui m'a fait attendre que la base de données soit disponible pour que l'application puisse y accéder
tsuz
6

J'ai modifié le docker-compose.ymlselon l'exemple suivant et cela a fonctionné.

  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

Dans mon cas, ../schemaAndSeedDatacontient plusieurs fichiers sql de schéma et de données d'amorçage. Design your own check scriptpeut être similaire au suivant select * from LastSchema.LastDBInsert.

Alors que le code de conteneur dépendant du Web était

depends_on:
  mysql:
    condition: service_healthy
Mukesh Agarwal
la source
Cela peut fonctionner pour vous, mais je ne sais pas si cela est pris en charge dans tous les moteurs MySQL.
halfpastfour.am
Je parle de moteurs de base de données comme InnoDB, MyISAM, etc. Un LastSchema.LastDBInsertmoteur de base de données par défaut ou MySQL est-il spécifique?
halfpastfour.am
Non, ce n'est pas non plus une valeur par défaut dans mysql. C'était juste un échantillon. une requête factice.
Mukesh Agarwal
5
Attention: Avec la "version 3" du fichier de composition, le support "condition" n'est plus disponible. Voir docs.docker.com/compose/compose-file/#depends_on
BartoszK
4

J'ai eu le même problème, j'ai créé un script de bash externe à cet effet (il est inspiré de la réponse de Maxim). Remplacez mysql-container-namepar le nom de votre conteneur MySQL et un mot de passe / utilisateur est également nécessaire:

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

Dans mon MakeFile, j'appelle ce script juste après mon docker-composeappel:

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

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

Ensuite, je peux appeler d'autres commandes sans avoir l'erreur:

Une exception s'est produite dans le pilote: SQLSTATE [HY000] [2006] Le serveur MySQL a disparu

Exemple de sortie:

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.

J'ai supprimé le bilan de santé car il est désormais inutile avec cette approche.

Bobine
la source
3

Ajout d'une solution mise à jour pour l'approche Healthcheck. Extrait simple:

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

Explication : Puisque mysqladmin pingrenvoie des faux positifs (en particulier pour un mot de passe incorrect), j'enregistre la sortie dans une variable temporaire, puis j'utilise greppour trouver la sortie attendue (mysqld is alive ). S'il est trouvé, il renverra le code d'erreur 0. Au cas où il ne serait pas trouvé, j'imprime le message entier et je renvoie le code d'erreur 1.

Extrait étendu:

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

Explication : J'utilise des secrets de docker au lieu de variables env (mais cela peut également être réalisé avec des variables d'environnement régulières). L'utilisation de $$est pour littéral$ signe qui est supprimé lorsqu'il est passé au conteneur.

Sortie de docker inspect --format "{{json .State.Health }}" db | jqà diverses occasions:

Tout va bien:

{
  "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 n'est pas (encore) en place:

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

Mauvais mot de passe:

{
  "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
la source
3

REDÉMARRER EN CAS D'ÉCHEC

Depuis la v3 condition: service_healthyn'est plus disponible. L'idée est que le développeur doit implémenter un mécanisme de récupération après incident dans l'application elle-même. Cependant, pour les cas d'utilisation simples, un moyen simple de résoudre ce problème consiste à utiliser l' restartoption.

Si l'état du service mysql provoque votre application, exited with code 1vous pouvez utiliser l'une des restartoptions de stratégie disponibles. par exemple,on-failure

version: "3"

services:

    app:
      ...
      depends_on:
        - db:
      restart: on-failure
Hamid Asghari
la source