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

我试图确保我的应用程序容器不会运行迁移/启动,直到 db 容器启动并且 READY TO 接受连接。

因此,我决定使用 healthcheck,并依赖 docker 撰写文件 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 管理员(将 db 容器标记为健康的,但似乎不是有效的测试) Test: [“ CMD”,“ mysqladmin”,“ ping”,“-h”,“ localhost”]

有人能解决这个问题吗?

138890 次浏览

如果可以更改容器以等待 mysql 准备好,那么可以这样做。

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

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

在这个示例中,我的服务器将等待 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

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

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 容器不会启动

我按照下面的例子修改了 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 文件。

而网络依赖的容器代码

depends_on:
mysql:
condition: service_healthy

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

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

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

如果命令成功,那么 db 已经准备好了,因此 healthcheck 路径。您可以使用 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

在版本3.0到3.8中,condition被删除了组合规范,但现在又回来了!

使用组合规范 V3.9 + (docker-compose v1.29)的版本,您可以在 depends_on的长句法形式中使用 condition作为一个选项。

使用 condition: service_completed_successfully告诉撰写必须在依赖服务启动之前运行服务。

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 Compose2e fixture上都找到了相关资料。不确定 Docker Compose v2是否支持。

这些应该够了

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

为 healthcheck 方法添加更新的解决方案。 简单的片段:

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

说明 : 我使用的是 码头秘密而不是 env 变量(但是这也可以通过常规的 env 变量来实现)。$$用于字面 $符号,该符号在传递到容器时被剥离。

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

我也有同样的问题,为此我创建了一个外部 bash 脚本(它的灵感来自 Maxim answer)。将 mysql-container-name替换为 MySQL 容器的名称,还需要密码/用户:

垃圾桶/ 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... 😴
mysqld is alive
php bin/console doctrine:schema:drop --force
Dropping database schema...
[OK] Database schema dropped successfully!

重启-失败

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

如果 mysql 服务状态导致您的应用程序到 exited with code 1,您可以使用可用的 restart策略选项之一

version: "3"


services:


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

我想为这个问题再提供一个解决方案,其中一条评论提到了这个问题,但没有真正解释清楚:
有一个叫做 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 在前面的评论中提供。

没有一个答案适合我:

  • Docker 版本20.10.6
  • Docker-compose 版本1.29.2
  • Docker-comseyml 版本:’3.7’
  • Mysql 5.7
    • 在容器开始运行脚本: docker-entrypoint-initdb. d

解决方案

检查 mysql 日志最后几行中的一些单词,它们指示我一些类似于“ Imready”的内容。

这是我的作文档案:

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 容器,以确定 log 是否相同。我发现最后几句总是一样的:

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

Tcp 端口:这样的单词返回了几个匹配项(start、 mid 和 at the end of log) ,因此不能检测启动 mysql 成功日志的结束。

健康检查

令人高兴的是,当 grep 至少找到一个匹配项时,它返回一个成功存在代码(0)。因此,在 healthcheck 中使用它很容易:

healthcheck:
test: "cat /var/log/mysql/general-log.log | grep \"root@localhost on  using Socket\""
interval: 1s
retries: 120

改进

  • 如果有人知道如何在 healthcheck 中获取 docker 日志 mysql,那么它将比启用查询日志更好
  • 处理 sql 脚本返回错误的时间。

condition被添加回来,所以现在您可以再次使用它。不需要 wait-for脚本。如果使用 scratch构建映像,则无论如何都不能运行这些脚本。

空气污染指数服务

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

对于 db 块

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.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:

这对我很有效:

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

//Container/mysql/Dockerfile

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

对我来说,两者兼而有之:

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:

尽管将 healthcheckservice_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毫秒检查一次端口