Docker-compose comprueba si la conexión mysql está lista

91

Estoy tratando de asegurarme de que el contenedor de mi aplicación no ejecute migraciones / inicio hasta que el contenedor db se inicie y esté LISTO PARA aceptar conexiones.

Así que decidí usar la verificación de estado y depende de la opción en el archivo de composición de la ventana acoplable v2.

En la aplicación, tengo lo siguiente

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

La base de datos, por otro lado, tiene la siguiente verificación de estado

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

He probado un par de enfoques como:

  1. asegurándose de que se crea el db DIR test: ["CMD", "test -f var/lib/mysql/db"]
  2. Obteniendo la versión de mysql: test: ["CMD", "echo 'SELECT version();'| mysql"]
  3. Haga ping al administrador (marca el contenedor de la base de datos como saludable pero no parece ser una prueba válida) test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]

¿Alguien tiene una solución para esto?

John Kariuki
fuente
¿Creaste una ventana acoplable para una base de datos? Por favor dígame que sus datos están fuera de este contenedor por el bien de la salud de su aplicación
Jorge Campos
O al menos este es un contenedor de prueba.
Jorge Campos
Esto es solo para fines de desarrollo / prueba SOLAMENTE en realidad.
John Kariuki
2
Creo que debería usar un comando para conectarse y ejecutar una consulta en mysql, ninguna de las muestras que proporcionó hace esto: algo como:mysql -u USER -p PASSWORD -h MYSQLSERVERNAME -e 'select * from foo...' database-name
Jorge Campos
1
@JorgeCampos Está bien, gracias. Por lo general, tengo un contenedor db, pero mapeo volúmenes para el directorio de datos. De modo que si el contenedor dejaba de funcionar, los datos persistirían hasta la siguiente instanciación.
S ..

Respuestas:

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

El contenedor api no se iniciará hasta que el contenedor db esté en buen estado (básicamente hasta que mysqladmin esté activo y aceptando conexiones).

John Kariuki
fuente
12
mysqladmin pingdevolverá un falso positivo si el servidor se está ejecutando pero aún no acepta conexiones.
halfpastfour.am
53
Solo para su información para las personas de 2017: conditionmenos depends_onno es compatible con la versión 3+
Mint
@BobKruithof Estoy enfrentando el mismo problema ... ¿hay alguna solución, algo como el estado de suspensión o salida para reintentar?
Mukesh Agarwal
1
@dKen vea mi respuesta a continuación stackoverflow.com/a/45058879/279272 , espero que también funcione para usted.
Mukesh Agarwal
1
Para verificar esto usando contraseña: test: ["CMD", 'mysqladmin', 'ping', '-h', 'localhost', '-u', 'root', '-p$$MYSQL_ROOT_PASSWORD' ]- si lo definió MYSQL_ROOT_PASSWORDen la environmentssección.
laimison
22

Si está utilizando docker-compose v3 + , se ha eliminadocondition una opción de .depends_on

La ruta recomendada es utilizar en lugar wait-for-it, dockerizeo wait-for. En su docker-compose.ymlarchivo, cambie su comando para que sea:

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

Personalmente lo prefiero, wait-forya que puede funcionar en un contenedor Alpine ( shcompatible, sin dependencia bash). El inconveniente es que depende de netcat, por lo que si decides usarlo, asegúrate de haberlo netcatinstalado en el contenedor, o instálalo en tu Dockerfile, por ejemplo con:

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

También bifurqué elwait-for proyecto para que pueda verificar el estado de HTTP saludable (usa wget). Entonces puedes hacer algo como eso:

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

PD: Un RP también está listo para fusionarse para agregar esa capacidad al wait-forproyecto.

Capripot
fuente
13

Esto debería ser suficiente

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
fuente
2
cuál es el doble $de?
InsOp
5
La sintaxis especial @InsOp que debe usar en el comando de prueba de verificación de estado para escapar de las variables env comienza con $, es decir, $$ MYSQL_PASSWORD dará como resultado $ MYSQL_PASSWORD, que a su vez resultará en mypassword en este ejemplo concreto
Maksim Kostromin
Entonces, ¿con esto estoy accediendo a la variable env dentro del contenedor? con un solo $estoy accediendo a la variable env desde el host, ¿entonces supongo? eso es lindo gracias!
InsOp
10

Si puede cambiar el contenedor para esperar a que mysql esté listo, hágalo.

Si no tiene el control del contenedor al que desea conectar la base de datos, puede intentar esperar el puerto específico.

Para ese propósito, estoy usando un pequeño script para esperar un puerto específico expuesto por otro contenedor.

En este ejemplo, myserver esperará a que se pueda acceder al puerto 3306 del contenedor mydb .

# 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

Puede encontrar la documentación del script wait-for-it aquí

