Starting with Docker 1.12, the Docker Engine comes with a built-in container orchestration module called Docker Swarm mode. The newest Docker Swarm mode is exciting because you no longer need to deal with complex network configurations, load balancers, service registries or certificate generation and management. The new Docker Engine makes multi-host and multi-container orchestration easy, with just a few commands you can setup a production environment for your applications that can surviving hardware failure and handle software updates with zero downtime.
With ASP.NET Core's release, .NET developers will switch to containers as the default deployment model, this article will show you how easily you can build, deploy and scale an ASP.NET Core app using Docker.
I'm using a Windows 10 machine for development but you can follow this tutorial on a Mac or Linux device using Visual Studio Code and Docker.
Windows 10 prerequisites:
We start by creating a very simple ASP.NET Core Web API project. In Visual Studio, go to "File > New Project > .NET Core > ASP.NET Core Web Application". Select Web API from the ASP.NET Core templates.
Let's name this app TokenGen
.
After the projects is created, go to the Controllers
folder and add a new controller named TokenController
with the following content:
1namespace TokenGen.Controllers
2{
3 [Route("api/[controller]")]
4 public class TokenController : Controller
5 {
6 [HttpGet]
7 public dynamic Get()
8 {
9 return new
10 {
11 Guid = Guid.NewGuid().ToString(),
12 Expires = DateTime.UtcNow.AddHours(1),
13 Issuer = Environment.MachineName
14 };
15 }
16 }
17}
This code generates a new GUID (Globally Unique Identifier) on every GET
call stating the expiration date and the issuer. We use the machine name to identify the issuer. When you run an ASP.NET Core app inside a container, the machine name gets populated with the container-unique identifier. As a result, our code will help us determine in which container our app is running.
The next step is to create a docker file, so we can build our app into a container image. Inside the root directory, next to the .sln
file, create a file named TokenGen.dockerfile
with the following content:
1FROM microsoft/dotnet:latest
2
3# Set environment variables
4ENV ASPNETCORE_URLS="http://*:5000"
5ENV ASPNETCORE_ENVIRONMENT="Development"
6
7# Copy files to app directory
8COPY /src/TokenGen /app
9
10# Set working directory
11WORKDIR /app
12
13# Restore NuGet packages
14RUN ["dotnet", "restore"]
15
16# Build the app
17RUN ["dotnet", "build"]
18
19# Open port
20EXPOSE 5000/tcp
21
22# Run the app
23ENTRYPOINT ["dotnet", "run"]
Now we are ready to build our tokengen-img
docker image. Open PowerShell, navigate to your project root directory and execute the build command:
1docker build -t tokengen-img -f TokenGen.dockerfile .
At this point the Docker engine will pull the dotnet:latest
image from Docker Hub, copy /src/TokenGen
files and run the dotnet CLI commands, you will see in the PS output the build log.
If everything worked out, we are ready to run our app from a container for the first time. Start the TokenGen container using the following bash command:
1docker run --name tokengen -d -p 5000:5000 -t tokengen-img
Let's test the deployment by calling the api/token
endpoint from Powershell:
1Invoke-RestMethod http://localhost:5000/api/token
You should get a response like this:
1guid expires issuer
2---- ------- ------
390c629af-3d78-4b53-81b0-4be563985887 2016-08-09T14:21:11.326422Z 5d0a1f82371c
If you also run docker ps
you will notice that the tokengen
container ID is the same with the issuer value from the api/token
response.
We can now stop and delete the container by using the following commands:
1docker stop tokengen
2docker rm tokengen
In order to scale our app we first need to enable Docker Swarm mode; do this by running docker swarm init
in PowerShell.
Now that Docker Swarm mode is enabled, we will create a task for the swarm and start our app as a service.
The service
command is similar to the docker run
command. You should name your service the same way we named our container earlier, so it's easy to target the service using scale
.
Create and start our new tokengen
service on Docker Swarm:
1docker service create --publish 5000:5000 --name tokengen tokengen-img
Now if we run docker service ls
, we can check if our service is running:
1ID NAME REPLICAS IMAGE COMMAND
296lqd9bdvwhs tokengen 1/1 tokengen-img
And if we call the api/token
endpoint from PowerShell with Invoke-RestMethod http://localhost:5000/api/token
we get the same result:
1guid expires issuer
2---- ------- ------
340afe497-ce14-4f65-936e-910c1490165a 2016-08-09T14:46:15.506Z df5197d48d32
It's time to scale our app. The scale
command lets us run replicas of our application.
1docker service scale tokengen=3
Running docker ps
will show 3 tokengen containers:
1CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2a87acd74a274 tokengen-img:latest "dotnet run" 41 seconds ago Up 35 seconds 5000/tcp tokengen.1.ayuux8p9ztip5uyappics17i7
3debcdb4c7e9d tokengen-img:latest "dotnet run" 41 seconds ago Up 35 seconds 5000/tcp tokengen.2.7gh3immv1tu5as0w4ps4709ib
4df5197d48d32 tokengen-img:latest "dotnet run" 8 minutes ago Up 8 minutes 5000/tcp tokengen.3.2g61t441iyuyehoafoelvkbik
Now we can see the Docker Swarm built-in load balancer at work. Open 3 PowerShell windows and run Invoke-RestMethod http://localhost:5000/api/token
:
1guid expires issuer
2---- ------- ------
30b36bb43-20f0-4183-a364-0c83894e79ac 2016-08-09T14:55:02.673038Z a87acd74a274
4b74b4808-237e-406a-8a8b-f580dcc78225 2016-08-09T14:55:02.68909Z debcdb4c7e9d
592dd2f7b-e47d-4b1d-8192-141d307d276d 2016-08-09T14:55:02.691565Z df5197d48d32
As you can see the load balancer distributes our calls to all 3 replicas. Docker Swarm uses a round-robin system to load balance between the containers.
We can also scale down our service or even stop it by scaling to 0:
1docker service scale tokengen=0
We can remove the service in the same way we deleted the container (using rm
):
1docker service rm tokengen
With Docker Swarm you can apply rolling updates to a service without disruption. In order to publish updates we first need to version our app.
Open project.json
and add the version element "version": "1.0.0-*"
. Now we need to use the version number to tag the Docker image.
In the project root, next the .sln
file, create a PowerShell script swarm-ver-deploy.ps1
with the following content:
1$serviceName = "tokengen"
2
3# parse project.json and extract app version
4$rootPath = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
5$projectPath = "$rootPath\src\TokenGen\project.json"
6$json = Get-Content -Raw -Path $projectPath | ConvertFrom-Json
7$version = $json.version.Split("-")[0]
8
9# tag docker image with app version
10$imageName = "$serviceName-img:$version"
11
12# build image
13if(docker images -q $imageName){
14 "Image $imageName exists!"
15 return
16}else{
17 docker build -t $imageName -f "$rootPath\TokenGen.dockerfile" $rootPath
18}
19
20# create service
21docker service create --publish 5000:5000 --name $serviceName --replicas 3 --update-delay 5s $imageName
This script reads the version from project.json
, builds the docker image tokengen-img:version
, and creates the tokengen
service.
Note that we are using update-delay
parameter to configure the swarm with a 5 second update delay, so the new service instances have time to start up.
If we run swarm-ver-deploy.ps1
then docker ps
we get the following:
1CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2223a898ee96b tokengen-img:1.0.0 "dotnet run" About a minute ago Up About a minute 5000/tcp tokengen.3.8ugt7nogpqczkorm7ehdvw2nb
30b2d5e3b7288 tokengen-img:1.0.0 "dotnet run" About a minute ago Up About a minute 5000/tcp tokengen.2.creqhebrccauxmiwm2iaz6fxt
49edb15886b9a tokengen-img:1.0.0 "dotnet run" About a minute ago Up About a minute 5000/tcp tokengen.1.a3ufm7s7iul5nouidrjt38tkg
We can now create another script that will publish new versions. Name this script swarm-ver-update.ps1
:
1$serviceName = "tokengen"
2
3# parse project.json and extract app version
4$rootPath = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
5$projectPath = $rootPath + "\src\TokenGen\project.json"
6$json = Get-Content -Raw -Path $projectPath | ConvertFrom-Json
7$version = $json.version.Split("-")[0]
8
9# tag docker image with app version
10$imageName = "$serviceName-img:$version"
11
12# build image
13if(docker images -q $imageName){
14 "Image $imageName exists!"
15 return
16}else{
17 docker build -t $imageName -f "$rootPath\TokenGen.dockerfile" $rootPath
18}
19
20# apply update
21docker service update --image $imageName $serviceName
Like the deploy script, swarm-ver-update.psl
reads the version from project.json
and builds the new image. But instead of creating a service, we use service update
command to specify the new image.
If you change the project version to 1.0.1-*
and run swarm-ver-update.ps1
then docker ps
output will be:
1CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2728cb9538f16 tokengen-img:1.0.1 "dotnet run" 8 seconds ago Up 3 seconds 5000/tcp tokengen.2.e9cyto0nuwmtdfo02tij3z0m9
331dfc6b8d626 tokengen-img:1.0.1 "dotnet run" 17 seconds ago Up 13 seconds 5000/tcp tokengen.3.e1qxbp7c2496pmuefjq4dhrok
4e88d06710e20 tokengen-img:1.0.1 "dotnet run" 27 seconds ago Up 22 seconds 5000/tcp tokengen.1.7p4gsxocbfa141uwuwedpfvut
As you can see all service replicas have been updated to v1.0.1. If you would run docker ps
while the update was running you would see that the v1.0.1 was gradually applied.
The Kestrel web server is not meant to be an internet-facing server. For this task, we can use IIS on Windows or NGNIX/HAProxy on Linux.
Using a reverse proxy in front of the Docker Swarm load balancer offers a lot of advantages. I will enumerate those I find most useful:
Let's assume you want to test on your local machine the TokenGen app with SSL.
In IIS, create a new website named tokengen-rp
and bind port 80 and 443 to tokengen.local
. You can use the IIS Express Development certificate or generate a new one from IIS.
Edit the windows hosts file and map tokengen.local
to ::1
.
Download and install IIS application request routing module.
In order to configure reverse proxy for TokenGen app, execute the following steps:
tokengen-rp
site settings click on "URL Rewrite" icon10.0.75.2:5000
as the server name where HTTP requests will be forwardedNote that 10.0.75.2
is the default IP for the Docker MobyLinuxVM that's running on your local machine.
Now we can test our TokenGen app from PowerShell over SSL:
1[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
2Invoke-RestMethod https://tokengen.local/api/token
I hope I've proven how easy and straightforward scaling an application can be with Docker Swarm mode. You can find the TokenGen project and all PowerShell script on GitHub in this public repository.
In the articles to come, I plan to use a NoSQL database to store and retrieve tokens, thereby showcasing Docker networks and container linking.
You can also follow me on twitter @stefanprodan