Docker 中的 web api 无法连接到主机上的 SQL Server,出现登录前握手错误

Posted

技术标签:

【中文标题】Docker 中的 web api 无法连接到主机上的 SQL Server,出现登录前握手错误【英文标题】:web api in Docker can't connect to SQL Server on host with pre-login handshake error 【发布时间】:2021-10-25 03:05:32 【问题描述】:

首先有一些类似的问题,但我已经尝试了我能找到的所有建议,但似乎没有任何效果。如果你能找到我没有提到的一个,请发表评论,我会试试看。

概要是我正在尝试将 Docker 容器中的 .NET Core 3.1 Web api 连接到我主机上的 SQL Server 2019,但它失败并显示 System.Data.SqlClient.SqlException (0x80131904): A connection was successfully established with the server, but then an error occurred during the pre-login handshake. (provider: TCP Provider, error: 0 - Success)

我有:

Docker(桌面)版本 20.10.7,使用 WSL2 构建 f0df350 SQL Server 开发人员 2019 15.0.2080.9 Windows 10 家庭版 21H1

我试过了:

不同的泊坞窗图像

根据here,人们使用mcr.microsoft.com/dotnet/core/aspnet:3.1-bionic而不是mcr.microsoft.com/dotnet/core/aspnet:3.1取得了成功

端口映射

docker run的各种形式,包括-p 1433:1433-p 0.0.0.0:1433:1433-p localhost:1433:1433

连接字符串

我的连接字符串当前是 "Server=host.docker.internal;Database=xxx;MultipleActiveResultSets=true;UID=sa;PWD='xxx';Integrated Security=false;TrustServerCertificate=true"

我也试过Server=host.docker.internal,1433Server='tcp:host.docker.internal\\SQLSERVER2019,1433'Server='tcp:host.docker.internal,1433'Server='host.docker.internal,1433'...

SQL Server 配置

TCP 已启用并侦听所有接口,但是我看不到 192.168.65.0/24 范围内的 IP 地址,这是 Docker 桌面配置使用的。

SSMS 在本地工作,如果 Web api 应用程序不在 Docker 容器中,则它会连接。

TLS 1.2

一些 cmets 说这是由于较新版本的 SQL Server 出现 TLS 错误,但是我已经尝试了解决方法:

    TrustServerCertificate=true 更改容器的最低协议:
RUN sed -i 's/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/g' /etc/ssl/openssl.cnf
RUN sed -i 's/MinProtocol = TLSv1.2/MinProtocol = TLSv1/g' /etc/ssl/openssl.cnf
RUN sed -i 's/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/g' /usr/lib/ssl/openssl.cnf
RUN sed -i 's/MinProtocol = TLSv1.2/MinProtocol = TLSv1/g' /usr/lib/ssl/openssl.cnf

Dockerfile

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-bionic AS base
WORKDIR /app
EXPOSE 80

ENV ASPNETCORE_URLS=http://+:80

FROM mcr.microsoft.com/dotnet/sdk:3.1 AS build
WORKDIR /
COPY . .
RUN dotnet build my.csproj -c Release -o /app/build

FROM build AS publish
RUN dotnet publish my.csproj -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
RUN sed -i 's/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/g' /etc/ssl/openssl.cnf
RUN sed -i 's/MinProtocol = TLSv1.2/MinProtocol = TLSv1/g' /etc/ssl/openssl.cnf
RUN sed -i 's/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/g' /usr/lib/ssl/openssl.cnf
RUN sed -i 's/MinProtocol = TLSv1.2/MinProtocol = TLSv1/g' /usr/lib/ssl/openssl.cnf
ENTRYPOINT ["dotnet", "my.dll"]

全栈跟踪

