Using LocalStack for NServiceBus Development

Brad Jolicoeur

05/30/2020

Having a local development environment that emulates AWS cloud services can be an extremely handy tool when developing NServiceBus endpoints that use AWS services. LocalStack is that tool and in this article I will describe what LocalStack is, how it is useful and how to get started with an example project.

What is LocalStack?

LocalStack is an open sourced set of mocking tools for AWS services that you can run on your workstation to emulate a fully functional local AWS cloud stack. The LocalStack emulation can also be used for integration testing in your Continuous Integration pipeline. It can even simulate common exceptions you might experience when deployed to the cloud like ProvisionedThrougputExceededException.

LocalStack has a free version that includes many of the services and a paid version called LocalStack Pro with an additional set of services and an enhanced web UI.

Using local stack means you can test on your local machine without provisioning cloud resources. This means you can save money and reduce the need to submit a ticket to get services provisioned. It is especially helpful when doing quick proof-of-concepts for services that have not been approved for use at your company.

Running LocalStack on your development workstation also solves the issue of shared server resources when developing. Shared resources is a real challenge when developing systems that use queues as it is always fun when your colleague unknowingly consumes your messages when dev testing.

Services included in LocalStack

  • API Gateway, Kinesis, DynamoDB, DynamoDB Streams, S3, Firehose, Lambda, SNS, SQS, Redshift, Elasticsearch Service, SES, Route53, CloudFormation, CloudWatch, SSM, SecretsManager, StepFunctions, CloudWatch Logs, STS, IAM, EC2, KMS

There are additional services included in the Pro edition of LocalStack.

While you can install LocalStack directly in your OS, The easiest and best way to run LocalStack is as a Docker Container using Docker-Compose. Using Docker-Compose means you can store the configuration to source control so that your teammates can use it, and it can also incorporate other server resources that are not offered by LocalStack (free) that you may need like RDS or swap in other mock services that you prefer over LocalStack.

As an example, the following docker-compose.yml file from the LocalStack GitHub repo can be used to quickly get LocalStack running. In the next section there are more details on how to get this up and running for an NServiceBus demo project.

version: '2.1'

services:
  localstack:
    container_name: "${LOCALSTACK_DOCKER_NAME-localstack_main}"
    image: localstack/localstack
    ports:
      - "4566-4599:4566-4599"
      - "${PORT_WEB_UI-8080}:${PORT_WEB_UI-8080}"
    environment:
      - SERVICES=${SERVICES- }
      - DEBUG=${DEBUG- }
      - DATA_DIR=${DATA_DIR- }
      - PORT_WEB_UI=${PORT_WEB_UI- }
      - LAMBDA_EXECUTOR=${LAMBDA_EXECUTOR- }
      - KINESIS_ERROR_PROBABILITY=${KINESIS_ERROR_PROBABILITY- }
      - DOCKER_HOST=unix:///var/run/docker.sock
      - HOST_TMP_FOLDER=${TMPDIR}
    volumes:
      - "${TMPDIR:-/tmp/localstack}:/tmp/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"

Getting Started

Install Docker

To get started you will need to install Docker on your workstation. If you are running Windows 10 Pro the best option is Docker Desktop. If you are running Windows 10 home or older windows OS you will need to run Docker Toolbox. Both of these come with docker-compose included.

Note: Docker Desktop will work with Windows 10 Home in the near future. Choose Docker Desktop over Docker toolbox if that option is available to you.

Configure your docker-compose file

For our sample project we want to create a docker compose that limits the LocalStack services to just the ones we need (SQS & S3) and we will add an additional container for a relational database. For this example we will use MySQL as the NServiceBus persistence.

In addition, we are going to use a different mock for KMS for NServiceBus property encryption in our example project.

Note: The KMS configuration is a bit of a diversion from the focus of this article, but I'm repurposing my example project and it is a good way to show that you can overload any of the LocalStack mocks with one you like better. Just know that if you are not using KMS for NServiceBus property encryption, it is optional.

  1. Create a directory on your workstation that you are going to use for your project
  2. Create a docker-compose.yml file in the root directory of your project
  3. Add the following contents to the docker-compose.yml file
version: '2.1'

services:
  localstack:
    container_name: "${LOCALSTACK_DOCKER_NAME-localstack_kms}"
    image: localstack/localstack
    ports:
      - "4567-4597:4567-4597"
      - "${PORT_WEB_UI-8080}:${PORT_WEB_UI-8080}"
    environment:
      - SERVICES=${SERVICES-s3,sqs }
      - DEBUG=${DEBUG- }
      - DATA_DIR=${DATA_DIR- }
      - PORT_WEB_UI=${PORT_WEB_UI- }
      - LAMBDA_EXECUTOR=${LAMBDA_EXECUTOR- }
      - KINESIS_ERROR_PROBABILITY=${KINESIS_ERROR_PROBABILITY- }
      - DOCKER_HOST=unix:///var/run/docker.sock
    volumes:
      - "${TMPDIR:-/tmp/localstack}:/tmp/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"

  db:
    image: mysql
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: example
      MYSQL_DATABASE: testdb
      MYSQL_USER: devuser
      MYSQL_PASSWORD: TestPassword
    ports:
      - "3306:3306"

  kms:
    image: nsmithuk/local-kms
    volumes:
      - ./init:/init
      - ./data:/data
    ports:
      - 8081:8080