no no
fuente
Intenté usarlo wait-for-it.sh anteriormente pero anula el Dockerfile predeterminado, ¿verdad? ¿Cómo se ve el archivo entrypoint.sh?
John Kariuki
El punto de entrada depende de tu imagen. Puede verificarlo con docker inspect <image id>. Esto debe esperar a que el servicio esté disponible y llamar a su punto de entrada.
nono
Está bien ? Lo entiendes?
nono
Tener sentido. Sí.
John Kariuki
6
Advertencia: MySQL 5.5 (posiblemente versiones más nuevas también) puede responder mientras aún se inicializa.
Blaise
8

Hola para una simple verificación de estado usando docker-compose v2.1 , usé:

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

Básicamente, ejecuta un mysqlcomando simple SHOW DATABASES;usando como ejemplo el usuario rootcon la contraseña rootpasswd en la base de datos.

Si el comando tiene éxito, la base de datos está activa y lista, por lo que la ruta de verificación de estado. Puede usarlo intervalpara que pruebe a intervalos.

Eliminando el otro campo para visibilidad, así es como se vería en su 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
fuente
1
Advertencia: Con la "versión 3" del archivo de redacción, el soporte de "condición" ya no está disponible. Ver docs.docker.com/compose/compose-file/#depends_on
BartoszK
1
Debe usar la función de comando junto con el script wait-for-it.sh . Yo lo hago de esta manera:command: ["/home/app/jswebservice/wait-for-it.sh", "maria:3306", "--", "node", "webservice.js"]
BartoszK
@BartoszKI no lo entiendo. ¿Podría agregar una respuesta completa con detalles? Estoy enfrentando exactamente el mismo problema, pero no puedo hacerlo funcionar.
Thadeu Antonio Ferreira Melo
Asegúrese de que está utilizando la v2.1; de lo contrario, siga las nuevas pautas para la v3.0 y superior.
Sylhare
1
--execute \"SHOW DATABASES;\"es lo que me hizo esperar hasta que la base de datos estuviera disponible para que la aplicación
tuviera
6

Modifiqué docker-compose.ymlsegún el siguiente ejemplo y funcionó.

  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

En mi caso, ../schemaAndSeedDatacontiene varios archivos SQL de siembra de datos y esquemas. Design your own check scriptpuede ser similar a seguir select * from LastSchema.LastDBInsert.

Mientras que el código de contenedor dependiente de la web era

depends_on:
  mysql:
    condition: service_healthy
Mukesh Agarwal
fuente
Esto puede funcionar para usted, pero no estoy seguro de si es compatible con todos los motores MySQL.
halfpastfour.am
Estoy hablando de motores de base de datos como InnoDB, MyISAM, etc. ¿Es LastSchema.LastDBInsertun motor de base de datos predeterminado o específico de MySQL?
halfpastfour.am
No, tampoco es un valor predeterminado en mysql. Fue solo una muestra. una consulta ficticia.
Mukesh Agarwal
5
Advertencia: Con la "versión 3" del archivo de redacción, el soporte de "condición" ya no está disponible. Ver docs.docker.com/compose/compose-file/#depends_on
BartoszK
4

Tuve el mismo problema, creé un script bash externo para este propósito (está inspirado en la respuesta de Maxim). Reemplazar mysql-container-namepor el nombre de su contenedor MySQL y también se necesita contraseña / usuario:

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

En mi MakeFile, llamo a este script justo después de mi docker-composellamada:

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

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

Entonces puedo llamar a otros comandos sin tener el error:

Se produjo una excepción en el controlador: SQLSTATE [HY000] [2006] El servidor MySQL desapareció

Ejemplo de salida:

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.

He eliminado la verificación de estado, ya que ahora es inútil con este enfoque.

Bobina
fuente
3

REINICIAR EN CASO DE FALLO

Dado que v3 condition: service_healthyya no está disponible. La idea es que el desarrollador debería implementar un mecanismo para la recuperación de fallos dentro de la propia aplicación. Sin embargo, para casos de uso simples, una forma sencilla de resolver este problema es utilizar restartoption.

Si el estado del servicio mysql hace que su aplicación se exited with code 1apague, puede usar una de restartlas opciones de política disponibles. p.ej,on-failure

version: "3"

services:

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

Añadiendo una solución actualizada para el enfoque de chequeo de salud. Fragmento 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; }

Explicación : Dado que mysqladmin pingdevuelve falsos positivos (especialmente para una contraseña incorrecta), estoy guardando la salida en una variable temporal y luego la uso greppara encontrar la salida esperada ( mysqld is alive). Si lo encuentra, devolverá el código de error 0. En caso de que no se encuentre, estoy imprimiendo el mensaje completo y devolviendo el código de error 1.

Fragmento extendido:

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

Explicación : Estoy usando secretos de Docker en lugar de variables env (pero esto también se puede lograr con vars env regulares). El uso de $$es para un $signo literal que se elimina cuando se pasa al contenedor.

Salida de docker inspect --format "{{json .State.Health }}" db | jqen varias ocasiones:

Todo 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 no está activo (todavía):

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

Contraseña incorrecta:

{
  "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
fuente