Overview

Running Spring Boot Apps on Docker Windows Containers with Ansible: A Complete Guide incl Packer, Vagrant & Powershell

No Comments

This is a crazy world. It´s not only possible to make Ansible provision Windows machines. No! There are Docker Windows Containers out there and if we need to run our Spring Boot Apps on Windows, we want to run them inside those tiny Windows buckets!

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

We´re not talking about Docker Linux Containers!

Oh I hear you saying “Docker on Windows? We´ve done that already…” . Before starting to go into any details, let us be clear here: this post is NOT about running Docker Linux containers on Windows – which is a really nice feature nevertheless. It is something well covered on the web. If you need to do that, go ahead and google one of the many posts about that.

What we´ll do here is something crazy (at least for me). Because some time ago, Microsoft startet to implement Docker in a complete new way! It is now possible to have tiny little Windows Containers (yeah, you´ve heared right) running as real Docker containers inside a Windows Docker Host. For me this was huge, as this means that Docker Inc. and Microsoft had worked together in the past years to really support Docker from within the core of Windows. Just see the official partnership announcement here or the Microsoft architect John Starks talking about Windows Server & Docker at DockerCon 2016. The latter is really interesting to see, as Microsoft got to improve Windows to support Docker API – and not the other way round (which they thought in the first place 🙂 ).

logo sources: Windows icon, Docker logo

Telling colleagues about this is funny almost every time…

A common dialogue is introduced like this:

Ok… I´am a bit confused. You´re saying, Windows should be able to run Containers containing Windows itself?
Yeah, exactly.
Well, are there…?
Yes, you can browse and pull the official Windows Docker Images right on hub.docker.com.
Ohh… Does this mean, I can use the Docker API to work with Windows containers…?
Yes, nearly the hole thing is already implemented (we´ll see whats the current state later). And you have Powershell inside your containers…
Wait… Powershell?
Yes.
But that´s …
Remember, you have tiny Windows Servers inside Docker containers…
Ah, I see… but…
What?
It´s Microsoft! They for sure developed their own Docker I assume.
No.
No?
No, it´s no fork, nothing special – just the official Docker GitHub repo. Again – just watch the DockerCon Video with John Starks to get to know some new flavour of Microsoft…

The build number matters!

As this is a follow up to the blog post Running Spring Boot Apps on Windows with Ansible, I thought it´s a good idea to start based on the findings there – especially on the easy to use Vagrant box with Windows 10 from the Microsoft Edge developer site, which you can just download. But please: DON´T DO THAT! It took me days to find out, that these boxes are based on too old Windows builds! The first important thing starting with Docker Windows Containers is to be sure to have the correct Build Number of Windows 10 (Anniversary Update) or Windows Server 2016. It won´t work with for example 10.0.14393.67 – but it will work with 10.0.14393.206! And yes, it´s the 4th number, that matters.

As a sidenote: The normal way to read the Windows Build number is by firing up a winver on console. This won´t work here, because this will only get you the first 3 numbers. The only way I found to obtain the complete number, is to run the following on a Powershell (I know, it´s not API – but we´re forced to know this last number!):
(Get-ItemProperty -Path c:\windows\system32\hal.dll).VersionInfo.FileVersion

As this is a very mean thing (because every step will work until the final docker run), I baked a check into the Ansible scripts of this post to prevent you from running any other step, if you´re not based on the correct build number. But we´ll talk about that later.

An alternative Windows Vagrant box…

Having found out about the build number issue, I was a bit demotivated. Because I wanted to have a completely comprehensible setup which only relies on official Images and sources – no custom Windows Vagrant boxes, that aren´t provided by Microsoft itself. And besides the Microsoft Edge boxes on Vagrant Atlas are sadly no official ones.

But HashiCorp has the same issue providing their Vagrant boxes on Vagrant Atlas – they have to start from a common ISO image and build their boxes somehow. They found a very nice and general solution for that kind of problem – and released Packer.io. This neat tool is capable of turning an ISO image into nearly every kind of machine image you need – covering Amazon´s AWS, Microsoft Azure, OpenStack, VMWare, VirtualBox and many others. And additionally they provide post-processors – e.g. for Atlas and plain Vagrant boxes. Quoting the docs:

