.Net Core and SQL Server In Docker - Part 2 : Deploying to AWS

In our last post we set up a new .Net Core WebApi application with a SQL Server backend, all running on Linux and Docker. This time, we will deploy our service and database to Amazon Web Services with Kontena.

Configuring Kontena on AWS

The first step is to make sure we have the Kontena CLI tool installed. After that, jump over to the AWS setup guide and follow the instructions. For your Kontena Master, you can use the t2.micro instance size, but your Kontena Nodes must be at least t2.medium or larger to support SQL Server's minimum RAM requirements. For this tutorial, we are going to assume you created a 2 node cluster with a Kontena grid named test.

Docker Registry

In order for Docker to download our service's image file, we need to store it in a Docker registry. Docker offers a public registry called Docker Hub, but that is not really the best place for us to store our private Docker images. Luckily, Kontena offers an easy to set up private image registry. To set this up, we can run the following command from the terminal:

$ kontena registry create

Note that this new registry is not running over HTTPS by default, so you will need to configure Docker to trust the insecure registry registry.test.kontena.local (Docker for Mac and Docker for Windows let you configure this via the Preferences menu).

VPN

In order for our local Docker to be able to access our new registry, we need to configure VPN access to our AWS grid. Kontena provides this out of the box using Open VPN. To create the VPN and associated config file, run the following command:

$ kontena vpn create
$ kontena vpn config > ~/kontena.ovpn

You can then use the kontena.ovpn file in any Open VPN software. If you are on macOS we recommend TunnelBlick.

Pushing to Docker

Now that we have our private registry and VPN to our new grid, it's time to build our service again with the proper tags and push to the registry. From the terminal run:

$ docker build -t registry.test.kontena.local/dotnet-example:latest .
$ docker push registry.test.kontena.local/dotnet-example:latest

Secrets Management

One thing that we want to avoid is hard coded passwords in our kontena.yml file, specifically the SQL Server user password. Kontena's answer to this is the Kontena Vault. Using Kontena CLI and kontena.yml we can create a password for our SQL Server instance, and assign it to an environment variable to be consumed by our application. From the terminal, run the following:

$ kontena vault write SQLSERVER_SA_PASSWORD Sup3rs3cr3t

Deploying with Kontena Stacks

Now we are finally ready to deploy our containers to the cloud. We will do this using Kontena Stacks. In order to describe our service and dependent database as a Stack, we need to make a kontena.yml file. In our root directory, create a file called kontena.yml and edit it as follows:

stack: dotnet-example  
version: 0.3.0  
services:  
  internet_lb:
    image: kontena/lb:latest
    ports:
      - 80:80
  sqlserver:
    image: microsoft/mssql-server-linux
    stateful: true
    ports:
      - 1433:1433
    environment:
      ACCEPT_EULA: Y
    secrets:
      - secret: SQLSERVER_SA_PASSWORD
        name: SA_PASSWORD
        type: env
    volumes:
      - /var/opt/mssql
  api:
    image: registry.test.kontena.local/dotnet-example:latest
    depends_on:
      - sqlserver
    environment:
      SQLSERVER_HOST: sqlserver
      KONTENA_LB_MODE: http
      KONTENA_LB_BALANCE: roundrobin
      KONTENA_LB_INTERNAL_PORT: 5000
      KONTENA_LB_VIRTUAL_PATH: /
    secrets:
      - secret: SQLSERVER_SA_PASSWORD
        name: SQLSERVER_SA_PASSWORD
        type: env
    links:
      - internet_lb

You will notice there are 3 services here. The first one, internet_lb, is an instance of the Kontena Load Balancer, based on HAProxy. This allows us to balance incoming HTTP traffic between multiple instances of our service running across multiple nodes.

The second service sqlserver is our database. Note the stateful: true line, which tells Kontena this is a stateful service that connects to some sort of hard disk storage. We also set the volumes section because none are defined in the microsoft/mssql-server-linux image provided by Microsoft. We also reference the password created earlier via the secrets section.

The last service is our WebApi service, labelled here as api. We use the depends_on section to tell Kontena our service needs the SQL Server service to be running, and we use the environment and secrets section to feed the required environment variables to our application to connect to SQL Server and register with the load balancer.

In order to deploy our new service, we use the Kontena CLI to build, install and deploy our new stack:

$ kontena stack build
$ kontena stack install
$ kontena stack deploy dotnet-example

Next, let's scale up our service to 3 containers:

$ kontena stack scale dotnet-example 3

Now go to the AWS EC2 console, find the public IP address of one of your nodes, and try to test the API using curl (obviously change the IP Address to match yours):

$ IP=192.168.1.123
$ curl -i http://$IP/api/products

For a little bit of fun, try modifying your Startup.cs file to add a bit of middleware that returns the hostname of the API server.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)  
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    app.Use(next =>
    {
        return async context =>
        {
            // For testing, don't use this in a real production application!
            context.Response.Headers.Add("X-MachineName", Environment.MachineName);
            await next(context);
        };
    });

    app.UseMvc();
}

After you redeploy your application, you should see the X-MachineName header rotate across all of your host nodes.

Wrapping Up

Hopefully by now you see how easy it can be to deploy an Asp.Net Core WebApi service in the cloud. With tools like Docker and Kontena, a modern elastic microservices architecture is within reach.

Accompanying source code for this tutorial can be found at https://github.com/kontena/dotnet-example.

About Kontena

Want to learn about real life use cases of Kontena, case studies, best practices, tips & tricks? Need some help with your project? Want to contribute to a project or help other people? Join Kontena Forum to discuss more about Kontena Platform, chat with other happy developers on our Slack discussion channel or meet people in person at one of our Meetup groups located all around the world. Check Kontena Community for more details.

Image Credits: Skyscrapers Skyline by Matthew Henry.