Overview

Running Spring Boot Apps on Windows with Ansible

No Comments

There are times you have to use a Windows box instead of your accustomed Linux machine to run your Spring Boot app on. Maybe you have to call some native libraries, that rely on an underlying Windows OS or there´s some other reason. But using the same Continuous Integration (CI) tools like we are used to should be non-negotiable!

Running Spring Boot Apps on Windows – Blog series

Part 1: Running Spring Boot Apps on Windows with Ansible
Part 2: Running Spring Boot Apps on Docker Windows Containers with Ansible: A Complete Guide incl Packer, Vagrant & Powershell
Part 3: Scaling Spring Boot Apps on Docker Windows Containers with Ansible: A Complete Guide incl Spring Cloud Netflix and Docker Compose
Part 4: Taming the Hybrid Swarm: Initializing a Mixed OS Docker Swarm Cluster running Windows & Linux Native Containers with Vagrant & Ansible

Windows? No problem, but not without beloved Ansible!

No matter how – it´s fine if we have to use Windows to run our App on. But we should´nt accept beeing forced to give up on our principles of modern software development like Continuous Integration (CI) and Deployment (CD) or automation of recurring tasks like setting up servers and bringing our apps to life on them.

In our current CI-Pipeline we have a Jenkins building & testing our Spring Boot apps and use Ansible to provision our (Linux) machines, so that we can deploy and run our apps on them. Why not just do the same with those Windows boxes?

Seems to be something like a dream? Ansible was this Unix/SSH-thing right?! How could that work with Windows? Our Jenkins runs on Linux – and this should be somehow capable of managing Windows environments?

windows_is_coming

Well, that´s possible and there´s a way to use Ansible here 🙂 From Version 1.7 on, Ansible also supports managing Windows machines! Instead of using SSH, Ansible does this with the help of native PowerShell remoting (and Windows Remote Management WinRM), as you can read in the docs.

Do you like the idea? Let´s go ahead and try it out!

Get yourself a Windows (Vagrant) box

First of all we need a Windows box we can do our magic on. So if you don´t have one spare, the Microsoft developer sites have something for you. It was really suprising to me, but there are Vagrant images you can just download! Go to https://developer.microsoft.com/en-us/microsoft-edge/tools/vms and select a virtual machine like Microsoft Edge on Windows 10 Stable (14.xxx) and Vagrant as platform. You´ll need to have some virtualization software running on your machine – my Mac is loaded with VirtualBox for example. Download the MSEdge.Win10_RS1.Vagrant.zip and extract it. And there you are: The Windows Vagrant box dev-msedge.box is nearly ready.

Because Microsoft doesn´t seem to ship metadata for the box, we need to add it via:

vagrant box add dev-msedge.box --name "windows10"

The last things we need is an installed Vagrant and a Vagrantfile, which is already prepared inside this blog post´s corresponding example project on github:

Vagrant.configure("2") do |config|
  config.vm.box = "windows10"
  config.vm.guest = :windows
 
  # Configure Vagrant to use WinRM instead of SSH
  config.vm.communicator = "winrm"
 
  # Configure WinRM Connectivity
  config.winrm.username = "IEUser"
  config.winrm.password = "Passw0rd"
 
  config.vm.provider "virtualbox" do |vb|
     # Display the VirtualBox GUI when booting the machine
     vb.gui = true
   end
end

Because we use Windows, the Vagrantfile mainly tweaks the Vagrant Configuration Options to use WinRM instead of SSH. You can read more details in the vagrant winrm docs. To fire up a full blown Windows 10 you only have to clone the repository and run vagrant up. Wait a few seconds and your Windows box should be running:

windows10_vagrant_box

There´s only one thing, that can cause the vagrant up to run into a “Timed out while waiting for the machine to boot […]”. This is because Microsoft sadly doesn´t configure the Network List Management Policies in a way, that Windows Remote Management (WinRM) could work together with Vagrant completely frictionless. To solve this we need to manually go into Local Security Policy / Network List Management Policies (after the Windows box is up and running), double klick on Network, go to tab Network Location and set the Location type to private and the User permissions to User can change location. Having made these changes, our vagrant up will work like a charm 🙂

Making Windows Ansible ready

There are some steps needed to prepare our Windows box so that it will smoothtly work together with Ansible. These steps are quite dependent on the Windows version.

If you´re going with a current version like the one from developer.microsoft.com mentioned above then there´s not much to do here. Because Powershell 3.0 or higher is a requirement for Ansible and Windows 10 ships with 5.0 out of the box, we just need to run this script to configure remoting for Ansible on our Windows box. The easiest way to do this is to use iwr and iex Powershell commands:

iwr https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1 -UseBasicParsing | iex

Having older versions could lead to a more complex setup process. Sometimes you need to allow script execution in general by running a command like the following (this is one of many possible solutions and it´s the “just make it work”-way 😉 ):

Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope CurrentUser

Or there´s no Powershell 3.0 or higher (see the list of powershell-versions-and-their-windows-version). To see the Powershell version, run a

