Docker.DotNet xUnit Test errors with Cannot assign requested address /var/run/docker.sock

Viewed 325

Versions

  • Docker Engine CE v19.03.8
  • dotnet sdk v3.1.301
  • Docker.DotNet v3.125.2
  • xUnit v2.4.0

Setup

I'm using the Docker.DotNet package to manage container dependencies for the Functional Tests in my ASP.NET Core API project

I've used the code from this blog (https://danieldonbavand.com/dockerdotnet/), to create a class to manage the interactions with the Docker API from within my test project - I've added this code to the end of the post because it's relatively long.

Problem

When I run the dotnet test command from my terminal everything works as expected. However, I want to run these tests when I build my Dockerfile.

When I run docker build -t my-project . I get the following error:

System.Net.Http.HttpRequestException : Connection failed ---- System.Net.Internals.SocketExceptionFactory+ExtendedSocketException : Cannot assign requested address /var/run/docker.sock Stack Trace: at Microsoft.Net.Http.Client.ManagedHandler.ProcessRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken) at Microsoft.Net.Http.Client.ManagedHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) at System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts) at Docker.DotNet.DockerClient.PrivateMakeRequestAsync(TimeSpan timeout, HttpCompletionOption completionOption, HttpMethod method, String path, IQueryString queryString, IDictionary2 headers, IRequestContent data, CancellationToken cancellationToken) at Docker.DotNet.DockerClient.PrivateMakeRequestAsync(TimeSpan timeout, HttpCompletionOption completionOption, HttpMethod method, String path, IQueryString queryString, IDictionary2 headers, IRequestContent data, CancellationToken cancellationToken) at Docker.DotNet.DockerClient.MakeRequestAsync(IEnumerable1 errorHandlers, HttpMethod method, String path, IQueryString queryString, IRequestContent body, IDictionary2 headers, TimeSpan timeout, CancellationToken token) at Docker.DotNet.ContainerOperations.CreateContainerAsync(CreateContainerParameters parameters, CancellationToken cancellationToken) at My.FunctionalTests.Helpers.DockerSetup.StartContainerAsync(String image, String tag, String hostPort, String containerPort, IList1 environmentVariables) in /src/My.FunctionalTests/Helpers/Docker/DockerSetup.cs:line 34 at My.FunctionalTests.Helpers.DockerSetupExtensions.StartLocalStackAsync(DockerSetup docker, String services) in /src/My.FunctionalTests/Helpers/Docker/DockerSetupExtentions.cs:line 12 at My.FunctionalTests.MyTests.GetAsync_Valid_Success(String url) in /src/My.FunctionalTests/MyTests.cs:line 36

Which after doing some googling it is possible this is caused by a localhost conflict. I'm a bit out of my depth with this one so was wondering

  • Why does this error throw?
  • What can I do to solve it?

Below is the relevant code:

Dockerfile

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
WORKDIR /src

# Copy csproj and restore as distinct layers
COPY ./My.sln .
COPY ./My.API/My.API.csproj ./My.API/My.API.csproj
COPY ./My.FunctionalTests/My.FunctionalTests.csproj ./My.FunctionalTests/My.FunctionalTests.csproj
RUN dotnet restore

# Copy everything else and build
COPY . .
RUN dotnet publish -c Release -o /app

# Functional tests
FROM build as functionaltest
WORKDIR /src/My.FunctionalTests
RUN dotnet test

DockerSetup.cs

public class DockerSetup
    {
        private readonly DockerClient _dockerClient;

        public DockerSetup()
        {
            _dockerClient = new DockerClientConfiguration(new Uri(DockerApiUri())).CreateClient();
        }

        private static string DockerApiUri()
        {
            var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);

            if (isWindows)
            {
                return "npipe://./pipe/docker_engine";
            }

            var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
            var isMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);

            if (isLinux || isMacOS)
            {
                return "unix:/var/run/docker.sock";
            }

            throw new Exception("Was unable to determine what OS this is running on, does not appear to be Windows, MacOS or Linux!?");
        }

        public async Task PullImageAsync(string image, string tag)
        {
            await _dockerClient.Images
                .CreateImageAsync(new ImagesCreateParameters
                    {
                        FromImage = image,
                        Tag = tag
                    },
                    new AuthConfig(),
                    new Progress<JSONMessage>());
        }

        public async Task<string> StartContainerAsync(string image, string tag, string hostPort, string containerPort, IList<string> environmentVariables)
        {
            var response = await _dockerClient.Containers.CreateContainerAsync(new CreateContainerParameters
            {
                Image = $"{image}:{tag}",
                ExposedPorts = new Dictionary<string, EmptyStruct>
                {
                    {
                        containerPort, default(EmptyStruct)
                    }
                },
                HostConfig = new HostConfig
                {
                    PortBindings = new Dictionary<string, IList<PortBinding>>
                    {
                        {containerPort, new List<PortBinding> {new PortBinding {HostPort = hostPort}}}
                    },
                    PublishAllPorts = false
                },
                Env = environmentVariables
            });

            await _dockerClient.Containers.StartContainerAsync(response.ID, null);

            return response.ID;
        }

        public async Task StopContainerAsync(string containerId)
        {
            if (containerId != null)
            {
                await _dockerClient.Containers.KillContainerAsync(containerId, new ContainerKillParameters());
            }
        }
    }

I then use this code in the test as so:

MyTests.cs

public class MyTests
        : IClassFixture<WebApplicationFactory<Startup>>
    {
        private readonly WebApplicationFactory<Startup> _factory;
        private readonly HttpClient _client;

        public DockerSetup DockerSetup { get; private set; }

        public MyTests(WebApplicationFactory<Startup> factory)
        {
            _factory = factory;
            _client = _factory.CreateClient();
            DockerSetup = new DockerSetup();
        }

        [Theory]
        [InlineData("s3")]
        public async Task GetAsync_Valid_Success(string url)
        {
            // Assemble
            string containerId = await DockerSetup.StartContainerAsync(
                "localstack/localstack",
                "latest",
                "4566",
                "4566",
                new List<string> { $"SERVICES=s3" }
            );

            // Act
            var response = await _client.GetAsync(url);

            // Assert
            Assert.Equal(HttpStatusCode.OK, response.StatusCode);

            Assert.Equal("application/json; charset=utf-8",
                response.Content.Headers.ContentType.ToString());

            // Cleanup
            await DockerSetup.StopContainerAsync(containerId);
        }
    }
1 Answers

According to your image there is no docker engine during image build, so your docker client fails to access it.

I'm aware of 2 options of providing docker engine inside a container:

  • Mounting host system docker engine connection socket (/var/run/docker.sock) inside a container via volume, so calls of your client will be passed to the host system docker engine.

  • Using docker in docker image to provide an independent docker engine inside your container.

The downside of a former approach is that you can't mount anything during image build, so you'll have to run your pre-built image with a mount and a correct command to execute your tests, something like that:

docker run \
    --volume //var/run/docker.sock:/var/run/docker.sock \
    IMAGE_NAME dotnet test

To use latter approach (docker in docker) you can use an official image docker:dind in your dockerfile to get there a working docker engine.

Some more info on running docker in docker.

Related