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个回答

6
你可以尝试使用以下的docker-compose.yml文件:
version: "3"

services:

  mysql:
    container_name: mysql
    image: mysql:8.0.26
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: test_db
      MYSQL_USER: test_user
      MYSQL_PASSWORD: 1234
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql
    healthcheck:
      test: "mysql $$MYSQL_DATABASE -u$$MYSQL_USER -p$$MYSQL_PASSWORD -e 'SELECT 1;'"
      interval: 20s
      timeout: 10s
      retries: 5

volumes:
  mysql-data:

4

在查看其他解决方案后,mysqladmin ping 对我无效。这是因为 mysqladmin 将返回一个成功的错误代码(即 0),即使 MySQL 服务器已启动但未在端口 3306 上接受连接。对于初始启动,MySQL 服务器将在端口 0 上启动服务器以设置根用户和初始数据库。这就是为什么会出现误报测试。

这是我的健康检查测试:

test: ["CMD-SHELL", "exit | mysql -h localhost -P 3306 -u root -p$$MYSQL_ROOT_PASSWORD" ]

exit | 命令可以在成功连接 MySQL 后关闭输入提示符。

我的完整 Docker Compose 文件:

version: '3.*'

services:
  mysql:
    image: mysql:8
    hostname: mysql
    ports:
      - "3306:3306"
    environment:
      - MYSQL_DATABASE=mydb
      - MYSQL_ALLOW_EMPTY_PASSWORD=1
      - MYSQL_ROOT_PASSWORD=mypass
    healthcheck:
      test: ["CMD-SHELL", "exit | mysql -h localhost -P 3306 -u root -p$$MYSQL_ROOT_PASSWORD" ]
      interval: 5s
      timeout: 20s
      retries: 30
  web:
    build: .
    ports:
      - '8000:8000'
    depends_on:
      mysql:
        condition: service_healthy

3
尽管使用`healthcheck`和`service_healthy`一起是一个不错的解决方案,但我想要一个不依赖于健康检查本身的不同解决方案。
我的解决方案利用了atkrad/wait4x镜像。Wait4X允许您等待端口或服务进入请求状态,并具有可定制的超时和间隔时间。
示例:
services:
  app:
    build: .
    depends_on:
      wait-for-db:
        condition: service_completed_successfully
        
  db:
    image: mysql
    environment:
      - MYSQL_ROOT_PASSWORD=test
      - MYSQL_DATABASE=test

  wait-for-db:
    image: atkrad/wait4x
    depends_on:
      - db
    command: tcp db:3306 -t 30s -i 250ms

说明

示例的docker-compose文件包含以下服务:

  • app - 这是连接到数据库的应用程序,一旦数据库实例准备就绪
    • depends_on 等待 wait-for-db 服务成功完成。 (以0的退出代码退出)
  • db - 这是MySQL服务
  • wait-for-db - 这个服务等待数据库打开它的端口
    • command: tcp db:3306 -t 30s -i 250ms - 使用TCP协议等待3306端口,超时时间为30秒,每250毫秒检查一次端口

1

这里的大部分答案都是对一半正确的。

我使用了mysqladmin ping --silent命令,它大多数情况下都很好用,但即使容器变成了healthy,它也无法处理外部请求。因此,我决定切换到更复杂的命令,并使用容器的外部IP地址来确保健康检查与实际请求相同:

services:
  my-mariadb:
    container_name: my-mariadb
    image: ${DB_IMAGE}
    environment:
      MARIADB_ROOT_PASSWORD: root_password
      MARIADB_USER: user
      MARIADB_PASSWORD: user_password
      MARIADB_DATABASE: db_name
    volumes:
      - ./db/dump.sql:/docker-entrypoint-initdb.d/dump.sql
    ports:
      - 3306:3306
    healthcheck:
      test: mysql -u"$${MARIADB_USER}" -p"$${MARIADB_PASSWORD}" -hmariadb "$${MARIADB_DATABASE}" -e 'SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES LIMIT 1;'
      interval: 20s
      timeout: 5s
      retries: 5
      start_period: 30s
    networks:
      my_network:
        aliases:
          - mariadb

这里的MySQL查询是通过外部主机名(mariadb)运行的。

测试也可能看起来像:

mysql -u"$${MARIADB_USER}" -p"$${MARIADB_PASSWORD}" -h"$$(ip route get 1.2.3.4 | awk '{print $7}' | awk /./)" "$${MARIADB_DATABASE}" -e 'SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES LIMIT 1;'

如果您想使用IP地址而不是主机名
当我使用mysqladmin ping命令时,状态改为healthy的时间约为21秒,切换到新命令后,它升高到41秒。这意味着数据库需要额外的20秒来最终配置并能够处理外部请求。

mysqladmin ping 对我也不起作用。我不得不使用类似于您的方法。这是因为即使MySQL服务器已启动但未在3306端口上接受连接,mysqladmin也会返回成功错误代码。这是我的健康检查测试:test: ["CMD-SHELL", "exit | mysql -h localhost -P 3306 -u root -p$$MYSQL_ROOT_PASSWORD" ] - Ahmad Daudu Sulaiman

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在先前的评论中提供。