Notice that we limited the services that start up to S3 and SQS by editing the SERVICES environment variable in our LocalStack container here SERVICES=${SERVICES-s3,sqs }

You may want to give your LocalLocal stack instance a different container name for each project you use it for. You will find you run into issues spinning up different configurations of LocalStack if you do have separate names by project.

The MySQL configuration is a relatively straight forward docker-compose configurations. You can check out the MySQL page in Docker Hub for more information.

Details on the KMS image I used can be found on the project site. Note that you will need to create a seed.yaml file for the KMS keys in an init folder off of the root of your project folder for this to work correctly. You may want to check out the structure of the example project for reference as the paths are important for the docker volume mappings.

This is an example contents of a seed.yaml file for the KMS mock. It is important to note that these keys are for demonstration purposes and you should generate your own keys and certainly don't use example keys for anything important.

Keys:
  - Metadata:
      KeyId: bc436485-5092-42b8-92a3-0aa8b93536dc
    BackingKeys:
      - 5cdaead27fe7da2de47945d73cd6d79e36494e73802f3cd3869f1d2cb0b5d7a9

Aliases:
  - AliasName: alias/testing
    TargetKeyId: bc436485-5092-42b8-92a3-0aa8b93536dc

Running and connecting to LocalStack

Now for the fun part!

We get to fire up a private local AWS stack with a simple docker-compose up command.

  1. Open up an console window to the root path of your project
  2. execute docker-compose up in the console window

If you are not familiar with docker-compose, it will first download the images referenced in your docker-compose.yml and then will configure and launch the containers.

Downloading the images will look like this docker-compose downloading images

Once your containers are running you will see something similar to this docker containers running

You can now open your browser to http://localhost:8080 to see the basic LocalStack dashboard which will show your SQS queues and S3 buckets once we get them set up.

LocalStack dashboard

You can also interact with your AWS services using the AWS CLI. The only trick to this will be to overwrite the default AWS endpoint the CLI uses with your local path using the --endpoint-url option.

For example you can create an SQS queue with the following CLI command

aws --endpoint-url=http://localhost:4576 sqs create-queue --queue-name test_queue

The blog article Using LocalStack with AWS CLI by Manish Pandit has many more examples of using the AWS CLI for interacting with LocalStack.

Use the configuration from your environment variables in the MySQL docker-compose configuration to connect to your MySQL instance.

Create an NServiceBus endpoint

Now that we have the local stack up and running, we can use it for our NServiceBus example project.

Create endpoint using project template

The best template to use to get started is the dotnet new nsbdockercontainer project template. This is a good template to use since it leverages IGenericHost and can be deployed in a docker container or other methods with a little configuration.

You can install the Particular NServiceBus templates by using the following command

dotnet new --install ParticularTemplates

Once you have the NServiceBus templates you can create an endpoint project with the following command:

dotnet new nsbdockercontainer --name MyEndpoint

Configure endpoint with AWS SQS transport

You will need to configure the transport of your NServiceBus endpoint to use the SQS transport. A simple example of how to configure the SQS transport can be found on Particular's website in the tutorial Simple AmazonSQS Transport usage.

The standard configuration of the SQS transport leverages the AWS .NET SDK and we will need to override the default AWS endpoint URL with your LocalStack URL.

You can use the code below to override the default AWS URL. Note that to keep this example simple and since we are running everything local, we are using the UseHttp = true option. Do not use this option with anything other than test data.

 var transport = endpointConfiguration.UseTransport<SqsTransport>();
        //configure client factory for localstack...not needed for normal AWS
        transport.ClientFactory(() => new AmazonSQSClient(
            new EnvironmentVariablesAWSCredentials(),
            new AmazonSQSConfig //for localstack
            {
                ServiceURL = "http://localhost:4576",
                UseHttp = true,
            }));

        // S3 bucket only required for messages larger than 256KB
        var s3Configuration = transport.S3("transportbucket", "my/key/prefix");

        //configure client factory for localstack...not needed for normal AWS
        s3Configuration.ClientFactory(() =>
            new AmazonS3Client(new EnvironmentVariablesAWSCredentials()
            , new AmazonS3Config
            {
                ServiceURL = "http://localhost:4572",
                ForcePathStyle = true,
                UseHttp = true,
            }));

As long as you do not configure property encryption you do not need to worry about the KMS configuration. I will save the KMS property encryption for a different article.

You can configure your NSB Persistence to use the container with the following example. It is a standard configuration.

var connection = "server=localhost;user=devuser;database=testdb;port=3306;password=TestPassword;AllowUserVariables=True;AutoEnlist=false";
var persistence = endpointConfiguration.UsePersistence<SqlPersistence>();
var subscriptions = persistence.SubscriptionSettings();
subscriptions.CacheFor(TimeSpan.FromMinutes(1));
persistence.SqlDialect<SqlDialect.MySql>();
persistence.ConnectionBuilder(() => new MySqlConnection(connection));

You are now ready to run your project

Once you have the transport and persistence configured in your project, you are ready to run it. Remember to run docker-compose up again if you need to get your containers running again.

Resource links