Docker-compose 检查 MySQL 连接是否就绪

190

我希望确保我的应用程序容器在数据库容器已经启动并准备好接受连接之后才运行迁移/开始。

因此,我决定在Docker Compose文件v2中使用健康检查和依赖项选项。

在应用程序中,我有以下代码:

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

另一方面,数据库具有以下健康检查:

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管理员(标记db容器为健康状态,但似乎不是有效的测试) test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]

有人有解决办法吗?


你为数据库创建了一个Docker容器吗?请告诉我你的数据不在这个容器里,这样有利于你的应用程序健康。 - Jorge Campos
这仅用于开发/测试目的。 - John Kariuki
太好了,听到这个消息很棒 :) - Jorge Campos
5
我认为你应该使用一个命令连接并在mysql中运行查询,你提供的示例都没有这样做:类似这样:mysql -u USER -p PASSWORD -h MYSQLSERVERNAME -e 'select * from foo...' database-name - Jorge Campos
2
@JorgeCampos 好的,谢谢。通常我会有一个数据库容器,但是映射数据目录的卷。这样,如果容器崩溃,数据将保留到下一次实例化。 - S..
显示剩余5条评论
18个回答

189
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容器才会开始启动。


36
如果服务器正在运行但尚未接受连接,则 mysqladmin ping 命令会返回一个错误的结果。请注意,这里的“错误的结果”指的是“假阳性”,即看起来好像一切正常,但实际上服务器并没有准备好接受连接。 - halfpastfour.am
1
@dKen请看下面的答案 https://dev59.com/zlgQ5IYBdhLWcg3wkEtJ#45058879,希望对您也有用。 - Mukesh Agarwal
23
如果您在“environments”部分中定义了“MYSQL_ROOT_PASSWORD”,则可以使用密码检查此项:test: ["CMD", 'mysqladmin', 'ping', '-h', 'localhost', '-u', 'root', '-p$$MYSQL_ROOT_PASSWORD' ] - laimison
2
请注意,使用分离的Compose规范后,“depends_on”中再次添加了“condition”:https://github.com/compose-spec/compose-spec/blob/a4a7e7c/spec.md#long-syntax-1 您需要使用Compose 1.27.0或更高版本才能实现此功能:https://github.com/docker/compose/releases/tag/1.27.0 - Mathias Brodala
12
我正在使用版本为3.9的Compose文件,其中condition字段有效。 - Sam Jones
显示剩余10条评论

49

condition 在版本3.0到3.8中已从compose规范中移除,但现在又回来了!

使用compose规范的版本v3.9+(docker-compose v1.29),您可以在依赖关系的长语法形式中将condition用作选项。

使用condition: service_completed_successfully告诉compose,在启动依赖服务之前,必须先运行该服务。

services:
  web:
    build: .
    depends_on:
      db:
        condition: service_completed_successfully
      redis:
        condition: service_completed_successfully
  redis:
    image: redis
  db:
    image: postgres

