Docker-compose check se a conexão mysql está pronta


96

Estou tentando ter certeza de que meu contêiner de aplicativo não execute migrações / inicie até que o contêiner db seja iniciado e PRONTO PARA aceitar conexões.

Portanto, decidi usar a verificação de integridade e depende da opção no docker compose file v2.

No aplicativo, tenho o seguinte

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

O db, por outro lado, tem a seguinte verificação de integridade

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

Eu tentei algumas abordagens como:

  1. certificando-se de que o db DIR foi criado test: ["CMD", "test -f var/lib/mysql/db"]
  2. Obtendo a versão do mysql: test: ["CMD", "echo 'SELECT version();'| mysql"]
  3. Execute ping no administrador (marca o contêiner db como íntegro, mas não parece ser um teste válido) test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]

Alguém tem uma solução para isso?


Você criou um docker para um banco de dados? Por favor, diga-me que seus dados estão fora deste contêiner para o bem da integridade de seu aplicativo
Jorge Campos

Ou pelo menos este é um contêiner de teste.
Jorge Campos

Na verdade, isso é apenas para fins de desenvolvimento / teste.
John Kariuki

2
Eu acho que você deve usar um comando para conectar e executar uma consulta no mysql, nenhum dos exemplos que você forneceu faz isso: algo como:mysql -u USER -p PASSWORD -h MYSQLSERVERNAME -e 'select * from foo...' database-name
Jorge Campos

1
@JorgeCampos Ok, obrigado. Normalmente, eu tenho um contêiner db, mas mapeio os volumes para o diretório de dados. De forma que, se o contêiner cair, os dados persistirão até a próxima instanciação.
S ..

Respostas:


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

O contêiner api não será iniciado até que o contêiner db esteja saudável (basicamente até que o mysqladmin esteja ativo e aceitando conexões).


12
mysqladmin pingretornará um falso positivo se o servidor estiver em execução, mas ainda não aceitar conexões.
halfpastfour.am

55
Apenas para sua informação para pessoas de 2017: conditionunder depends_onnão é compatível com a versão 3+
Mint

@BobKruithof Estou enfrentando o mesmo problema ... existe alguma solução alternativa, algo como estado de suspensão ou saída para nova tentativa
Mukesh Agarwal

1
@dKen veja minha resposta abaixo stackoverflow.com/a/45058879/279272 , espero que funcione para você também.
Mukesh Agarwal

1
Para verificar isso usando a senha: test: ["CMD", 'mysqladmin', 'ping', '-h', 'localhost', '-u', 'root', '-p$$MYSQL_ROOT_PASSWORD' ]- se você definiu MYSQL_ROOT_PASSWORDna environmentsseção.
laimison de

23

Se você estiver usando docker-compose v3 + , conditionuma opção de depends_onfoi removida .

O caminho recomendado é usar wait-for-it, em vez disso dockerize, ou wait-for. Em seu docker-compose.ymlarquivo, altere o comando para ser:

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

Eu pessoalmente prefiro, wait-forpois pode ser executado em um contêiner Alpine ( shcompatível, sem dependência bash). A desvantagem é que isso depende netcat, então, se você decidir usá-lo, certifique-se de ter netcatinstalado no contêiner ou instale-o em seu Dockerfile, por exemplo com:

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

Eu também fiz um fork dowait-for projeto para que ele possa verificar o status de HTTP saudável (ele usa wget). Então você pode fazer algo assim:

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

PS: Um PR também está pronto para ser mesclado para adicionar essa capacidade de wait-forprojeto.


15

Isso deve ser o 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

2
para que serve o duplo $?
InsOp

5
A sintaxe especial @InsOp que você tem que usar no comando de teste de verificação de saúde para escapar variáveis ​​env começa com $, ou seja, $$ MYSQL_PASSWORD resultará em $ MYSQL_PASSWORD, que por si só resultará em minha senha neste exemplo concreto
Maksim Kostromin

Então, com isso estou acessando a variável env dentro do contêiner? com um único $Im acessando a variável env do host, então eu suponho? isso é bom obrigado!
InsOp

10

Se você pode alterar o container para esperar que o mysql esteja pronto, faça isso.

Se você não tem o controle do contêiner ao qual deseja conectar o banco de dados, pode tentar aguardar a porta específica.

Para isso, estou usando um pequeno script para esperar por uma porta específica exposta por outro container.

Neste exemplo, myserver aguardará a porta 3306 do container mydb ser alcançável.

# 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

Você pode encontrar a documentação do script wait-for-it aqui


Tentei usar wait-for-it.sh anteriormente, mas substitui o Dockerfile padrão, certo? Como é o entrypoint.sh?
John Kariuki,

