In our last post from this series, we talked about how our dreams of running ASP.NET v5 in Linux seemed like they had become reality, until we discovered that Mono was not stable enough to support our application. As it turned out, the shortest route to getting a stable environment was to host the application in Windows instead.

Abandoning Docker and Linux for Windows Server

We had already written Ansible playbooks to deploy the Tinman application in Docker on Linux, but now our deployment story was changing completely. Fortunately, while Ansible was originally written to run over SSH on UNIX-like systems, it also supports a growing subset of functionality for Windows systems. Instead of communicating over SSH, for Windows remote hosts, Ansible uses Powershell remoting. There are also modules developed specifically for Windows.

Running Ansible on Windows

In order to support being controlled remotely by an Ansible agent, we had to first set up some prerequisites on our servers. After some trial and error, we found that the Ansible team actually provides a setup script that ended up working well for us. It allows remote access to WinRM with reasonable security defaults.

Although there aren't as many modules for Windows as there are for Linux systems, we were able to get significant functionality between the following three modules:

  • script - Run a Powershell script on the remote box.
  • raw - Run a cmd.exe command.
  • win_template - Fill in the parameters for a template and upload to the remote server.

With this functionality, even if there wasn't a module to do exactly what we wanted, we could write our own Powershell scripts that would perform any of the logic that would be part of our deployment.

Zero-Downtime Deployments

As we mentioned in earlier posts, when we developed Tinman to run in Docker on Linux, we were using NGINX to route requests to the docker container. This allowed us to have multiple versions of our application running on a single machine, to give longer-running processes time to finish while also granting us the control to route API requests only to the latest version of our code.

NSSM

To turn our self-hosted dnx process into a service, we used NSSM. Starting in Ansible 2.0, support for NSSM comes as a built-in module, win_nssm. Because we were writing our deployment for Ansible 1.9, we controlled NSSM by issuing commands via the raw module.

We created a batch file run.cmd that's parameterized using the win_template module. Then we told NSSM to run that batch file to start our application.

- name: Upload run script
  win_template: src=run.cmd.j2 dest="{{ app_path }}\\run.cmd"

- name: Install service
  raw: nssm install {{ service_name }} "{{ app_path }}\\run.cmd"

IIS

NGINX support is better for Linux than it is for Windows, so it wasn't as attractive an option once we made the switch. Instead, we were inspired by a blog post and guided in our implementation by another blog post to use IIS as a reverse proxy to direct requests to our self-hosted application, thus using the URL Rewrite and Application Request Routing IIS modules.

When a new version is deployed we configure IIS to direct requests to the new version instead of the old one by using the Set-WebConfigurationProperty commandlet.

Sequence

To maintain zero downtime, we:

  • Move the build artifact from CI to the server.
  • Create a parameterized batch file to run our application.
  • Delete any old installations that are longer running.
  • Configure NSSM to run the batch file to start our service on an unused port.
  • Start the new version as a service.
  • Wait for the new instance to start responding to web requests.
  • Direct the IIS proxy to the new instance's port.
  • Tell the old instance to shut down cleanly via a web method invocation.

Conclusion

Because our deployment requires zero downtime, we can deploy with confidence that our customers will not experience any interruption. Deploying frequently as well as deploying during normal working hours, therefore, became much easier.

We hope that one day we can return to a Linux-hosted solution as more of our third-party dependencies are made compatible with CoreCLR. In the meantime, we now have a Windows-hosted web service that reliably deploys in a few minutes. Running on Windows, we've found developing ASP.NET 5 to be a great experience.