Docker-compose检查mysql连接是否准备就绪

时间:2017-03-02 22:48:01

标签: mysql docker docker-compose dockerfile

我正在尝试确保我的应用容器在db容器启动和READY TO接受连接之前不会运行迁移/启动。

所以我决定使用healthcheck并依赖于docker compose file v2中的选项。

在应用程序中,我有以下

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

另一方面,db具有以下健康检查

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

我尝试了几种方法,如:

  1. 确保已创建db DIR test: ["CMD", "test -f var/lib/mysql/db"]
  2. 获取mysql版本: test: ["CMD", "echo 'SELECT version();'| mysql"]
  3. ping管理员(将数据库容器标记为健康,但似乎不是有效的测试) test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
  4. 有没有人有解决方案?

10 个答案:

答案 0 :(得分:52)

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

在db容器运行正常之前,api容器才会启动(基本上直到mysqladmin启动并接受连接。)

答案 1 :(得分:7)

如果您可以更改容器以等待mysql准备就绪。

如果您没有要将数据库连接到的容器的控件,则可以尝试等待特定端口。

为此,我使用一个小脚本来等待另一个容器暴露的特定端口。

在此示例中, myserver 将等待 mydb 容器的端口 3306 可以访问。

    name    phone   email
0   1       1       hi@hi.com

您可以找到脚本wait-for-it文档here

答案 2 :(得分:4)

如果您使用的是docker-compose v3 + ,则已将condition作为depends_on的选项删除

推荐的路径是使用wait-for-itdockerizewait-for。在您的docker-compose.yml文件中,将命令更改为:

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

我个人更喜欢wait-for,因为它可以在Alpine容器中运行(兼容sh,不依赖于bash)。缺点是它取决于netcat,因此,如果您决定使用它,请确保已在容器中安装了netcat,或将其安装在Dockerfile中,例如:

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

我也forked the wait-for项目,以便它可以检查健康的HTTP状态(它使用wget)。然后您可以执行以下操作:

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

答案 3 :(得分:4)

为运行状况检查方法添加更新的解决方案。 简单的代码段:

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

说明: 由于mysqladmin ping返回假肯定(尤其是密码错误),因此我将输出保存到临时变量中,然后使用grep查找期望的输出(mysqld is alive)。如果找到,它将返回0错误代码。如果找不到它,我将打印整个消息,并返回1错误代码。

扩展片段:

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

说明:我使用的是docker secrets而不是env变量(但这也可以通过常规的env var来实现)。 $$用于文字$,当传递到容器时会被剥离。

docker inspect --format "{{json .State.Health }}" db | jq在各种情况下的输出:

一切正常:

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

数据库尚未启动:

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

密码错误:

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

答案 4 :(得分:4)

我遇到了同样的问题,为此我创建了一个外部bash脚本(受Maxim的启发)。将mysql-container-name替换为您的MySQL容器的名称,并且还需要密码/用户:

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

在我的MakeFile中,我的docker-compose通话结束后立即调用此脚本:

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

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

然后我可以调用其他命令而不会出现错误:

驱动程序中发生异常:SQLSTATE [HY000] [2006] MySQL服务器已消失

输出示例:

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.

我已经删除了健康检查,因为这种方法现在已经无效了。

答案 5 :(得分:3)

我根据以下示例修改了docker-compose.yml并且它有效。

  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

在我的情况下,../schemaAndSeedData包含多个架构和数据种子sql文件。 Design your own check script可以与select * from LastSchema.LastDBInsert类似。

依赖于Web的容器代码

depends_on:
  mysql:
    condition: service_healthy

答案 6 :(得分:3)

您好,我使用 docker-compose v2.1 进行了简单的健康检查,我使用了:

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

基本上,它以作为示例用户mysql在数据库中使用密码SHOW DATABASES;来运行简单的root命令rootpasswd。< / p>

如果命令成功执行,则数据库已启动并准备就绪,因此运行状况检查路径。您可以使用interval,以便每隔一段时间进行测试。

删除其他字段以提高可见性,这就是您的docker-compose.yaml中的样子。

version: '2.1'

  services:
    db:
      ...
      healthcheck:
        test: "/usr/bin/mysql --user=root --password=rootpasswd --execute \"SHOW DATABASES;\""
        interval: 2s
        timeout: 20s
        retries: 10

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

答案 7 :(得分:3)

重新启动失败

由于v3 condition: service_healthy不再可用。 这个想法是,开发人员应该在应用程序内部实现崩溃恢复机制。 但是,对于简单的用例而言,解决此问题的一种简单方法是使用restart选项。

如果mysql服务状态导致您的应用程序运行exited with code 1,则可以使用可用的restart策略选项之一。例如on-failure

version: "3"

services:

    app:
      ...
      depends_on:
        - db:
      restart: on-failure

答案 8 :(得分:2)

这应该足够了

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

答案 9 :(得分:1)

我想为此提供另一种解决方案,在其中一条评论中提到但并未真正解释:
有一个工具叫wait-for-it,在
上提到 https://docs.docker.com/compose/startup-order/
这个怎么运作?您只需指定脚本在准备好时需要定期检查的主机和端口。如果是,它将执行您提供给它的程序。您还可以指定检查 host:port 是否准备就绪的时间。对我来说,这是最有效的最干净的解决方案。
这是我的 docker-compose.yml 文件中的片段。

version : '3'

services:

database: 
    build: DatabaseScripts
    ports:
        - "3306:3306"
    container_name: "database-container"
    restart: always

backend:
    build: backend
    ports: 
        - "3000:3000"
    container_name: back-container
    restart: always
    links:
        - database
    command : ["./wait-for-it.sh", "-t", "40", "database:3306", "--", "node", "app.js"]
    # above line does the following:
        # check periodically for 40 seconds if (host:port) = database:3306 is ready
        # if it is, run 'node app.js'
        # app.js is the file that is connecting with the db

frontend: 
    build: quiz-app
    ports:
        - "4200:4200"
    container_name: front-container
    restart: always

默认等待时间为 20 秒。关于它的更多细节可以在
上找到 https://github.com/vishnubob/wait-for-it

我在 2.X 和 3.X 版本上尝试过 - 它在任何地方都能正常工作。
当然,您需要向容器提供 wait-for-it.sh - 否则它将无法工作。
为此,请使用以下代码:

COPY wait-for-it.sh <DESTINATION PATH HERE>

我在 /backend/Dockerfile 中添加了它,所以它看起来像这样:

FROM node
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY package.json /usr/src/app
COPY wait-for-it.sh /usr/src/app
RUN npm install
COPY . /usr/src/app
EXPOSE 3000
CMD ["npm", "start"]

要检查一切是否正常,请运行 docker-compose logs。在日志中的某个地方一段时间后,您应该会看到类似的输出:

<container_name> | wait-for-it.sh: waiting 40 seconds for database:3306
<container_name> | wait-for-it.sh: database:3306 is available after 12 seconds

注意:此解决方案由 BartoszK 在之前的评论中提供。