Docker-compose controlla se la connessione mysql è pronta


96

Sto cercando di assicurarmi che il mio contenitore di app non esegua le migrazioni / inizi finché il contenitore db non viene avviato e PRONTO PER accettare le connessioni.

Quindi ho deciso di utilizzare il controllo dell'integrità e dipende dall'opzione nella finestra mobile compose file v2.

Nell'app ho quanto segue

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

Il db d'altra parte ha il seguente healthcheck

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

Ho provato un paio di approcci come:

  1. assicurandosi che venga creato il db DIR test: ["CMD", "test -f var/lib/mysql/db"]
  2. Ottenere la versione mysql: test: ["CMD", "echo 'SELECT version();'| mysql"]
  3. Ping the admin (contrassegna il contenitore db come integro ma non sembra essere un test valido) test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]

Qualcuno ha una soluzione a questo?


Hai creato una finestra mobile per un DB? Per favore dimmi che i tuoi dati sono fuori da questo contenitore per il bene della tua applicazione
Jorge Campos

O almeno questo è un contenitore di prova.
Jorge Campos

Questo è solo per scopi di sviluppo / test in realtà.
John Kariuki

2
Penso che dovresti usare un comando per connetterti ed eseguire una query in mysql, nessuno degli esempi che hai fornito fa questo: qualcosa come:mysql -u USER -p PASSWORD -h MYSQLSERVERNAME -e 'select * from foo...' database-name
Jorge Campos

1
@JorgeCampos Va bene grazie. Di solito ho un contenitore db, ma mappare i volumi per la directory dei dati. In modo che se il contenitore si interrompesse, i dati persisterebbero fino alla successiva istanza.
S ..

Risposte:


86
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

Il contenitore api non si avvierà fino a quando il contenitore db non sarà integro (fondamentalmente fino a quando mysqladmin non sarà attivo e accetterà connessioni).


12
mysqladmin pingrestituirà un falso positivo se il server è in esecuzione ma non accetta ancora le connessioni.
halfpastfour.am

55
FYI per le persone del 2017: conditionsotto depends_onnon è supportato nella versione 3+
Mint

@BobKruithof Sto affrontando lo stesso problema ... c'è qualche soluzione, qualcosa come il sonno o lo stato di uscita per riprovare
Mukesh Agarwal

1
@dKen vedi la mia risposta sotto stackoverflow.com/a/45058879/279272 , spero che funzioni anche per te.
Mukesh Agarwal

1
Per verificarlo utilizzando la password: test: ["CMD", 'mysqladmin', 'ping', '-h', 'localhost', '-u', 'root', '-p$$MYSQL_ROOT_PASSWORD' ]- se definita MYSQL_ROOT_PASSWORDnella environmentssezione.
laimison

23

Se stai usando docker-compose v3 + , conditionun'opzione di depends_onè stata rimossa .

Il percorso consigliato è quello di utilizzare piuttosto wait-for-it, dockerizeo wait-for. Nel tuo docker-compose.ymlfile, cambia il tuo comando in:

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

Personalmente preferisco wait-forperché può funzionare in un container alpino ( shcompatibile, nessuna dipendenza da bash). Lo svantaggio è che dipende da netcat, quindi se decidi di usarlo, assicurati di averlo netcatinstallato nel container o installalo nel tuo Dockerfile, ad esempio con:

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

Ho anche biforcato ilwait-for progetto in modo che possa controllare lo stato HTTP sano (utilizza wget). Quindi puoi fare qualcosa del genere:

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

PS: anche un PR è pronto per essere unito per aggiungere quella capacità al wait-forprogetto.


15

Questo dovrebbe bastare

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

2
a cosa serve il doppio $?
InsOp

5
La sintassi speciale @InsOp che devi utilizzare nel comando di test di controllo dello stato per l'escape delle variabili env inizia con $, ovvero $$ MYSQL_PASSWORD risulterà in $ MYSQL_PASSWORD, che a sua volta si tradurrà in
mia password

Quindi con questo sto accedendo alla variabile env all'interno del contenitore? con un singolo $Im che accede alla variabile env dall'host, suppongo? è bello grazie!
InsOp

10

Se puoi cambiare il contenitore per aspettare che mysql sia pronto fallo.

Se non hai il controllo del contenitore a cui desideri connettere il database, puoi provare ad attendere la porta specifica.

A tale scopo, sto utilizzando un piccolo script per attendere una porta specifica esposta da un altro contenitore.

In questo esempio, il server attenderà che la porta 3306 del contenitore mydb sia raggiungibile.

# 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

Puoi trovare la documentazione dello script wait-for-it qui


Ho provato a utilizzare in wait-for-it.sh precedenza ma sovrascrive il Dockerfile predefinito, giusto? Come appare il file entrypoint.sh?
John Kariuki

Il punto di ingresso dipende dalla tua immagine. Puoi verificarlo con docker inspect <ID immagine>. Questo dovrebbe attendere che il servizio sia disponibile e chiamare il punto di ingresso.
nono

