Azure CI/CD流水线运行Test Containers时连接失败求助
问题:Azure Pipelines中Test Containers连接失败(本地正常)
当前Azure Pipelines配置
trigger: - master pool: vmImage: ubuntu-latest variables: buildConfiguration: 'Release' steps: - task: DotNetCoreCLI@2 displayName: 'dotnet build $(buildConfiguration)' inputs: command: 'build' projects: '**/BookyWooks.sln' arguments: '--configuration $(buildConfiguration)' # Add step to run tests - task: DotNetCoreCLI@2 displayName: 'dotnet test' inputs: command: 'test' projects: '**/*Tests.csproj' # Specify test project(s) or wildcard pattern arguments: '--configuration $(buildConfiguration)'
问题现象
本地运行测试可正常连接SQL Server和RabbitMQ容器,但在Azure Pipelines流水线中执行测试时出现连接失败错误。
Test Containers配置代码
public class OrderApiApplicationFactory<TEntryPoint> : WebApplicationFactory<Program>, IAsyncLifetime where TEntryPoint : Program { private const string Database = "master"; private const string Username = "sa"; private const string Password = "yourStrong(!)Password"; private string ConnectionString; private const ushort MsSqlPort = 1433; private const ushort RabbitMqPort = 5672; // RabbitMQ default port private readonly IContainer _mssqlContainer; private readonly IContainer _rabbitMqContainer; public HttpClient HttpClient { get; private set; } = default!; private const string RabbitMqUsername = "guest"; private const string RabbitMqPassword = "guest"; public OrderApiApplicationFactory() { _mssqlContainer = new ContainerBuilder() .WithImage("mcr.microsoft.com/mssql/server:2022-latest") .WithPortBinding(MsSqlPort, true) .WithEnvironment("ACCEPT_EULA", "Y") .WithEnvironment("SQLCMDUSER", Username) .WithEnvironment("SQLCMDPASSWORD", Password) .WithEnvironment("MSSQL_SA_PASSWORD", Password) .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MsSqlPort)) .Build(); _rabbitMqContainer = new ContainerBuilder() .WithImage("rabbitmq:3-management-alpine") // Use rabbitmq:management image .WithPortBinding(RabbitMqPort, 5672) .WithEnvironment("RABBITMQ_DEFAULT_USER", RabbitMqUsername) .WithEnvironment("RABBITMQ_DEFAULT_PASS", RabbitMqPassword) .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(RabbitMqPort)) // Adding wait strategy .Build(); } public async Task InitializeAsync() { await _mssqlContainer.StartAsync(); await _rabbitMqContainer.StartAsync(); var host = _mssqlContainer.Hostname; var port = _mssqlContainer.GetMappedPublicPort(MsSqlPort); ConnectionString = GetDatabaseConnectionString(); HttpClient = CreateClient(); } protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.ConfigureServices(services => { RemoveDbContextOptions<BookyWooksOrderDbContext>(services); RemoveDbContextOptions<IntegrationEventLogDbContext>(services); AddDbContext<BookyWooksOrderDbContext>(services); AddDbContext<IntegrationEventLogDbContext>(services); }); } private void RemoveDbContextOptions<T>(IServiceCollection services) where T : DbContext { var descriptor = services.SingleOrDefault( d => d.ServiceType == typeof(DbContextOptions<T>)); if (descriptor != null) services.Remove(descriptor); } private void AddDbContext<T>(IServiceCollection services) where T : DbContext { services.AddDbContext<T>(options => { options.UseSqlServer(ConnectionString, builder => builder.MigrationsAssembly(typeof(T).Assembly.FullName)); }); } private string GetDatabaseConnectionString() { return $"Server={_mssqlContainer.Hostname},{_mssqlContainer.GetMappedPublicPort(MsSqlPort)};Database={Database};User Id={Username};Password={Password};TrustServerCertificate=True"; } public new async Task DisposeAsync() { await _mssqlContainer.DisposeAsync(); await _rabbitMqContainer.DisposeAsync(); } }
排查与解决思路
1. 修正RabbitMQ端口绑定逻辑
当前RabbitMQ使用固定端口映射WithPortBinding(RabbitMqPort, 5672),在Azure Pipelines环境中可能出现端口冲突。改为随机端口映射,和SQL Server保持一致:
_rabbitMqContainer = new ContainerBuilder() .WithImage("rabbitmq:3-management-alpine") .WithPortBinding(RabbitMqPort, true) // 随机映射端口 .WithEnvironment("RABBITMQ_DEFAULT_USER", RabbitMqUsername) .WithEnvironment("RABBITMQ_DEFAULT_PASS", RabbitMqPassword) .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(RabbitMqPort)) .Build();
同时确保RabbitMQ的连接配置使用动态获取的端口:_rabbitMqContainer.GetMappedPublicPort(RabbitMqPort)。
2. 增强容器就绪等待策略
仅检查端口可用性不足以确保服务完全就绪,需添加更精准的等待逻辑:
- SQL Server:等待SQL命令执行成功,确认服务可接受连接:
.WithWaitStrategy(Wait.ForUnixContainer() .UntilPortIsAvailable(MsSqlPort) .UntilCommandIsCompleted("sqlcmd -U sa -P yourStrong(!)Password -Q 'SELECT 1'"))
- RabbitMQ:通过管理API检查服务健康状态:
.WithWaitStrategy(Wait.ForUnixContainer() .UntilPortIsAvailable(RabbitMqPort) .UntilHttpRequestIsSucceeded(r => r .ForPath("/api/health") .ForPort(15672) // RabbitMQ管理端口 .WithBasicAuth(RabbitMqUsername, RabbitMqPassword)))
3. 调整连接字符串的主机名
在Docker-in-Docker(DIND)环境中,容器的Hostname可能无法被Agent进程访问,改用localhost配合映射端口:
private string GetDatabaseConnectionString() { return $"Server=localhost,{_mssqlContainer.GetMappedPublicPort(MsSqlPort)};Database={Database};User Id={Username};Password={Password};TrustServerCertificate=True"; }
4. 优化流水线配置
- 添加Docker状态检查步骤,确保Agent环境中Docker服务正常:
- script: | docker info docker version displayName: 'Verify Docker environment'
- 测试步骤添加详细日志输出,便于定位错误:
- task: DotNetCoreCLI@2 displayName: 'dotnet test' inputs: command: 'test' projects: '**/*Tests.csproj' arguments: '--configuration $(buildConfiguration) --verbosity detailed'
5. 检查Docker权限与版本
确保Test Containers使用最新版本(避免DIND环境兼容性问题),同时在流水线中添加权限修复步骤(若需要):
- script: sudo chmod 666 /var/run/docker.sock displayName: 'Fix Docker socket permissions'
内容的提问来源于stack exchange,提问作者Ports
相关产品推荐
相关产品推荐

