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


91

我试图确保我的应用容器在数据库容器启动并且准备接受连接之前不运行迁移/启动。

所以我决定使用运行状况检查,并取决于docker compose文件v2中的选项。

在应用程序中,我有以下内容

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

另一方面,数据库具有以下运行状况检查

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

我尝试了几种方法,例如:

  1. 确保已创建数据库DIR test: ["CMD", "test -f var/lib/mysql/db"]
  2. 获取mysql版本: test: ["CMD", "echo 'SELECT version();'| mysql"]
  3. 对管理员执行ping操作(将db容器标记为运行状况良好,但似乎不是有效的测试) test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]

有人对此有解决方案吗?


您为数据库创建了一个泊坞窗?请告诉我,为了您的应用程序健康,您的数据不在此容器中
Jorge Campos

或者至少这是一个测试容器。
Jorge Campos'3

实际上,这仅用于开发/测试目的。
约翰·卡里基

2
我认为您应该使用命令连接并在mysql中运行查询,您提供的所有示例都没有做到这一点:类似:mysql -u USER -p PASSWORD -h MYSQLSERVERNAME -e 'select * from foo...' database-name
Jorge Campos

1
@JorgeCampos好的,谢谢。通常我有一个db容器,但是为数据目录映射了卷。这样,如果容器发生故障,数据将保留到下一个实例中。
S ..

Answers:


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

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


12
mysqladmin ping如果服务器正在运行但尚未接受连接,则将返回false肯定。
halfpastfour.am,2017年

53
仅供2017年用户参考:版本3+不支持conditionunderdepends_on
Mint

@BobKruithof我正面临着同样的问题……是否有任何解决方法,例如睡眠或退出状态以重试
Mukesh Agarwal

1
@dKen在stackoverflow.com/a/45058879/279272下查看我的答案,希望它也对您有用。
Mukesh Agarwal

1
要使用密码进行检查:test: ["CMD", 'mysqladmin', 'ping', '-h', 'localhost', '-u', 'root', '-p$$MYSQL_ROOT_PASSWORD' ]-如果您MYSQL_ROOT_PASSWORDenvironments部分中定义。
laimison

22

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

推荐的途径是使用相当wait-for-itdockerize或者wait-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

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

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

PS:PR也准备好合并以增加该功能到wait-for项目中。


13

这样就足够了

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
双重$用途是什么?
InsOp

5
您必须在运行状况检查测试命令中使用@InsOp特殊语法来转义env变量,以$开头,即$ MYSQL_PASSWORD将导致$ MYSQL_PASSWORD,在此具体示例中,其本身将导致mypassword
Maksim Kostromin 19/12/14

因此,通过此im访问容器内的env变量?一个单一的$即时消息访问主机的环境变量,然后我想?很好,谢谢!
InsOp

10

如果可以更改容器以等待mysql准备就绪,请执行此操作。

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

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

在这个例子中,MYSERVER将等待端口3306mydb的容器可到达。

# 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

您可以在此处找到脚本等待文档


我尝试使用wait-for-it.sh 更早的版本,但是它会覆盖默认的Dockerfile,对吗?entrypoint.sh的外观如何?
约翰·卡里基

入口点取决于您的图像。您可以使用docker inspect <image id>进行检查。这应该等待该服务可用并致电您的入口点。
诺诺

可以吗?你理解吗?
2017年

有道理。是的
约翰·卡里基

6
警告:MySQL 5.5(可能还有较新的版本)仍可以在初始化时作出响应。
布莱斯

8

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

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

基本上,它运行的简单mysql命令SHOW DATABASES;使用,例如,用户root的密码rootpasswd 在数据库中。

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

删除其他字段以提高可见性,这就是您的中的样子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
警告:在撰写文件的“版本3”中,“条件”支持不再可用。请参阅docs.docker.com/compose/compose-file/#depends_on
BartoszK

1
您应该将命令功能与wait-for-it.sh脚本一起使用。我这样做:command: ["/home/app/jswebservice/wait-for-it.sh", "maria:3306", "--", "node", "webservice.js"]
BartoszK,

@BartoszKI不明白。您能否附上完整的详细答案?我正面临着完全相同的问题,但我无法解决这个问题。
Thadeu Antonio Ferreira Melo,

确保您使用的是v2.1,否则请遵循v3.0及更高版本的新准则。
Sylhare'1

1
--execute \"SHOW DATABASES;\"是什么让我等到数据库可供应用程序访问
tsuz

6

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

虽然网络依赖的容器代码是

depends_on:
  mysql:
    condition: service_healthy

这可能对您有用,但是我不确定所有MySQL引擎是否都支持此功能。
halfpastfour.am

我说的是数据库引擎,例如InnoDB,MyISAM等LastSchema.LastDBInsert。MySQL默认还是数据库引擎特定?
halfpastfour.am

不,它也不是mysql中的默认设置。这只是一个示例。虚拟查询。
Mukesh Agarwal'17年

5
警告:在撰写文件的“版本3”中,“条件”支持不再可用。请参阅docs.docker.com/compose/compose-file/#depends_on
BartoszK

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-composeup调用之后立即调用此脚本:

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.

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


3

重新启动失败

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

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

version: "3"

services:

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

2

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

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 vars实现)。使用的$$是文字$符号,当传递到容器时会被剥离。

来自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"
    }
  ]
}

DB尚未启动(尚未):

{
  "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"
    }
  ]
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.