Va bene ? Capisci?
nono

Ha senso. Sì.
John Kariuki

6
Attenzione: MySQL 5.5 (possibilmente anche versioni più recenti) può rispondere durante l'inizializzazione.
Blaise

8

Salve per un semplice controllo dello stato di salute utilizzando docker -compose v2.1 , ho usato:

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

Fondamentalmente esegue un semplice mysqlcomando SHOW DATABASES;utilizzando come esempio l'utente rootcon la password rootpasswd nel database.

Se il comando ha successo il db è attivo e pronto quindi il percorso healthcheck. È possibile utilizzare in intervalmodo che esegua i test a intervalli.

Rimuovendo l'altro campo per la visibilità, ecco come apparirebbe nel tuo file 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

1
Attenzione: con la "versione 3" del file di composizione, il supporto per la "condizione" non è più disponibile. Vedi docs.docker.com/compose/compose-file/#depends_on
BartoszK

1
Dovresti usare la funzione di comando insieme allo script wait-for-it.sh . Io lo faccio in questo modo:command: ["/home/app/jswebservice/wait-for-it.sh", "maria:3306", "--", "node", "webservice.js"]
BartoszK

@BartoszKI non lo capisco. Potresti aggiungere una risposta completa con i dettagli? Sto affrontando lo stesso identico problema, ma non riesco a farlo funzionare.
Thadeu Antonio Ferreira Melo

Assicurati di utilizzare la v2.1, altrimenti segui le nuove linee guida per la v3.0 e successive.
Sylhare

1
--execute \"SHOW DATABASES;\"è ciò che mi ha fatto aspettare fino a quando il database non era disponibile per l'accesso
all'applicazione

6

Ho modificato docker-compose.ymlcome nell'esempio seguente e ha funzionato.

  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

Nel mio caso ../schemaAndSeedDatacontiene più schemi e file SQL di seeding dei dati. Design your own check scriptpuò essere simile al seguente select * from LastSchema.LastDBInsert.

Mentre il codice del contenitore dipendente dal Web era

depends_on:
  mysql:
    condition: service_healthy

Questo potrebbe funzionare per te, ma non sono sicuro che sia supportato o meno in tutti i motori MySQL.
halfpastfour.am

Sto parlando di motori di database come InnoDB, MyISAM ecc. L' LastSchema.LastDBInsertimpostazione predefinita di MySQL o il motore di database è specifico?
halfpastfour.am

No, non è nemmeno un'impostazione predefinita in mysql. Era solo un campione. una query fittizia.
Mukesh Agarwal

6
Attenzione: con la "versione 3" del file di composizione, il supporto per la "condizione" non è più disponibile. Vedi docs.docker.com/compose/compose-file/#depends_on
BartoszK

4

Aggiunta di una soluzione aggiornata per l'approccio healthcheck. Snippet semplice:

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

Spiegazione : poiché mysqladmin pingrestituisce falsi positivi (specialmente per password errate), sto salvando l'output in una variabile temporanea, quindi lo uso grepper trovare l'output ( mysqld is alive) previsto . Se trovato restituirà il codice di errore 0. Nel caso in cui non venga trovato, stampo l'intero messaggio e restituisco il codice di errore 1.

Snippet esteso:

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

Spiegazione : sto usando i segreti di finestra mobile invece delle variabili env (ma questo può essere ottenuto anche con le normali variabili env). L'uso di $$è per il $segno letterale che viene rimosso quando viene passato al contenitore.

Uscita da docker inspect --format "{{json .State.Health }}" db | jqin varie occasioni:

Tutto bene:

{
  "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 non è (ancora) attivo:

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

Password errata:

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

3

Ho avuto lo stesso problema, ho creato uno script bash esterno per questo scopo (è ispirato alla risposta di Maxim). Sostituisci mysql-container-namecon il nome del tuo contenitore MySQL e anche la password / utente è necessaria:

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

Nel mio MakeFile, chiamo questo script subito dopo la mia docker-composechiamata:

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

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

Quindi posso chiamare altri comandi senza avere l'errore:

Si è verificata un'eccezione nel driver: SQLSTATE [HY000] [2006] Il server MySQL è andato via

Esempio di output:

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.

Ho cancellato il controllo dello stato di salute poiché ora è inutile con questo approccio.


2

RIAVVIA IN CASO DI MANCATO

Poiché la v3 condition: service_healthynon è più disponibile. L'idea è che lo sviluppatore debba implementare un meccanismo per il ripristino in caso di arresto anomalo all'interno dell'app stessa. Tuttavia, per casi d'uso semplici, un modo semplice per risolvere questo problema è usare l' restartopzione.

Se lo stato del servizio mysql causa l'accesso alla tua applicazione exited with code 1, puoi utilizzare una delle restartopzioni di policy disponibili. per esempio,on-failure

version: "3"

services:

    app:
      ...
      depends_on:
        - db:
      restart: on-failure
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.