Coding Comrade

Running Microsoft Orleans in AWS ECS

October 05, 2020

Microsoft Orleans is a great framework for building distributed systems. It runs many little silos that do their bit of work. In order to work as a one system, they need to talk to each other. Unfortunately, there is no documentation on how to deploy it into AWS ECS and one will most likely face some networking issues.

In a dynamic environment, like AWS ECS, containers scale up and down all the time to achieve optimal resource utilisation or to respond to the load, e.g. number of messages in the processing queue. Each Microsoft Orleans silo registers itself in a directory during the startup stage, normally it’s a database. This is how they know how to discover each other. But how is it possible to find out the host’s IP address and port, when we don’t even know the container instance which will host the task?

AWS provides multiple HTTP endpoints that allow us to get useful information at runtime. The port information can be read from the Amazon ECS Task metadata endpoint version 4. It returns JSON data, including the port mappings we are after. The container instance IP address can be obtained by calling the following instance metadata endpoint: http://169.254.169.254/latest/meta-data/local-ipv4. Now, there are no AWS SDK classes, which provide this functionality out of the box. I’ve created a handy NuGet package that does what we need.

  1. Add the AWSECS.ContainerMetadata NuGet package to the project:
dotnet add package AWSECS.ContainerMetadata
  1. Add the AWS Container Metadata Service to the DI container:
services.AddAWSContainerMetadataService();
  1. Finally, get the information we are looking for and configure the Microsoft Orleans’ endpoint options (assuming we exposed the default ports from within the container):
.Configure<EndpointOptions>(options =>
{
    var awsContainerMetadata = _awsContainerMetadata.GetContainerMetadata();
    var siloPort = awsContainerMetadata?.Ports?.FirstOrDefault(p => p.ContainerPort == EndpointOptions.DEFAULT_SILO_PORT)?.HostPort ?? EndpointOptions.DEFAULT_SILO_PORT;
    var gatewayPort = awsContainerMetadata?.Ports?.FirstOrDefault(p => p.ContainerPort == EndpointOptions.DEFAULT_GATEWAY_PORT)?.HostPort ?? EndpointOptions.DEFAULT_GATEWAY_PORT;
    var advertisedIPAddress = _awsContainerMetadata.GetHostPrivateIPv4Address() ?? Dns.GetHostAddresses(Dns.GetHostName()).First();

    options.AdvertisedIPAddress = advertisedIPAddress;
    options.SiloPort = siloPort;
    options.GatewayPort = gatewayPort;
    options.GatewayListeningEndpoint = new IPEndPoint(IPAddress.Any, EndpointOptions.DEFAULT_GATEWAY_PORT);
    options.SiloListeningEndpoint = new IPEndPoint(IPAddress.Any, EndpointOptions.DEFAULT_SILO_PORT);
})


It’s as simple as that. If you want to take a look at the source code, it’s hosted here. If you find an issue or just want to make it better, please don’t hesitate to create a pull request.

Happy coding!


Eugene Yamenko

Hi, I'm Eugene, a Software Engineer in Sydney, Australia.