[Packer] … is in fact how the official boxes distributed by Vagrant are created.

So if we were just able to find an correct Windows ISO to build our Vagrant box with Packer, we should be fine! And there the Windows Server 2016 Evalutation ISO or the Windows 10 Enterprise Evalutation ISO come to our rescue. Both Windows 2016 Server and 10 Enterprise come with a 180 Days Evaluation licence (you have to register a live-ID for that).

DISCLAIMER: There are two Windows Container Types : Windows Server Containers (aka isolation level “process” or shared Windows kernel) and Hyper-V-Containers (aka isolation level “hyper-v”). Windows 10 only supports the latter one. But Hyper-V-Containers seem not the thing you´re used to, when it comes to the Docker core concepts. Because Docker relies on Process-Level isolation and does not use a Hypervisor. So with that knowledge I would strongly encourage you to go with Windows Server 2016 and leave Windows 10 behind. At first glance it seems somehow “easier” to start with the “smaller” Windows 10. But I encourage you to not go with that into real life scenarios! If you just want to play, it´s nice (and you currently get the nicer Docker GUI on Windows 10). But if you want to virtualize the Windows Docker Host itself (which is the default settings in most Datacenter I know), I experienced strange behaviors with Windows 10 and it´s needed Hyper-V layer. Not to mention real customer projects, where you couldn´t always run the newest VM-Ware ESXi Version for example. But only the latest version will support virtualized Hyper-V. So just stay with Windows Server 2016 and you should be fine!

Altogether we have the following setup for this post (if you have Linux or Windows on your machine, all steps should apply also):

logo sources: Windows icon, Docker logo, Ansible logo, Packer logo, Vagrant logo, VirtualBox logo

The described Toolchain is quite a huge achievement for getting to know Docker Windows Containers and how this all works. It happened to me so often, that I needed to start fresh from the beginning or somewhere in between. If I haven´t got this completely automated process where everything is just code inside my git repository, I would have needed much more time to accomplish my goals.

Building your Windows Server 2016 Vagrant box

Ok, enough talk guys. Let´s get our hands dirty! As I´am always striving to write practical blog post, everything here is 100% comprehensible based on Open Source tools or at least evaluation licences (it´s Windows after all). Visit the example project on GitHub for more details. It has several steps inside we´ll go through as this post continuous.

Vagrant is really nice for local development and testing on your development machine. Using a Windows Server 2016 installation on another machine, you can simply skip this step – just be sure to prepare your machine correctly for Ansible.

The complete source to accomplish the following step reside inside the folder step0-packer-windows-vagrantbox. But before we continue: Without the groundwork of Joe Fitzgerald and Stefan Scherer the following steps would have been much harder to do. Thank´s for your excellent work!

After installing Virtual Box, Vagrant and Packer (which can easily be accomplished via brew cask install virtualbox, brew cask install vagrant & brew install packer if you´re on a Mac), check out the repository and cd into step0-packer-windows-vagrantbox. Also download the Windows Server 2016 14393.0.161119-1705.RS1_REFRESH_SERVER_EVAL_X64FRE_EN-US.ISO and place it into the current step0-packer-windows-vagrantbox folder. Now start the Packer build with the following command:

packer build -var iso_url=14393.0.161119-1705.RS1_REFRESH_SERVER_EVAL_X64FRE_EN-US.ISO -var iso_checksum=70721288bbcdfe3239d8f8c0fae55f1f windows_server_2016_docker.json

Now get yourself a coffee. This will take some time. Or just stay in front of your machine and watch – it´s better then a movie! Because in this process Windows will be installed completely unattended – which means, we don´t have to click on a single installation screen! But honestly – when did you install Windows on some of your friends´ machine manually…?!? 🙂

Running the build, Packer will create a Virtual Box image, which is configured inside the Packer template windows_server_2016_docker.json. The first lines show the Packer builder configuration:

