如何在 Docker Compose 中等待 MSSQL?

Ham*_*med 20 c# sql-server docker docker-compose asp.net-core

我有一个依赖于 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
Run Code Online (Sandbox Code Playgroud)

通过这种健康检查,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
Run Code Online (Sandbox Code Playgroud)

如日志中所示,docker compose 首先启动数据库,但在运行我的服务之前不会等待它准备就绪。

我为 尝试了不同的语法healthcheck,例如:

test: /opt/mssql-tools/bin/sqlcmd -S http://localhost:1433 -U sa -P ${SA_PASSWORD} -Q "SELECT 1" || exit 1
Run Code Online (Sandbox Code Playgroud)

但两者都没有按预期工作。

我已经在线检查了以下来源,但使用两者都无法解决问题:

甚至支持此功能version 3.7吗?因为这个令人困惑的评论


关于如何最好地等待 MSSQL 服务启动的任何想法?

小智 9

当您使用 时depends_on,docker-compose 只会以更高的优先级启动您的基本服务,而不会等待启动服务。

有一些有用的外部程序可以帮助您等待特定服务(端口),然后运行另一个服务。

vishnubob/wait-for-it是其中之一,它会阻止执行流,直到您的特定端口准备就绪。
另一个不错的选择是eficode/wait-for,它已经为docker -compose 做好了准备。

示例用法(根据 eficode/wait-for 文档)

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
Run Code Online (Sandbox Code Playgroud)

- 更新 -

假设您有一个依赖于 PostgreSQL 等数据库的 Python 应用程序,并且您的应用程序将使用以下命令运行:python app.py
正如Docker 官方文档所说,放入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
Run Code Online (Sandbox Code Playgroud)

注意:不要忘记将此命令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
Run Code Online (Sandbox Code Playgroud)


Wol*_*and 9

在搜索并尝试了许多不同的场景后,我能够使用以下 Composer 文件添加等待。这是asp.net核心解决方案。关键是entrypoint如果在dockerfile. 此外,您需要确保将“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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)


Ric*_*ard 9

我认为你最初的尝试实际上并不遥远。使用运行状况检查似乎是最合适的途径,因此我将继续使用该方法,但是您将希望condition利用depends_on. 这样,您可以使用service_healthy条件,该条件将等待您的 SQL Server 运行状况检查报告运行状况良好。

请参阅 Docker 网站上的这篇文章,其中提到了这一点: https ://docs.docker.com/compose/startup-order/

你的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:
        condition: service_healthy
Run Code Online (Sandbox Code Playgroud)

您还可以利用healthcheck如下所示的选项和稍微简洁的语法:

version: '3.7'

services:

  sql.data:
    container_name: db_service
    image: microsoft/mssql-server-linux:2017-latest
    healthcheck:
      test: /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P "Pass_word" -Q "SELECT 1" -b -o /dev/null
      interval: 10s
      timeout: 3s
      retries: 10
      start_period: 10s

  my_service:
    container_name: my_service_container
    image: ${DOCKER_REGISTRY-}my_service
    build:
      context: .
      dockerfile: MyService/Dockerfile
    depends_on:
      sql.data:
        condition: service_healthy
Run Code Online (Sandbox Code Playgroud)


All*_*ool 6

创建两个单独的 dockerfile(例如):

  1. Mssql.Dockerfile
  2. 应用程序.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
Run Code Online (Sandbox Code Playgroud)

应用程序.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"]
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

注意: 连接字符串将如下所示:"Server=sqlserver;Database=Northwind;Trusted_Connection=False;User Id=sa;Password=Password123;MultipleActiveResultSets=true"


cra*_*eem 5

这是一个完整的例子