Microsoft.Data.SqlClient.SqlException (0x80131904): A connection was successfully established with the server, but then an error occurred during the pre-login handshake. (provider: TCP Provider, error: 0 - Success)
   at Microsoft.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection)
   at Microsoft.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal& connection)
   at Microsoft.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection)
   at Microsoft.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
   at Microsoft.Data.ProviderBase.DbConnectionClosed.TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
   at Microsoft.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry)
   at Microsoft.Data.SqlClient.SqlConnection.OpenAsync(CancellationToken cancellationToken)
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenDbConnectionAsync(Boolean errorsExpected, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenDbConnectionAsync(Boolean errorsExpected, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenAsync(CancellationToken cancellationToken, Boolean errorsExpected)
   at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.AsyncEnumerator.InitializeReaderAsync(DbContext _, Boolean result, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
ClientConnectionId:88dafbd1-163f-486b-a750-ec3dbf5c75ff

谢谢!

[编辑]

当我使用 SSMS 连接到 localhost 时,我得到了一个不同的 SQL Server 实例。因此,我尝试使用 Server=host.docker.internal\SQLSERVER2019; 从连接字符串连接到正确的命名实例,但出现错误: System.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: 25 - Connection string is not valid)

如果我尝试使用 Server=host.docker.internal\\SQLSERVER2019; 我会得到 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: 25 - Connection string is not valid)

注意:我将连接字符串作为环境变量设置为docker run-e ConnectionStrings__MyConnectionString="..."

【问题讨论】:

network-related or instance-specific error 表示服务器不可访问。 error occurred during the pre-login handshake 表示建立 TLS 连接失败,但服务器可访问。如果这是一个命名实例,你确定端口是 1433,通常只用于默认实例。你确定你的openssl 版本支持TLS 1.2 是的,关于“不可访问”部分,但这只是在尝试了一些不同的连接字符串之后,所以可能是一个不同的问题。登录前握手并不总是指 TLS,据我所知,如果是这种情况,我应该得到一个(provider: SSL Provider, error: 31 - Encryption(ssl/tls) handshake failed)。如何为非默认实例配置端口? 【参考方案1】:

我要连接的 SQL Server 命名实例未使用端口 1433!

当您使用 SSMS 连接到 .\namedinstance 时,它使用该实例的正确(动态)端口,所以这让我很失望。使用 SSMS 连接到localhost 给了我一个不正确的密码错误,所以我怀疑我连接到了错误的实例。

我使用 SQL Server 配置管理器为我想要的实例调整端口:

    导航到您不想想要连接的实例的 TCP/IP 设置 在我的情况下,此实例绑定到 1433。对于所有 TCP Dynamic Ports,输入“0”并将所有 TCP Port 设置为空(确保执行列表中的所有 IPxx 项) 重启刚刚编辑的实例 对要设置为 1433 的命名实例重复步骤 2-3,但这次除外,将所有 TCP Dynamic Ports 设置为 0 并将所有 TCP Port 设置为 1433 重启泊坞窗

奇怪的是,在这样做之后我无法使用语法-p 127.0.0.1:1433:1433,因为我会得到一个错误 docker: Error response from daemon: Ports are not available: listen tcp 127.0.0.1:1433: bind: An attempt was made to access a socket in a way forbidden by its access permissions. time="2021-08-25T10:12:15+09:30" level=error msg="error waiting for container: context canceled"

但是,如果我使用更简单的语法:-p 1433:1433,那么 docker 很高兴(我也是!)

【讨论】:

【参考方案2】:

我今天遇到了这个问题,我在同一台机器上运行了三个 SQL Server 实例。 2012、2014 和 2016。我想连接到 2012,因为那是数据库所在的位置。我尝试按照@thinkOfaNumber's 答案中的说明设置端口,但这对我不起作用。

有效的方法是在 docker 文件中设置 TLSv1.2;

FROM mcr.microsoft.com/dotnet/aspnet:3.1 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
RUN sed -i 's/TLSv1.2/TLSv1.0/g' /etc/ssl/openssl.cnf

【讨论】:

SQL 2012 - 像魅力一样工作。

以上是关于Docker 中的 web api 无法连接到主机上的 SQL Server,出现登录前握手错误的主要内容,如果未能解决你的问题,请参考以下文章

C# 和 Docker - 无法从容器化 .NET Core 3.1 Web API 连接到容器化 MySQL 服务器

无法从 NET Core 2.2 Web API 连接到 docker sql server

无法从本地主机连接到 Docker 中的 MySQL(Docker for Mac beta)

尝试连接到 Stripe 的 API 时“无法解析主机”[关闭]

无法从 docker windows 容器中的 asp.net 核心应用程序连接到主机数据库

我无法使用 DataGrip 数据库客户端从本地主机连接到 docker 中的 mysql