get-host

To upgrade to Powershell 3.0, there´s also a script for you.

We also need to know some kind of credentials of our Windows box. You could do it with Kerberos as well, but you should be warned that the configuration is rather complex. I would advice to go with the good old administrative account, which is the second possible option besides Kerberos to connect to the Windows box using Ansible. Looking at our Microsoft Edge on Windows 10 Vagrant box, the installation instructions tell us the needed secrets (IEUser & Passw0rd!).

Testdrive the Ansible connectivity

That´s nearly everything we need to testdrive the Ansible connectivity of our Windows box. To give it a try we need a ansible playbook we can execute. That should contain a hostsfile with the IP of our Windows box (which is localhost in our case) and a correct Ansible configuration. Our github repository provides a working configuration example:

ansible_user: IEUser
ansible_password: Passw0rd!
ansible_port: 55986
ansible_connection: winrm
ansible_winrm_server_cert_validation: ignore

Together with our running & Ansible ready Windows 10 Vagrant box, we could run our first connectivity test. A good method here is to use the win_ping module, which is one of the many Ansible Windows modules. Just be sure to have the latest Ansible release installed on your Linux or Mac. Writing this article this is 2.2.0.0, which I installed via pip from the PyPI. Installing ansible on Windows is another story… To start the test, type the following command:

ansible restexample-windows-dev -i hostsfile -m win_ping

If you get a SUCCESS, everything is fine:

127.0.0.1 | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}

But if you get an UNREACHABLE!, it could be a real challenge. That could lead to a lot of work to get things working. For me it helped to double or triple check the credentials (try to log off and on to your Windows box again!). If you get time outs, check if you did all the steps described in Making Windows Ansible ready above.

Have a runnable & buildable Spring Boot App in place

This is the easy part – but if you want to deploy an Spring Boot app, you have to have a working example in place, right!?! 🙂 You could create one in a few minutes for yourself (e.g. with Spring Initializr or the awesome Spring starter guides), take something existing you already build or just take the example project used for this blog post (a extremely simple REST service app).

Either way you choose: Be sure to have a working build in place that is able to produce a runnable Spring Boot jar-File. In our example project restexamples you get the needed restexamples-0.0.1-SNAPSHOT.jar by running:

mvn clean package

Suggestions for Windows-ready ansible playbooks

Before you start to craft your first ansible playbook to provision a Windows box, let me give you some points to take along. If you have some Ansible experience with managing Unix-like machines, maybe you´re not aware of those things in the first place:

Update to the latest Ansible version. Ansible´s Windows support is getting better with every release. Many of the Windows modules only work with the latest Ansible version – so be sure to have that installed!

Don´t think the Ansible Windows docs have the same quantity or quality like as you are used to. I don´t want to obsess anybody here and great work is done from the Ansible team! But working with Ansible on Windows is not only this high-gloss way that you are used to. There are times you have to try 3-5 different modules till you finally have a working solution for your problem.

ALWAYS escape backslash in paths with a leading backslash. If you have a path like C:\temp you should place somthing like this in your playbook:

"C:\\temp"

Don´t assume that paths with C:\ProgramFiles (x86)\XYZ will work. Especially in our case this is quite important as we need a Java Runtime Environment (JRE) to fire up our Java application. If you use the installed one, try to use alternative paths instead like this one that Oracle places after a successful JRE installation:

"C:\\\ProgramData\\\Oracle\\\Java\\\javapath\\\java.exe"

A complete example

This blog posts´ example project already ships with a complete Ansible playbook that shows how to provision a Windows box, so that we can deploy and run a Spring Boot app on it. Let´s have a more detailed look on this!

First of all, we prepare the Windows box to handle our new deployment:

- hosts: "{{host}}"
  vars:
    spring_boot_app_path: "C:\\spring-boot\\{{spring_boot_app_name}}"
    path_to_java_exe: "C:\\ProgramData\\Oracle\\Java\\javapath\\java.exe"
 
  tasks:
  - name: Create directory C:\spring-boot\spring_boot_app_name, if not there
    win_file: path={{spring_boot_app_path}} state=directory
 
  - name: Install nssm (non-sucking service manager) via chocolatey
    win_chocolatey:
      name: nssm

After defining some paths needed later, we create a directory to deploy our Spring Boot app to and install the Non-Sucking Service Manager (nssm) with the help of the Windows package manager chocolatey, which are both really helpful in the context of working with Windows boxes.

The latter brings the missing capability of package managment to Windows, which you already love on your Linux or Mac (with brew) machines. And nssm will give us the power to run our Spring Boot app as real Windows services with all the benefits like auto-restarting after reboots. Gone through to several experiments with many possible solutions I discovered this as a quite elegant way to manage Spring Boot apps on Windows.