\n
version: "3.8"\n\nservices:\n  ms-db-server:\n    image: mcr.microsoft.com/mssql/server\n    environment: \n      - SA_PASSWORD=P@ssw0rd\n      - ACCEPT_EULA=Y\n    volumes:\n      - ./data/db/mssql/scripts:/scripts/\n    ports:\n      - "1433:1433"\n    #entrypoint: /bin/bash\n    command:\n      - /bin/bash\n      - -c\n      - |\n        /opt/mssql/bin/sqlservr &\n        pid=$$!\n\n        echo "Waiting for MS SQL to be available \xe2\x8f\xb3"\n        /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"\n        is_up=$$?\n        while [ $$is_up -ne 0 ] ; do\n          echo -e $$(date)\n          /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"\n          is_up=$$?\n          sleep 5\n        done\n\n        for foo in /scripts/*.sql\n          do /opt/mssql-tools/bin/sqlcmd -U sa -P $$SA_PASSWORD -l 30 -e -i $$foo\n        done\n        echo "All scripts have been executed. Waiting for MS SQL(pid $$pid) to terminate."\n\n        wait $$pid\n\n  tempo:\n    image: grafana/tempo:latest\n    command: ["-config.file=/etc/tempo.yaml"]\n    volumes:\n      - ./etc/tempo-local.yaml:/etc/tempo.yaml\n      - ./data/tempo-data:/tmp/tempo\n    ports:\n      - "14268"      # jaeger ingest, Jaeger - Thrift HTTP\n      - "14250"      # Jaeger - GRPC\n      - "55680"      # OpenTelemetry\n      - "3100"       # tempo\n      - "6831/udp"   # Jaeger - Thrift Compact\n      - "6832/udp"   # Jaeger - Thrift Binary   \n\n  tempo-query:\n    image: grafana/tempo-query:latest\n    command: ["--grpc-storage-plugin.configuration-file=/etc/tempo-query.yaml"]\n    volumes:\n      - ./etc/tempo-query.yaml:/etc/tempo-query.yaml\n    ports:\n      - "16686:16686"  # jaeger-ui\n    depends_on:\n      - tempo\n\n  loki:\n    image: grafana/loki:2.1.0\n    command: -config.file=/etc/loki/loki-local.yaml\n    ports:\n      - "3101:3100"                                   # loki needs to be exposed so it receives logs\n    environment:\n      - JAEGER_AGENT_HOST=tempo\n      - JAEGER_ENDPOINT=http://tempo:14268/api/traces # send traces to Tempo\n      - JAEGER_SAMPLER_TYPE=const\n      - JAEGER_SAMPLER_PARAM=1\n    volumes:\n      - ./etc/loki-local.yaml:/etc/loki/loki-local.yaml\n      - ./data/loki-data:/tmp/loki\n\n  nodejs-otel-tempo-api:\n    build: .\n    command: \'./wait-for.sh ms-db-server:1433 -- node ./dist/server.js\'\n    ports:\n      - "5555:5555"\n    environment:\n      - OTEL_EXPORTER_JAEGER_ENDPOINT=http://tempo:14268/api/traces\n      - OTEL_SERVICE_NAME=nodejs-opentelemetry-tempo\n      - LOG_FILE_NAME=/app/logs/nodejs-opentelemetry-tempo.log\n      - DB_USER=sa\n      - DB_PASS=P@ssw0rd\n      - DB_SERVER=ms-db-server\n      - DB_NAME=OtelTempo\n    volumes:\n      - ./data/logs:/app/logs\n      - ./etc/wait-for.sh:/app/bin/wait-for.sh   #https://github.com/eficode/wait-for\n    depends_on:\n      - ms-db-server\n      - tempo-query\n\n  promtail:\n    image: grafana/promtail:master-ee9c629\n    command: -config.file=/etc/promtail/promtail-local.yaml\n    volumes:\n      - ./etc/promtail-local.yaml:/etc/promtail/promtail-local.yaml\n      - ./data/logs:/app/logs\n    depends_on:\n      - nodejs-otel-tempo-api\n      - loki\n\n  prometheus:\n    image: prom/prometheus:latest\n    volumes:\n      - ./etc/prometheus.yaml:/etc/prometheus.yaml\n    entrypoint:\n      - /bin/prometheus\n      - --config.file=/etc/prometheus.yaml\n    ports:\n      - "9090:9090"\n    depends_on:\n      - nodejs-otel-tempo-api\n\n  grafana:\n    image: grafana/grafana:7.4.0-ubuntu\n    volumes:\n      - ./data/grafana-data/datasources:/etc/grafana/provisioning/datasources\n      - ./data/grafana-data/dashboards-provisioning:/etc/grafana/provisioning/dashboards\n      - ./data/grafana-data/dashboards:/var/lib/grafana/dashboards\n    environment:\n      - GF_AUTH_ANONYMOUS_ENABLED=true\n      - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin\n      - GF_AUTH_DISABLE_LOGIN_FORM=true\n    ports:\n      - "3000:3000"\n    depends_on:\n      - prometheus\n      - tempo-query\n      - loki\n
Run Code Online (Sandbox Code Playgroud)\n