O ponto de entrada depende da sua imagem. Você pode verificar isso com docker inspect <id da imagem>. Isso deve esperar que o serviço esteja disponível e ligar para o seu ponto de entrada.
exceto em

Tudo bem? Voce entende?
exceto em

Faz sentido. Sim.
John Kariuki

6
Aviso: o MySQL 5.5 (possivelmente versões mais recentes também) pode responder enquanto ainda inicializa.
Blaise

8

Olá, por uma verificação de integridade simples usando docker-compose v2.1 , usei:

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

Basicamente ele executa um mysqlcomando simples SHOW DATABASES;usando como exemplo o usuário rootcom a senharootpasswd no banco de dados.

Se o comando for bem-sucedido, o banco de dados está ativo e pronto para verificar o caminho. Você pode usarinterval o teste no intervalo.

Removendo o outro campo para visibilidade, aqui está como ficaria no seu 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
Aviso: Com a "versão 3" do arquivo de composição, o suporte à "condição" não está mais disponível. Consulte docs.docker.com/compose/compose-file/#depends_on
BartoszK

1
Você deve usar o recurso de comando junto com o script wait-for-it.sh . Eu fazendo assim:command: ["/home/app/jswebservice/wait-for-it.sh", "maria:3306", "--", "node", "webservice.js"]
BartoszK

@BartoszKI não entendo. Você poderia adicionar uma resposta completa com detalhes? Estou enfrentando exatamente o mesmo problema, mas não consigo fazer funcionar.
Thadeu Antonio Ferreira Melo

Certifique-se de usar a v2.1, caso contrário, siga as novas diretrizes para a v3.0 e superior.
Sylhare

1
--execute \"SHOW DATABASES;\"foi o que o fez esperar por mim até que o banco de dados estivesse disponível para o aplicativo acessar
tsuz

6

Modifiquei o docker-compose.ymlconforme o exemplo a seguir e funcionou.

  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

No meu caso, ../schemaAndSeedDatacontém vários arquivos sql schema e data seed. Design your own check scriptpode ser semelhante a seguirselect * from LastSchema.LastDBInsert .

Enquanto o código do contêiner dependente da web era

depends_on:
  mysql:
    condition: service_healthy

Isso pode funcionar para você, mas não tenho certeza se isso é ou não compatível com todos os mecanismos do MySQL.
halfpastfour.am

Estou falando sobre mecanismos de banco de dados como InnoDB, MyISAM etc. É LastSchema.LastDBInsertum padrão do MySQL ou um mecanismo de banco de dados específico?
halfpastfour.am

Não, também não é um padrão no mysql. Foi apenas uma amostra. uma consulta fictícia.
Mukesh Agarwal

6
Aviso: Com a "versão 3" do arquivo de composição, o suporte à "condição" não está mais disponível. Consulte docs.docker.com/compose/compose-file/#depends_on
BartoszK

4

Adicionando uma solução atualizada para a abordagem de verificação de integridade. Snippet simples:

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

Explicação : Como mysqladmin pingretorna falsos positivos (especialmente para senha incorreta), estou salvando a saída em uma variável temporária e usando greppara encontrar a saída esperada ( mysqld is alive). Se encontrado, ele retornará o código de erro 0. Caso não seja encontrado, estou imprimindo a mensagem inteira e retornando o 1 código de erro.

Snippet estendido:

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

Explicação : Estou usando os segredos do docker em vez de variáveis ​​env (mas isso também pode ser obtido com vars env regulares). O uso de $$é para literal$ sinal que é retirado quando passado para o contêiner.

Resultado docker inspect --format "{{json .State.Health }}" db | jqem várias ocasiões:

Tudo certo:

{
  "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ão está ativo (ainda):

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

Senha incorreta:

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

Eu tive o mesmo problema, criei um script bash externo para esse fim (é inspirado na resposta da Maxim). Substitua mysql-container-namepelo nome do seu contêiner MySQL e também é necessária a senha / usuário:

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

Em meu MakeFile, eu chamo este script logo após minha docker-composechamada:

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

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

Então posso chamar outros comandos sem ter o erro:

Ocorreu uma exceção no driver: SQLSTATE [HY000] [2006] O servidor MySQL foi removido

Exemplo de saída:

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.

Excluí a verificação de integridade, pois agora ela é inútil com essa abordagem.


2

REINICIAR COM FALHA

Uma vez que a v3 condition: service_healthynão está mais disponível. A ideia é que o desenvolvedor implemente um mecanismo de recuperação de falhas dentro do próprio aplicativo. No entanto, para casos de uso simples, uma maneira simples de resolver esse problema é usar a restartopção.

Se o status do serviço mysql causar seu aplicativo, exited with code 1você pode usar uma das restartopções de política disponíveis. por exemplo,on-failure

version: "3"

services:

    app:
      ...
      depends_on:
        - db:
      restart: on-failure
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.