.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.

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
    build: .
    hooks:
      pre_build:
        - name: dotnet restore
          cmd: dotnet restore
        - name: dotnet publish
          cmd: dotnet publish -c Release -o out
    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.

Note the build and hooks lines on our kontena.yml file. Using the stack API we can have Kontena restore and publish our dotnet application, as well as build and push our Docker images all in one command.

Time to build and deploy our application using the Kontena CLI tool:

$ kontena stack build
$ kontena stack install

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

$ kontena service scale dotnet-example/api 3

Now let's test out our application. We can use a mix of Kontena CLI commands and some shell scripting to extract our public IP address and test:

$ IP=$(kontena service show dotnet-example/internet_lb | grep 'public ip' | awk '{ print $3 }')
$ 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();
}

And now let's redeploy our application:

$ kontena stack build
$ kontena stack upgrade dotnet-example kontena.yml

Now you should see the X-MachineName header rotate across all of your host nodes when making API calls.

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: photography of brown wooden handled stair by Mike Wilson.