"builders": [
    {
      "vm_name":"WindowsServer2016Docker",
      "type": "virtualbox-iso",
      "communicator": "winrm",
      ...

The following Provisioners section runs the well known Ansible Configuration Powershell Script, which is only for additional secure feeling that we have everything working. Because WinRM (aka Powershell remoting) and correctly set Firewall configuration is all done through the help of the Autounattend.xml, including installing the correct Windows Version and configuring the needed vagrant User. These XML files are created with the Windows Assessment and Deployment Kit (Windows ADK) – but you´ll need a running Windows instance for that.

The last Post-Processors step configures our Packer build to result in a completely Ansible ready Vagrant box windows_2016_docker_virtualbox.box. It uses a normal Vagrantfile as template for the resulting box.

After your Packer build has successfully finished the only thing that´s left is to add the new Vagrant box to your local Vagrant installation with the following command:

vagrant init windows_2016_docker_virtualbox.box

Now we are where we wanted to be: By just typing a normal vagrant up into our console, the preconfigured Windows box starts in seconds and we are ready for provisioning Docker with Ansible:

Provisioning Windows Containers and Docker with Ansible

Again everything needed to comprehend the following steps is provided inside the folder step1-prepare-docker-windows on our GitHub repository. We´ll now learn how to provision a Windows box to be able to run Docker Windows Containers successfully on. As usual cd into step1-prepare-docker-windows and run the following command (assuming you have a current Ansible installed):

ansible ansible-windows-docker-springboot-dev -i hostsfile -m win_ping

This will check the Ansible connectivity – as you´ve already seen in the previous Blog post. If that gives you a SUCCESS we could proceed to the preparation step. Just run the preparation playbook prepare-docker-windows.yml:

ansible-playbook -i hostsfile prepare-docker-windows.yml --extra-vars "host=ansible-windows-docker-springboot-dev"

As we use the power of Ansible here, that´s everything needed to run Docker Windows Containers – and if this wasn´t a Blog post, we could stop here. But hey, we want to know a bit more – this is a post about Ansible. So let´s have a look into the main playbook´s structure. The tasks inside the prepare-docker-windows.yml provide us with the overview on what needs to be done:

  tasks:
  - name: Check the minimum Windows build number
    include: check-build-number.yml
 
  - name: Install Windows Containers and Hyper-V Windows Features (if not already present)
    include: install-windows-container-features.yml
 
  - name: Install Docker on Windows (always the newest version) and pull needed base images
    include: install-docker.yml
 
  - name: Run a Microsoft Docker Windows Testcontainer
    include: run-test-container.yml
 
  - name: Build the springboot-oraclejre-nanoserver Docker image
    include: build-springboot-oraclejre-nanoserver-image.yml
    vars:
      image_name: springboot-oraclejre-nanoserver
      java8_update_version: 121
      java_build_version: b13
      server_jre_name: server-jre-8u{{java8_update_version}}-windows-x64.tar.gz

After preventing ourselfs from trying to run Docker on Windows with the wrong Build number we install two needed Windows Features: Containers and Hyper-V. After that, we´re able to install Docker itself and pull some base images already (for now we leave out the Installation of Docker Compose – which will be the topic of the next blog post). Then we run a Testcontainer to check if Docker Windows Containers are completely configured and ready. The last step is to build a Spring Boot base image to build our Docker Containers later on.

As these are all crucial steps to do – so let´s look a bit deeper into what´s happening here. Microsoft provides quick start tutorials for Windows Server 2016 and Windows 10, but there are some problems with both described approaches. Although I didn´t recommend it for you to do, these Ansible playbooks are also compatible to Windows 10 (besides Windows Server 2016). My target was to have a installation process that is capable of handling both versions. Since the InstallDocker.msi is currently not supporting Windows Server 2016, it´s not a good way to use it here.

On the other hand the described installation process for Windows Server is not compatible with Windows 10 – although it could have been, because it uses the awesome new Package Manager OneGet (Microsoft, you answered my prayers) with the Powershell Gallery Module DockerMsftProvider. OneGet is compatible to both Windows 10 and Windows Server 2016 – but sadly the module uses the Get-WindowsFeature Powershell Commandlet, which isn´t available on Windows 10.

Windows 10 and Windows Server 2016 agnostic Feature installation

So to achieve a version agnostic installation process I had to go another way. To install the needed Windows Features Containers and Hyper-V for both Windows 10 and Windows Server 2016, I decided to go with the Powershell Commandlets around WindowsOptionalFeature. Because they are version agnostic – at least for all current Windows versions (for more information on that, have a look at this blog and especially into the table “Platform Support”). You can see them in action inside the install-windows-container-features.yml:

  - name: Check if Containers are already installed as Windows Feature
    win_shell: Get-WindowsOptionalFeature -Online –FeatureName Containers | Where State -CContains "Enabled"
    register: check_feature_container_installed
 
  - name: Install Containers natively as Windows Feature (only, if not already installed)   
    win_shell: Enable-WindowsOptionalFeature -Online -FeatureName containers -All -NoRestart
    when: check_feature_container_installed.stdout == ''
    ignore_errors: yes
    register: feature_container_installation   
 
  - name: Check if Hyper-V is already installed as Windows Feature
    win_shell: Get-WindowsOptionalFeature -Online –FeatureName Microsoft-Hyper-V | Where State -CContains "Enabled"
    register: check_feature_hyperv_installed
 
  - name: Install Hyper-V as Windows Feature (only, if not already installed)    
    win_shell: Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All -NoRestart
    when: check_feature_hyperv_installed.stdout == ''
    ignore_errors: yes
    register: feature_hyperv_installation
 
  - name: When Containers and/or Hyper-V had to be installed, we have to reboot the Machine to have them take effect (mostly this step will fail, but it should work nevertheless)
    win_reboot:
      reboot_timeout_sec: 60
      shutdown_timeout_sec: 60
    when: feature_container_installation.changed or feature_hyperv_installation.changed
    ignore_errors: yes

The first Ansible win_shell module uses the Get-WindowsOptionalFeature Commandlet to check if the Containers feature is already installed. Only if it´s not, the second module uses the Enable-WindowsOptionalFeature Commandlet to install Containers.

The third and forth step show a similar procedure for the Hyper-V feature. If one of the two features have to be installed, we need to reboot the Windows box via the win_reboot module – which in case of a locally running virtualized Windows Docker Host (like our Windows Vagrant box) sometimes goes wrong – therefore we set the timeout settings to be safe. If this step crashes, it shouldn´t be a problem since the reboot worked fine in most cases. If the features are already present on the box, there is no reboot required 🙂

Docker Installation

To install Docker itself I went with bits of the manual installation guide together with the Docker chocolatey package. Let´s have a look into the install-docker.yml:

  - name: Checking if Docker Windows Service is already configured (to always ensure a fresh Windows Docker installation)
    win_service:
      name: docker
    register: docker_service_info
    ignore_errors: yes
 
  - name: Stop old Docker Windows Service if there 
    win_service:
      name: docker
      state: stopped
    when: docker_service_info|succeeded
 
  - name: Remove old Docker Windows Service if there 
    win_shell: "C:\\ProgramData\\chocolatey\\lib\\docker\\tools\\docker\\dockerd.exe --unregister-service"
    when: docker_service_info|succeeded
    ignore_errors: yes
 
  - name: Install (or upgrade) Docker via chocolatey
    win_chocolatey:
      name: docker
      upgrade: true
 
  - name: Register Docker as a Windows Service
    win_shell: "C:\\ProgramData\\chocolatey\\lib\\docker\\tools\\docker\\dockerd.exe --register-service"
    ignore_errors: yes
 
  - name: Start Docker as Windows Service
    win_service:
      name: docker
      state: started
 
  - name: Pull the small Windows Docker Base Image microsoft/nanoserver from hub.docker.com
    win_shell: docker pull microsoft/nanoserver

Because the Docker chocolatey package does not take care of the Service startup handling and we always want to start with a fresh Docker installation, we check if the Docker Windows Service is already configured through Ansible´s win_service module. If the Service was already there (which does not apply to the first playbook run), we need to stop and remove it first. The Service deletion should be always done through a dockerd.exe --unregister-service. Therefore we use win_shell module here. After those steps we install or upgrade (if installed before) Docker via chocolatey with the win_chocolatey module. Next things are registering Docker as Windows Service and starting it again.

The install-docker.yml´s last step is to pull the needed Docker base images from Microsoft, so that we´re ready to run our first container afterwards.

There are two base Images from Microsoft: microsoft/windowsservercore and microsoft/nanoserver. The first one is quite huge (~ 10 GBytes!) and more or less a full-fledged Windows Server. This is the Image you need to go with, if the App you want to Dockerize depends on some special Windows libraries etc. For us, the much smaller Nanoserver (~ 600 MBytes) is enough here and we don´t need to await the long running pull (even with a 100MB Internet connection, that is quite slow!) for the Windows Server “Core”.

After the Docker installation we should run a first Docker Windows Container – just to be safe we didn´t forget anything. And there the nice Dotnet-bot comes into play 🙂 By running docker run microsoft/dotnet-samples:dotnetapp-nanoserver this small guy should give us a very cool smile from the console, if Docker is successfully installed. And that´s the purpose of the small run-test-container.yml – it should give an output like this:

TASK [Docker is ready on your Box and waiting for your Containers :)] **********
ok: [127.0.0.1] => {
    "msg": [
        "", 
        "        Dotnet-bot: Welcome to using .NET Core!", 
        "    __________________", 
        "                      \\", 
        "                       \\", 
        "                          ....", 
        "                          ....'", 
        "                           ....", 
        "                        ..........", 
        "                    .............'..'..", 
        "                 ................'..'.....", 
        "               .......'..........'..'..'....", 
        "              ........'..........'..'..'.....", 
        "             .'....'..'..........'..'.......'.", 
        "             .'..................'...   ......", 
        "             .  ......'.........         .....", 
        "             .                           ......", 
        "            ..    .            ..        ......", 
        "           ....       .                 .......", 
        "           ......  .......          ............", 
        "            ................  ......................", 
        "            ........................'................", 
        "           ......................'..'......    .......", 
        "        .........................'..'.....       .......", 
        "     ........    ..'.............'..'....      ..........", 
        "   ..'..'...      ...............'.......      ..........", 
        "  ...'......     ...... ..........  ......         .......", 
        " ...........   .......              ........        ......", 
        ".......        '...'.'.              '.'.'.'         ....", 
        ".......       .....'..               ..'.....", 
        "   ..       ..........               ..'........", 
        "          ............               ..............", 
        "         .............               '..............", 
        "        ...........'..              .'.'............", 
        "       ...............              .'.'.............", 
        "      .............'..               ..'..'...........", 
        "      ...............                 .'..............", 
        "       .........                        ..............", 
        "        .....", 
        "", 
        "", 
        "**Environment**", 
        "Platform: .NET Core 1.0", 
        "OS: Microsoft Windows 10.0.14393 ", 
        ""
    ]
}

Build a Spring Boot Windows Container Docker image

We´re nearly there. The only thing that´s left is to build a Windows Container Docker image, that our Spring Boot App(s) could run on. As you could see the last step of our prepare-docker-windows.yml covers this task:

  - name: Build the springboot-oraclejre-nanoserver Docker image
    include: build-springboot-oraclejre-nanoserver-image.yml
    vars:
      image_name: springboot-oraclejre-nanoserver
      java8_update_version: 121
      java_build_version: b13
      server_jre_name: server-jre-8u{{java8_update_version}}-windows-x64.tar.gz

The included build-springboot-oraclejre-nanoserver-image.yml will do two things: the first thing is to download Java 8 as Server JRE (with the help of wget and some cryptic HTTP header magic) and the second step is to build the springboot-oraclejre-nanoserver. It therefore uses the Dockerfile template Dockerfile-SpringBoot-OracleJRE-Nanoserver.j2 and there are a few things to note, so let´s have a look into it:

#jinja2: newline_sequence:'\r\n'
FROM microsoft/nanoserver:latest
 
# This is a base-Image for running Spring Boot Apps on Docker Windows Containers
MAINTAINER Jonas Hecht
 
# Extract Server-JRE into C:\\jdk1.8.0_xyz in the Container
ADD {{server_jre_name}} /
 
# Configure Path for easy Java usage
ENV JAVA_HOME=C:\\jdk1.8.0_{{java8_update_version}}
RUN setx /M PATH %PATH%;%JAVA_HOME%\bin
 
# Create logging default path for Spring Boot
VOLUME C:\\tmp
 
# A 'Temporary workaround for Windows DNS client weirdness' randomly found at https://github.com/docker/labs/blob/master/windows/windows-containers/MultiContainerApp.md
# Without this, DNS
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop';"]
RUN set-itemproperty -path 'HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters' -Name ServerPriorityTimeLimit -Value 0 -Type DWord

The first line prevents Ansible from ignoring the linebreaks – otherwise Ansible (or Jinja2) kills all of them and you get one long line… The FROM defines the base line image – which is the mentioned microsoft/nanoserver. Only change this line to contain the microsoft/windowsservercore, if you really need the full-fledged Server for your Apps. The ADD takes the pre-downloaded Server JRE and unzips it into the Windows Docker Container at C:\jdk1.8.0_{{java8_update_version}} (I needed to read over the Dockerfile reference twice till I got the tar unpacking feature of ADD).

Having the JRE in place, we set the JAVA_HOME environment variable and Windows Path to contain the correct path to Java. As the spring.io guide for Spring Boot with Docker states, we create a C:\tmp because “that is where a Spring Boot application creates working directories for Tomcat by default”.

And… There is one last step! Don´t miss that one. It took me days to find out. Although this is only relevant when we start to scale our Spring Boot Apps to more than one (which will be part of the next blog post), you should implement it here! You are then safe of the “Temporary workaround for Windows DNS client weirdness” that will occur later. It just empties the Windows Containers´ DNS caches and everything will be fine. As the Microsoft development team states, this will be fixed in a Windows Update soon…

Running your Spring Boot App in a Docker Windows Container

The following step2-single-spring-boot-app is again available on GitHub. If you followed all the steps above, you can give it a try now. You´ll need to check out the example project restexamples and do a mvn clean package to have the needed restexamples-0.0.1-SNAPSHOT.jar ready. As an alternative you can use your own Spring Boot app (just tweek the two parameters app_name and jar_input_path accordingly). Then just cd into step2-single-spring-boot-app and run:

ansible-playbook -i hostsfile ansible-windows-docker-springboot.yml --extra-vars "host=ansible-windows-docker-springboot-dev app_name=restexamples jar_input_path=../../restexamples/target/restexamples-0.0.1-SNAPSHOT.jar"

Calling the main playbook ansible-windows-docker-springboot.yml will mainly do four things on your Windows box:

1. Prepare for the Docker build: Creating a dictionary for the Docker build (win_file module), templating Dockerfile-SpringBoot-App.j2 to a common Dockerfile (win_template module) and copying your Spring Boot App jar file (win_copy module):

  # Prepare for the Docker build...
  - name: Create directory C:\spring-boot\app_name, if not there
    win_file: path={{target_path}} state=directory
 
  - name: Template and copy Spring Boot app´s Dockerfile to directory C:\spring-boot\app_name
    win_template:
      src: "templates/Dockerfile-SpringBoot-App.j2"
      dest: "{{target_path}}\\Dockerfile"
 
  - name: Copy Spring Boot app´s jar-File to directory C:\spring-boot\app_name
    win_copy:
      src: "{{jar_input_path}}"
      dest: "{{target_path}}\\{{app_name}}.jar"

2. Cleanup old Docker containers & images: This is just a simple way to always start with a freshly build Docker image and container – and it´s only relevant from the second execution on. Therefore we stop an existing Docker container, remove it and finally remove the image (all steps use the win_shell module:

  - name: Stop the Service Docker container
    win_shell: docker stop {{app_name}}
    ignore_errors: yes
 
  - name: Remove the Service Docker container
    win_shell: docker rm {{app_name}} --force
    ignore_errors: yes
 
  - name: Remove the Service Docker image
    win_shell: docker rmi {{app_name}}:latest --force
    ignore_errors: yes

Oh… I know! We sadly can´t use the nice Ansible Docker modules here, because they wouldn´t work with Windows. And yes, Redhat – we also want them for Windows!

3. Build & run our Spring Boot App: Now we´re finally there, where we wanted to be from the beginning of this blog post: We build our Docker Container containing our Spring Boot App using win_shell module again. But be sure to change the current working directory to the path, where we have our Dockerfile and app.jar! After that we eventually run our Docker Container. We do this in detached mode (-d) and bind a port to a Host´s port (--publish or -p) for easy access of our Spring Boot App. As you can see it uses the build Docker image.

  - name: Build the Service Docker image
    win_shell: docker build . --tag {{app_name}}:latest
    args:
      chdir: "{{target_path}}"
 
  - name: Run the Service Docker container
    win_shell: "docker run -d --publish {{port}}:{{port}} --name={{app_name}} --restart=unless-stopped {{app_name}}:latest"

4. Healthcheck our Spring Boot App: The last step is to simply do a healthcheck on our hopefully running App. Did I sad “simply”? Well… Not with Windows 🙂 There is sadly no possibility to use localhost to speak to a Container, which´s port was bound to a Host port. Yes! You heard´ right. NO LOCALHOST! Ok, to be fair: This should be fixed in one of the next Windows Updates – the question is only when. But this seems to be not even known by Microsoft itself…

  - name: Obtain the Docker Container´s internal IP address (because localhost doesn´t work for now https://github.com/docker/for-win/issues/458)
    win_shell: "docker inspect -f {% raw %}'{{ .NetworkSettings.Networks.nat.IPAddress }}' {% endraw %} {{app_name}} {{ '>' }} container_ip.txt"
 
  - name: Get the Docker Container´s internal IP address from the temporary txt-file
    win_shell: cat container_ip.txt
    register: win_shell_txt_return
 
  - name: Define the IP as variable
    set_fact:
      docker_container_ip: "{{ win_shell_txt_return.stdout.splitlines()[0] }}"
 
  - name: Wait until our Spring Boot app is up & running
    win_uri:
      url: "http://{{ docker_container_ip }}:{{port}}/health"
      method: GET
    register: health_result
    until: health_result.status_code == 200
    retries: 10
    delay: 5
    ignore_errors: yes

So we have to go the hard way. First we obtain the Docker Container´s IP and – because life isn´t hard enough – we need to pipe it into a temporary container_ip.txt. Why? Because in the first step we need to prevent Ansible from trying to read the Powershell command {{ .NetworkSettings.Networks.nat.IPAddress }} with the help of a raw block. This works perfectly fine, but if we want to obtain the IP as a returning value from the first win_shell call, we sadly can´t prevent Ansible from trying to somehow read Powershell command, which will fail. Therefore we need the container_ip.txt and the second win_shell module.

After that we need to clean the IP from line endings from the > (with a smart combination of set_fact module and the splitlines Filter) and do the wanted health check, where we use the win_uri module – waiting for the /health Spring Boot Actuator endpoint to become available. You can also read the full story on stackoverflow.

We´ve done it!

This post got far longer than I expected in the first place! There are many things you need to know when really doing Docker Windows Containers – I read so many blog posts about the new feature, but they mostly all shared one problem: they left out so many obstacles and there was no complete introduction at all. I hope to provide you with a much more complete guide, that will get you to running Spring Boot Apps inside Docker Windows Containers fast.

What´s left? Well, we deployed one Docker Windows Container. But hey, Docker was build for something more. We want many Containers (e.g. with Spring Cloud), want to be able to look them up (Service registry and discovery), want to scale them as we´d like to (Docker Compose), have a single entry point (Proxy), dynamic routes and so on. The next post about that topics will follow soon. Stay tuned!

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 *