如何在 Docker Compose 中等待 MSSQL?
Posted
技术标签:
【中文标题】如何在 Docker Compose 中等待 MSSQL?【英文标题】:How to wait for MSSQL in Docker Compose? 【发布时间】:2020-06-17 17:11:29 【问题描述】:我有一个依赖于 MSSQL 的 服务(一个 ASP.NET Core Web 应用程序)。这些服务是使用 Docker compose 编排的,我希望 docker compose 首先启动数据库并等待它准备好,然后再运行我的服务。为此,我将docker-compose.yml
定义为:
version: '3.7'
services:
sql.data:
container_name: db_service
image: microsoft/mssql-server-linux:2017-latest
healthcheck:
test: ["CMD", "/opt/mssql-tools/bin/sqlcmd", "-S", "http://localhost:1433", "-U", "sa", "-P", "Pass_word", "-Q", "SELECT 1", "||", "exit 1"]
my_service:
container_name: my_service_container
image: $DOCKER_REGISTRY-my_service
build:
context: .
dockerfile: MyService/Dockerfile
depends_on:
- sql.data
通过此健康检查,Docker compose 不会等待数据库服务准备好,而是在之后立即启动 my_service
,并且正如预期的那样,my_service
无法连接到数据库。部分日志是:
Recreating db_service ... done
Recreating my_service_container ... done
Attaching to db_service, my_service_container
my_service_container | info: ...Context[0]
my_service_container | Migrating database associated with context Context
my_service_container | info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
my_service_container | Entity Framework Core 3.1.1 initialized 'Context' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: MigrationsAssembly=MyService
my_service_container | fail: Context[0]
my_service_container | An error occurred while migrating the database used on context Context
my_service_container | Microsoft.Data.SqlClient.SqlException (0x80131904): A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: TCP Provider, error: 40 - Could not open a connection to SQL Server)
...
exception details
...
my_service_container | ClientConnectionId:00000000-0000-0000-0000-000000000000
my_service_container exited with code 0
db_service | 2020-03-05 05:45:51.82 Server Microsoft SQL Server 2017 (RTM-CU13) (KB4466404) - 14.0.3048.4 (X64)
Nov 30 2018 12:57:58
Copyright (C) 2017 Microsoft Corporation
Developer Edition (64-bit) on Linux (Ubuntu 16.04.5 LTS)
2020-03-05 05:45:51.82 Server UTC adjustment: 0:00
2020-03-05 05:45:51.82 Server (c) Microsoft Corporation.
2020-03-05 05:45:51.82 Server All rights reserved.
2020-03-05 05:45:51.82 Server Server process ID is 4120.
2020-03-05 05:45:51.82 Server Logging SQL Server messages in file '/var/opt/mssql/log/errorlog'.
2020-03-05 05:45:51.82 Server Registry startup parameters:
-d /var/opt/mssql/data/master.mdf
-l /var/opt/mssql/data/mastlog.ldf
-e /var/opt/mssql/log/errorlog
如日志所示,docker compose 首先启动数据库,但在运行我的服务之前不等待它准备好。
我为healthcheck
尝试了不同的语法,例如:
test: /opt/mssql-tools/bin/sqlcmd -S http://localhost:1433 -U sa -P $SA_PASSWORD -Q "SELECT 1" || exit 1
但两者都没有按预期工作。
我在网上查了以下资源,但都无法解决问题:
*** answer github comment github sampleversion 3.7
甚至支持此功能吗? because of this confusing comment
问题
对于等待 MSSQL 服务启动的最佳方式有什么想法吗?
【问题讨论】:
【参考方案1】:您也可以延迟 docker 启动,直到 mssql 启动:
docker-compose.yaml
mssql:
image: mcr.microsoft.com/mssql/server:2017-latest
ports:
- 1433:1433
environment:
SA_PASSWORD: "t9D4:EHfU6Xgccs-"
ACCEPT_EULA: "Y"
networks:
- backend
command:
- /bin/bash
- -c
- |
/opt/mssql/bin/sqlservr
curl -s https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh | bash /dev/stdin localhost:1433
【讨论】:
【参考方案2】:使用 Kubernetes Deployment,以下探针成功识别就绪状态:
containers:
- name: mssql
image: mcr.microsoft.com/mssql/server:2019-latest
ports:
- containerPort: 1433
env:
- name: ACCEPT_EULA
value: 'Y'
startupProbe:
exec:
command:
- /bin/sh
- '-c'
- '/opt/mssql-tools/bin/sqlcmd -U sa -P "$SA_PASSWORD" -Q "SELECT \"READY\"" | grep -q "READY"'
failureThreshold: 15
periodSeconds: 10
如果您的 Kubernetes 版本为
说明:
无论查询是否返回任何内容,sqlcmd 都会返回“0”状态。但是,grep -q
根据是否存在“READY”一词返回 0 或 1。
我没有使用过 docker-compose,但我怀疑这个命令可以作为健康检查测试(假设 SA_PASSWORD 被注入到环境中),即:
healthcheck:
test:
- /bin/sh
- '-c'
- '/opt/mssql-tools/bin/sqlcmd -U sa -P "$SA_PASSWORD" -Q "SELECT \"READY\"" | grep -q "READY"'
Configure Liveness, Readiness and Startup Probes
Pod Lifecycle
【讨论】:
【参考方案3】:这是一个完整的example
version: "3.8"
services:
ms-db-server:
image: mcr.microsoft.com/mssql/server
environment:
- SA_PASSWORD=P@ssw0rd
- ACCEPT_EULA=Y
volumes:
- ./data/db/mssql/scripts:/scripts/
ports:
- "1433:1433"
#entrypoint: /bin/bash
command:
- /bin/bash
- -c
- |
/opt/mssql/bin/sqlservr &
pid=$$!
echo "Waiting for MS SQL to be available ⏳"
/opt/mssql-tools/bin/sqlcmd -l 30 -S localhost -h-1 -V1 -U sa -P $$SA_PASSWORD -Q "SET NOCOUNT ON SELECT \"YAY WE ARE UP\" , @@servername"
is_up=$$?
while [ $$is_up -ne 0 ] ; do
echo -e $$(date)
/opt/mssql-tools/bin/sqlcmd -l 30 -S localhost -h-1 -V1 -U sa -P $$SA_PASSWORD -Q "SET NOCOUNT ON SELECT \"YAY WE ARE UP\" , @@servername"
is_up=$$?
sleep 5
done
for foo in /scripts/*.sql
do /opt/mssql-tools/bin/sqlcmd -U sa -P $$SA_PASSWORD -l 30 -e -i $$foo
done
echo "All scripts have been executed. Waiting for MS SQL(pid $$pid) to terminate."
wait $$pid
tempo:
image: grafana/tempo:latest
command: ["-config.file=/etc/tempo.yaml"]
volumes:
- ./etc/tempo-local.yaml:/etc/tempo.yaml
- ./data/tempo-data:/tmp/tempo
ports:
- "14268" # jaeger ingest, Jaeger - Thrift HTTP
- "14250" # Jaeger - GRPC
- "55680" # OpenTelemetry
- "3100" # tempo
- "6831/udp" # Jaeger - Thrift Compact
- "6832/udp" # Jaeger - Thrift Binary
tempo-query:
image: grafana/tempo-query:latest
command: ["--grpc-storage-plugin.configuration-file=/etc/tempo-query.yaml"]
volumes:
- ./etc/tempo-query.yaml:/etc/tempo-query.yaml
ports:
- "16686:16686" # jaeger-ui
depends_on:
- tempo
loki:
image: grafana/loki:2.1.0
command: -config.file=/etc/loki/loki-local.yaml
ports:
- "3101:3100" # loki needs to be exposed so it receives logs
environment:
- JAEGER_AGENT_HOST=tempo
- JAEGER_ENDPOINT=http://tempo:14268/api/traces # send traces to Tempo
- JAEGER_SAMPLER_TYPE=const
- JAEGER_SAMPLER_PARAM=1
volumes:
- ./etc/loki-local.yaml:/etc/loki/loki-local.yaml
- ./data/loki-data:/tmp/loki
nodejs-otel-tempo-api:
build: .
command: './wait-for.sh ms-db-server:1433 -- node ./dist/server.js'
ports:
- "5555:5555"
environment:
- OTEL_EXPORTER_JAEGER_ENDPOINT=http://tempo:14268/api/traces
- OTEL_SERVICE_NAME=nodejs-opentelemetry-tempo
- LOG_FILE_NAME=/app/logs/nodejs-opentelemetry-tempo.log
- DB_USER=sa
- DB_PASS=P@ssw0rd
- DB_SERVER=ms-db-server
- DB_NAME=OtelTempo
volumes:
- ./data/logs:/app/logs
- ./etc/wait-for.sh:/app/bin/wait-for.sh #https://github.com/eficode/wait-for
depends_on:
- ms-db-server
- tempo-query
promtail:
image: grafana/promtail:master-ee9c629
command: -config.file=/etc/promtail/promtail-local.yaml
volumes:
- ./etc/promtail-local.yaml:/etc/promtail/promtail-local.yaml
- ./data/logs:/app/logs
depends_on:
- nodejs-otel-tempo-api
- loki
prometheus:
image: prom/prometheus:latest
volumes:
- ./etc/prometheus.yaml:/etc/prometheus.yaml
entrypoint:
- /bin/prometheus
- --config.file=/etc/prometheus.yaml
ports:
- "9090:9090"
depends_on:
- nodejs-otel-tempo-api
grafana:
image: grafana/grafana:7.4.0-ubuntu
volumes:
- ./data/grafana-data/datasources:/etc/grafana/provisioning/datasources
- ./data/grafana-data/dashboards-provisioning:/etc/grafana/provisioning/dashboards
- ./data/grafana-data/dashboards:/var/lib/grafana/dashboards
environment:
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
- GF_AUTH_DISABLE_LOGIN_FORM=true
ports:
- "3000:3000"
depends_on:
- prometheus
- tempo-query
- loki
【讨论】:
【参考方案4】:在搜索并尝试了许多不同的场景后,我能够使用以下作曲家文件添加等待。这是针对asp.net
核心解决方案的。关键是如果dockerfile
中指定了entrypoint
,则必须覆盖它。另外,你需要确保将 "wait-for-it.sh" LF 保存为行尾而不是 CRLF,否则你会得到file not found
的错误。
dockerfile
应该有以下内容(从这里下载:https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh,确保保存文件):
COPY ./wait-for-it.sh /wait-for-it.sh
RUN chmod +x wait-for-it.sh
docker-compose.yml
version: '3.7'
services:
vc-db:
image: mcr.microsoft.com/mssql/server:latest
ports:
- "$DOCKER_SQL_PORT:-1433:1433"
expose:
- 1433
environment:
- ACCEPT_EULA=Y
- MSSQL_PID=Express
- SA_PASSWORD=v!rto_Labs!
networks:
- virto
vc-platform-web:
image: virtocommerce/platform:$DOCKER_TAG:-latest
ports:
- "$DOCKER_PLATFORM_PORT:-8090:80"
environment:
- ASPNETCORE_URLS=http://+
depends_on:
- vc-db
entrypoint: ["/wait-for-it.sh", "vc-db:1433", "-t", "120", "--", "dotnet", "VirtoCommerce.Platform.Web.dll"]
networks:
- virto
【讨论】:
如何连接内部mssql DB丢docker?当 docker 将运行时,我在一个带有主机的网络中拥有带有 MSSQL DB 的服务器。在我的简单烧瓶应用程序中,我使用 pyodbc 连接到数据库。在其他应用程序上,我使用带有 docker 的 sqlite db 文件,连接起来很简单。 我觉得这种“等待”和类似的解决方案是半解决方案:如果容器/sql-container 由于错误或任何原因而无法启动怎么办。我们要永远等待吗?或者如果我们使用超时,我们如何确定这个超时在给定的特定环境中是足够的? @EugeneGorbovoy 我认为等待相关服务可用是合理的,此时抛出错误是调度程序的工作。超时是一种技巧,因为我们真的不知道服务启动需要多长时间。【参考方案5】:创建两个单独的 dockerfile(例如):
-
Mssql.Dockerfile
App.Dockerfile
在 docker-compose.yml 中设置序列
Mssql.Dockerfile
FROM mcr.microsoft.com/mssql/server AS base
ENV ACCEPT_EULA=Y
ENV SA_PASSWORD=Password123
COPY . .
COPY ["Db/Scripts/*", "Db/Scripts/"]
VOLUME ./Db:/var/opt/mssql/data
HEALTHCHECK --interval=10s --timeout=5s --start-period=10s --retries=10 \
CMD /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Password123 -i Db/Scripts/SetupDb.sql || exit 1
App.Dockerfile:
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
COPY ["AspNetCoreWebApplication/AspNetCoreWebApplication.csproj", "AspNetCoreWebApplication/"]
COPY ["WebApp.Data.EF/WebApp.Data.EF.csproj", "WebApp.Data.EF/"]
COPY ["WebApp.Service/WebApp.Service.csproj", "WebApp.Service/"]
RUN dotnet restore "AspNetCoreWebApplication/AspNetCoreWebApplication.csproj"
COPY . .
WORKDIR "/src/AspNetCoreWebApplication"
RUN dotnet build "AspNetCoreWebApplication.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "AspNetCoreWebApplication.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "AspNetCoreWebApplication.dll"]
Docker-compose.yml:
version: '3.7'
services:
api:
image: aspnetcore/mentoring_api
container_name: mentoring_api
build:
context: .
dockerfile: App.Dockerfile
ports:
- 8081:80
expose:
- 8081
environment:
ASPNETCORE_ENVIRONMENT: Development
depends_on:
- sqlserver
sqlserver:
image: aspnetcore/mentoring_db
container_name: mentoring_db
build:
context: .
dockerfile: Mssql.Dockerfile
ports:
- "1433:1433"
expose:
- 1433
environment:
- ACCEPT_EULA=Y
- SA_PASSWORD=Password123
volumes:
- ./Db:/var/opt/mssql/data
注意:
连接字符串将如下所示:"Server=sqlserver;Database=Northwind;Trusted_Connection=False;User Id=sa;Password=Password123;MultipleActiveResultSets=true"
【讨论】:
您设置了 ENV SA_PASSWORD 但随后在 HEALTHCHECK 中写出密码 嘿@caduceus,为什么这是个问题? @ShaharShokrani 如果您不打算使用变量,为什么还要设置它? 在您之前的评论中,您是否打算在ENV
或 CMD
中不需要密码?
如果没有通过健康检查的CMD
提供的密码,不确定健康检查是否能正常工作,也许我错了,但我认为两者都是必要的,值得检查。【参考方案6】:
当您使用depends_on
时,docker-compose 只会以更高的优先级启动您的基础服务,而不会等待启动服务。
有一些有用的外部程序可以帮助您等待特定的服务(端口),然后运行另一个服务。
vishnubob/wait-for-it 是其中之一,它会阻止执行流程,直到您的特定端口准备好。 另一个不错的选择是eficode/wait-for,它已经为 docker-compose 做好了准备。
使用示例(根据 eficode/wait-for docs)
version: '2'
services:
db:
image: postgres:9.4
backend:
build: backend
# Blocks execution flow util db:5432 is ready (Or you can use localhost instead)
command: sh -c './wait-for db:5432 -- npm start'
depends_on:
- db
-- 更新--
假设您有一个依赖于 PostgreSQL 等数据库的 Python 应用程序,并且您的应用程序将使用以下命令运行:python app.py
正如Official Docker Document 所说,将vishnubob/wait-for-it
放入您的图像中(在您的其他项目文件中,如app.py
)
现在只需将此行放入您的docker-compose.yml
:
version: "3"
services:
web:
build: .
ports:
- "80:8000"
depends_on:
- "db"
# This command waits until `db:5432` respond (5432 is default PostgreSQL port)
# then runs our application by this command: `python app.py`
command: ["./wait-for-it.sh", "db:5432", "--", "python", "app.py"]
db:
image: postgres
注意:不要忘记将此命令放在您的图像文件中的 Dockerfile
中:
# Copy wait-for-it.sh into our image
COPY wait-for-it.sh wait-for-it.sh
# Make it executable, in Linux
RUN chmod +x wait-for-it.sh
【讨论】:
能否提供一个完整的功能示例?例如,./wait-for
命令应该安装在db
图像上?
我在镜像文件夹的根目录下添加了 wait-for-it.sh 文件,在构建时,Docker 20.10.2 说:chmod: wait-for-it.sh: Operation not allowed在 Windows 10 19.09 上。有什么想法吗?
@JoanComasFdz 是你的容器镜像 Windows,chmod 是一个 linux 命令
@AjayBhasy 我在 Windows 10 1909 和 Docker Desktop 上使用 linux 容器
@JoanComasFdz 我将脚本文件添加到我的项目中,导致它被复制到发布文件夹,然后在入口点之前的 Dockerfile 的发布部分中,我运行了这个命令并且它工作了【参考方案7】:
您可以编写一个简单的脚本,该脚本将在您的应用程序的容器中启动。例如,您可以使用sleep N
设置延迟(其中 N 是启动数据库所需的时间),或者您可以使用until
循环,在该循环中您可以尝试连接到您的数据库,并且在可能的情况下,您可以启动您的应用程序。
我知道这不是一个完美的解决方案,但是当我遇到类似问题时它帮助了我
【讨论】:
检查应用程序内部的连接是个好主意,但 docker 文档本身建议使用wait-for-it
和替代脚本。
当然,我不反对这个,我只是提供了一个简单(不完美)的解决方案以上是关于如何在 Docker Compose 中等待 MSSQL?的主要内容,如果未能解决你的问题,请参考以下文章
在创建其他服务之前,等待在docker compose中准备好mysql服务
docker-compose 等待启动,直到挂载主机文件系统
sh 等待应用程序从https://docs.docker.com/compose/startup-order/完全在docker容器中运行