The next steps are quite interesting and yet not really intuitive. It took me some time to figure those out and there are some alterations we should discuss afterwards:

  - name: Stop Spring Boot service, if there - so we can extract JRE & other necessary files without Windows file handle problems
    win_service:
      name: "{{spring_boot_app_name}}"
      state: stopped
    ignore_errors: yes
 
  - name: Install Java Runtime Environment (JRE) 8 via chocolatey
    win_chocolatey:
      name: jre8
 
  - name: Copy Spring Boot app´s jar-File to directory C:\spring-boot\spring_boot_app_name
    win_copy:
      src: "{{spring_boot_app_jar}}"
      dest: "{{spring_boot_app_path}}\\{{spring_boot_app_name}}.jar"

The first thing here is to stop the service, that manages our Spring Boot app. Well – that´s kind of weird I hear you saying. It is, but it has nothing to do with the first execution of our playbook – but with all the others, beginning with the second one.

Because Windows has this “fine feature” called sharing violation error: If a running process has a handle on a file or directory, Windows won´t let you change or delete it. But that´s what we want to do: We want to be able to update the used JRE or other files, that our Spring Boot app will need to run fine. So that´s something like a missing advice: always stop your processes or services before taking any more action!

I mentioned the first run of our script – it would break, if the service does not exist. Therefore we use a really nice Ansible feature – we just ignore errors with the help of ignore_errors: yes. Now the service is stopped if it´s already installed to prevent sharing violation errors or the win_service module brings up an error – which is ignored, if there was no service installed before.

Now we are able to download and extract the necessary Java Runtime Environment or just install the jre8 package with the help of chocolatey. In the third step we deploy the pre-build Spring Boot app as jar into our previously created directory.

Install & configure the Windows service

We finally reached the point where we could install our Spring Boot app as Windows service:

  - name: Install Spring Boot app as Windows service (via nssm), if not already there - but remain stopped to configure Application directory
    win_nssm:
      name: "{{spring_boot_app_name}}"
      application: "{{path_to_java_exe}}"
      app_parameters:
          "-jar": "{{spring_boot_app_path}}\\{{spring_boot_app_name}}.jar"
      state: stopped
 
  - name: Set the Application path for the Spring Boot app to the folder where the needed native libraries reside
    raw: nssm set {{spring_boot_app_name}} AppDirectory {{spring_boot_app_path}}
 
  - name: Fire up Spring Boot app Windows service
    win_service:
      name: "{{spring_boot_app_name}}"
      state: restarted

The first thing here is to define the Windows service with the help of the win_nssm module. We provide the path to the java.exe as application option and the -jar spring-boot-app.jar as app_parameters. The state is only stopped for the moment, because we want to configure another nssm service option.

The nssm service option AppDirectory could be really important, if your application needs native libraries like dll files at the same directory like your jar file. The crucial nssm option could be configured manually via a nssm edit servicename which will bring up something like this:

nssm_startup_directory

But we need to change the value of Startup Directory within our Ansible script. Because the win_nssm module sadly doesn´t provide a configuration option, we need to rely onto the raw module. With the help of nssm set servicename AppDirectory path we are able to do the trick.

Using win_service we could now safely fire up our Spring Boot app as a Windows service 🙂 So let´s get our hands dirty and run our Ansible playbook! Just make sure you´re Windows box is running and Ansible prepared:

ansible-playbook -i hostsfile restexample-windows.yml --extra-vars "spring_boot_app_jar=../restexamples/target/restexamples-0.0.1-SNAPSHOT.jar spring_boot_app_name=restexample-springboot host=restexample-windows-dev"

The script should produce an output like that:

running_ansible_playbook_windows

Smoketest

As you may noticed, we didn´t discuss the script´s last step. Let´s have a look into the restexample-windows.yml:

  - name: Wait until our Spring Boot app is up & running
    win_uri:
      url: "http://localhost:8080/swagger-ui.html"
      method: GET
    register: result
    until: result.status_code == 200  
    retries: 5
    delay: 5

As a final step it is a good practice to check if our Spring Boot app is running fine. This could be achieved with the win_uri module. The test is only working with something we can do some kind of meaningful HTTP GET on. Therefore the small example application leverages the power of SpringFox, which generates JSON API documentation and provides a small web app we can do a GET on – you can try it yourself on http://localhost:8080/swagger-ui.html. So if the SwaggerFox app is up and running (and returns a HTTP Statuscode 200), we assume our Spring Boot app is working as expected.

Endless possibilities…

Now we are able to deploy our Spring Boot apps to Windows boxes – and run more complex scenarios on the Microsoft machines. How about a good old SOAP Service, based on Spring Boot and deployed 10 times in parallel – each one with a separate port? Or any other app you´d like to run!

The possibilities are endless. I would be really pleased to hear from your deployment scenarios with Ansible and Windows 🙂

Jonas Hecht

Trying to bridge the gap between software architecture and hands on coding, Jonas hired at codecentric. He has deep knowledge in all kinds of enterprise software development, paired with passion for new technology. Connecting systems via integration frameworks Jonas learned to not only get the hang of technical challenges.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Comment

Your email address will not be published. Required fields are marked *