I’m playing around with some integration tests using TestContainers for .NET, Azurite and an Azure Function App.
I have an Azurite TestContainer declared:
_azuriteContainer = new AzuriteBuilder()
.Build();
Which provides (in my environment) a connection string by calling _azuriteContainer.GetConnectionString()
of
DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:59273/devstoreaccount1;QueueEndpoint=http://127.0.0.1:59274/devstoreaccount1;TableEndpoint=http://127.0.0.1:59275/devstoreaccount1
Please note that this is generating a random external port number as expected as the underlying AzuriteBuilder
is calling .WithPortBinding(BlobPort, true)
etc.
The container spins up successfully and the connection string can be used to connect to the Azurite instance from my host machine (e.g. test project tests/Azure Storage Explorer) without issue.
I’m then composing and spinning up my Functions App container using TestContainers:
_functionsContainer = new ContainerBuilder()
.WithImage(_functionsImage)
.WithPortBinding(80, true)
.WithWaitStrategy(
Wait.ForUnixContainer()
.UntilHttpRequestIsSucceeded(r => r.ForPort(80)))
.WithEnvironment(new Dictionary<string, string>
{
{ "AzureWebJobsStorage", _azuriteContainer.GetConnectionString() }
})
.Build();
The Functions App host within the container is initially starting successfully, however within the functions app logs I can see that it’s unable to communicate with the Azurite instance, with error message Connection refused (127.0.0.1:59274)
, which leads to the host process terminating. I’ve verified that the AzureWebJobsStorage
environment variable is being set as expected and that the Azurite instance is still running.
My understanding of Docker is that the Functions App container should be able to communicate with the Azurite container on ports that are exposed externally to the host, which is the case in this stance as the Azurite instance is available to the host via it’s published connection string (confirmed by connecting to Azure Storage Explorer) – what am I missing here?
I’ve also gone a bit further with investigating this by defining a network, adding both containers to that network, defining a network alias for the Azurite container and fudging the connection string to eliminate any other variables from my testing:
_network = new NetworkBuilder()
.Build();
_azuriteContainer = new AzuriteBuilder()
.WithNetwork(_network)
.WithNetworkAliases("azurite")
.Build();
var internalAzuriteConnectionString = _azuriteContainer.GetConnectionString()
.Replace($"{_azuriteContainer.Hostname}:{_azuriteContainer.GetMappedPublicPort(AzuriteBuilder.BlobPort)}" , $"azurite:{AzuriteBuilder.BlobPort}")
.Replace($"{_azuriteContainer.Hostname}:{_azuriteContainer.GetMappedPublicPort(AzuriteBuilder.QueuePort)}", $"azurite:{AzuriteBuilder.QueuePort}")
.Replace($"{_azuriteContainer.Hostname}:{_azuriteContainer.GetMappedPublicPort(AzuriteBuilder.TablePort)}", $"azurite:{AzuriteBuilder.TablePort}");
_functionsContainer = new ContainerBuilder()
.WithImage(_functionsImage)
.WithNetwork(_network)
.WithPortBinding(80, true)
.WithWaitStrategy(
Wait.ForUnixContainer()
.UntilHttpRequestIsSucceeded(r => r.ForPort(80)))
.WithEnvironment(new Dictionary<string, string>
{
{ "AzureWebJobsStorage", internalAzuriteConnectionString }
})
.Build();
This works perfectly, with the Functions App able to communicate with Azurite container to container, but the fudging of the connection string seems fragile and dirty, the approach seems unnecessary and I’d rather reduce the complexity/maintenance burden as much as possible.