我尝试使用wait-for-it脚本来检查依赖服务的host:port,但仍然失败了。似乎当端口准备好连接时,但数据库实例仍在进行中。 - Hantsy

1
这对我有效:

这对我有用:

version: '3'

services:

  john:
    build:
      context: .
      dockerfile: containers/cowboys/john/Dockerfile
      args:
        - SERVICE_NAME_JOHN
        - CONTAINER_PORT_JOHN
    ports:
      - "8081:8081" # Forward the exposed port on the container to port on the host machine
    restart: unless-stopped
    networks:
      - fullstack
    depends_on:
      db:
        condition: service_healthy
    links:
      - db

 db:
    build:
      context: containers/mysql
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_USER: docker_user
      MYSQL_PASSWORD: docker_pass
      MYSQL_DATABASE: cowboys
    container_name: golang_db
    restart: on-failure
    networks:
      - fullstack
    ports:
      - "3306:3306"
    healthcheck:
      test: mysqladmin ping -h 127.0.0.1 -u $$MYSQL_USER --password=$$MYSQL_PASSWORD

networks:
  fullstack:
    driver: bridge

// containers/mysql/Dockerfile

FROM mysql
COPY cowboys.sql /docker-entrypoint-initdb.d/cowboys.sql

1

对我来说没有一个答案有效:

  • Docker版本20.10.6
  • docker-compose版本1.29.2
  • docker-compose yml版本:version: '3.7'
  • mysql5.7
    • 在容器启动时运行脚本:docker-entrypoint-initdb.d

解决方案

检查mysql日志的最后几行是否有包含“我准备好了”之类的某些单词,这可能表明mysql已经成功启动。

这是我的compose文件:

version: '3.7'

services:
  mysql:
    image: mysql:5.7
    command: mysqld --general-log=1 --general-log-file=/var/log/mysql/general-log.log
    container_name: mysql
    ports:
     - "3306:3306"
    volumes:
     - ./install_dump:/docker-entrypoint-initdb.d
    environment:
      MYSQL_ROOT_PASSWORD: changeme
      MYSQL_USER: jane
      MYSQL_PASSWORD: changeme
      MYSQL_DATABASE: blindspot
    healthcheck:
          test: "cat /var/log/mysql/general-log.log | grep \"root@localhost on  using Socket\""
          interval: 1s
          retries: 120

  some_web:
    image: some_web
    container_name: some_web
    ports:
     - "80:80"
    depends_on:
        mysql:
            condition: service_healthy

解释

经过多次检查,我成功获取了容器的完整mysql日志。

docker logs mysql可能已足够,但是我无法在healthcheck中访问docker日志,因此我不得不使用以下命令将mysql的查询日志转储到文件中:

command: mysqld --general-log=1 --general-log-file=/var/log/mysql/general-log.log

之后我多次运行了mysql容器,以确定日志是否相同。我发现最后几行始终相同:

2021-08-30T01:07:06.040848Z    10 Connect   root@localhost on  using Socket
2021-08-30T01:07:06.041239Z    10 Query SELECT @@datadir, @@pid_file
2021-08-30T01:07:06.041671Z    10 Query shutdown
2021-08-30T01:07:06.041705Z    10 Query 
mysqld, Version: 5.7.31-log (MySQL Community Server (GPL)). started with:
Tcp port: 0  Unix socket: /var/run/mysqld/mysqld.sock
Time                 Id Command    Argument

最终,在尝试了一些方法后,这个grep只返回了一个匹配项,对应于在/docker-entrypoint-initdb.d中执行转储后mysql日志的结尾:

cat /var/log/mysql/general-log.log | grep \"root@localhost on  using Socket\"

Words like started with or Tcp port: returned several matches (start, middle and at the end of log) so are not options to detect the end of starting mysql success log. 健康检查 幸运的是,当grep找到至少一个匹配项时,它会返回成功的存在代码(0)。因此,在健康检查中使用它很容易:
healthcheck:
  test: "cat /var/log/mysql/general-log.log | grep \"root@localhost on  using Socket\""
  interval: 1s
  retries: 120

改进

  • 如果有人知道如何在healthcheck内获取docker logs MySQL,则比启用查询日志更好
  • 处理当SQL脚本返回错误时

0

对我来说,两者都很重要:

MySQL镜像版本和环境变量SPRING_DATASOURCE_URL。 如果我删除SPRING_DATASOURCE_URL,它就无法工作。即使我使用MySQL:8.0或更高版本也不行。

version: "3.9"

services:
  api:
    image: api
    build:
      context: ./api
    depends_on:
      - db
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/api?autoReconnect=true&useSSL=false
    networks:
      - private
    ports:
      - 8080:8080
  db:
    image: mysql:5.7
    environment:
      MYSQL_DATABASE: "api"
      MYSQL_ROOT_PASSWORD: "root"
    networks:
      - private
    ports:
      - 3306:3306

networks:
  private:

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