condition选项可以是:

  • service_started等效于短语法形式
  • service_healthy正在等待服务变得健康。使用healthcheck选项定义健康状态。
  • service_completed_successfully指定依赖项在启动依赖服务之前必须成功完成运行(已添加到docker-compose中,使用PR#8122)。

可惜这个功能的文档非常不好。我在Docker论坛Docker文档问题Docker compose问题Docker Compose e2e fixtures中找到了对它的引用。不确定它是否受到Docker Compose v2的支持。


4
我正在使用3.9版本,虽然文档上说条件不适用,但实际上看起来它起作用了。 - Sam Jones
“host:port” 检查对于大多数应用程序似乎不够,我发现当数据库端口准备就绪(它通过了 wait-for 脚本),但仍然失败。从 “docker-compose logs” 中,我发现数据库实例无法连接。我添加了一个硬编码的 “sleep 10” 来等待数据库,它起作用了。我认为必须在数据库上执行 ping 查询字符串以确保其可用。 - Hantsy
2
condition 已经被添加回去。 - leogoesger
2
对我来说不起作用,我是指服务已成功完成,但主应用程序没有启动。有什么建议吗? - PanZWarzywniaka
15
我认为这永远不会起作用,或者从未起过作用。“service_completed_successfully”意味着该应用程序将等待指定的服务以0代码退出。根据文档: “service_completed_successfully:指定在启动依赖服务之前,需要将依赖项成功运行完成。” - Dávid Szabó
显示剩余3条评论

39

这应该足够了

version: '3.4'
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
      start_period: 5s
      interval: 5s
      timeout: 5s
      retries: 55

5
双重美元符号 $ 是用来表示变量的。 - InsOp
13
@InsOp 在健康检查测试命令中,您需要使用特殊语法来转义以$开头的环境变量,例如$$MYSQL_PASSWORD将会被转换为$MYSQL_PASSWORD,而在这个具体的例子中,它本身将会被转换为mypassword。 - Maksim Kostromin
1
所以通过这个方法,我可以在容器中访问环境变量吗?使用单个 $ 我能够从主机中访问环境变量,是这样吗?太好了,谢谢! - InsOp
为什么要重试55次?看起来很随意。 - mritalian
@mritalian 使用你需要的值,那只是我项目中的一个例子。 - Maksim Kostromin
显示剩余4条评论

25

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

Hi for a simple healthcheck using docker-compose v2.1, I used:

/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

4
警告:在“版本3”的compose文件中,“condition”支持已不再可用。请参见https://docs.docker.com/compose/compose-file/#depends_on。 - BartoszK
1
你应该将 command 特性与 wait-for-it.sh 脚本一起使用。我是这样做的:command: ["/home/app/jswebservice/wait-for-it.sh", "maria:3306", "--", "node", "webservice.js"] - BartoszK
@BartoszKI,我不理解。你能否提供一个详细的完整答案?我也遇到了完全相同的问题,但是我无法解决它。 - Thadeu Melo
请确保您正在使用v2.1版本,否则请遵循v3.0及以上版本的新指南。 - Sylhare
2
“--execute \”SHOW DATABASES;\”” 是让它等待数据库可供应用程序访问的原因。 - Taku
1
docker-compose v1.27.0版本以来,“condition”似乎又可以正常工作了。这个健康检查对我使用的mysql 8.0可行,命令为--execute="SHOW DATABASES;" - Yarrow

13

为健康检查方法添加更新的解决方案。 简单代码片段:

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而不是环境变量(但是这也可以通过常规环境变量实现)。使用$$是为了保留传递到容器中的文本中的美元符号$

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

非常感谢,这救了我。 - Gideao

12

如果您可以改变容器以等待mysql准备就绪,请这样做。

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

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

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

# 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

你可以在这里找到脚本的等待文档 here


我之前尝试使用 wait-for-it.sh,但它会覆盖默认的 Dockerfile,对吗?entrypoint.sh 是什么样子的? - John Kariuki
可以吗?你明白吗? - nono
有道理。是的。 - John Kariuki
这很有帮助,但我选择使用默认的v2.1健康检查。 - John Kariuki
8
注意:MySQL 5.5(可能包括较新的版本)在初始化过程中仍可能会响应。 - Blaise
显示剩余3条评论

9

出现故障时重新启动

自版本3起,condition: service_healthy选项已不再可用。 这意味着开发者应该在应用程序内部实现崩溃恢复机制。 然而,对于简单的用例,解决此问题的简单方法是使用restart选项。

如果MySQL服务状态导致您的应用程序退出代码为1,则可以使用可用的restart策略选项之一。例如:on-failure

version: "3"

services:

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

8

我曾遇到同样的问题,我为此创建了一个外部的 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 up调用之后立即调用此脚本:
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... 
mysqld is alive
php bin/console doctrine:schema:drop --force
Dropping database schema...
[OK] Database schema dropped successfully!

7

按照以下示例修改了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 文件。您可以设计自己的检查脚本,类似于以下代码:select * from LastSchema.LastDBInsert
当 Web 依赖容器代码时,它是这样的。
depends_on:
  mysql:
    condition: service_healthy

这可能适用于您,但我不确定是否在所有MySQL引擎中都支持。 - halfpastfour.am
我正在谈论像InnoDB、MyISAM等数据库引擎。 LastSchema.LastDBInsert是MySQL默认值还是特定于数据库引擎的? - halfpastfour.am
不,这也不是MySQL的默认设置。这只是一个示例,一个虚拟查询。 - Mukesh Agarwal
6
警告:使用“版本3”的Compose文件时,“condition”支持已不再可用。请参见https://docs.docker.com/compose/compose-file/#depends_on。 - BartoszK
1
@BartoszK docker-compose 版本 3.9 支持 "condition"。 - Anh Nhat Tran

7

condition已经恢复,现在您可以再次使用它。不需要wait-for脚本。如果您正在使用scratch构建镜像,则无法运行这些脚本。

针对API服务

api:
    build:
      context: .
      dockerfile: Dockerfile
    restart: always
    depends_on:
      content-db:
        condition: service_healthy
    ...

针对数据库块

content-db:
    image: mysql:5.6
    restart: on-failure
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - "./internal/db/content/sql:/docker-entrypoint-initdb.d"
    environment:
      MYSQL_DATABASE: content
      MYSQL_TCP_PORT: 5306
      MYSQL_ROOT_PASSWORD: $MYSQL_ROOT_PASSWORD
    healthcheck:
      test: "mysql -uroot -p$MYSQL_ROOT_PASSWORD content -e 'select 1'"
      interval: 1s
      retries: 120

这个解决方案对我在Docker Compose V3中起到了作用。我使用了以下语法:test: "mariadb --host=database --user=${MARIADB_USER} --password=${MARIADB_PASSWORD} -e 'SELECT 1;'" - edwinbradford

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接