Although Ansible has great support overall, it still comes up lacking when you try to do “cutting edge” work, especially around Windows.
In recent times, I have been working a lot with Windows Server. I felt the need to create my own custom modules to get certain things to work, and this was quite an interesting experience. So here’s my brain dump.
What I want to achieve
- I want to create a Prometheus Docker container on Windows with Ansible.
- As of date (Ansible 2.5.3) the
docker_container
module does not support containers on Windows.
1. Getting Started
- To start with, read the official document on custom module development.
- I will be creating a custom module within my role. For this I create a
roles/<role-name>/library
directory. - The module I create is called
win_docker_container
so I create a filewin_docker_container.ps1
. - I took the basic
win_environment
module from the above link and pasted that into the above file and started modifying it accordingly. - I have a Windows Server 2016 VM on Azure which I used for testing. The Azure VM is called “Windows Server 2016 Datacenter - with Containers”.
First I added a simple task that shows the intended usage of my module:
I then created a simple hello world
starting point as follows. Note that I have edited the win_environment
module from above link and added appropriate variable parsing.
Running the module on a Windows Server 2016 VM now gives me the message:
TASK [prometheus-grafana : win_docker_container] ******************************************************************************************************************************************************************
EXEC (via pipeline wrapper)
ok: [TestVMWin] => {
"changed": false
}
Now let’s start developing the module!
2. Add Basic logic to create containers declaratively
My logic will include the following for starters:
- Check whether a container exists with the given name.
- If yes, do not create a new container. Return existing container’s ID.
- If no, create a container. Return new container’s ID.
The logic would look something like this. I added this in between the areas marked INSERT CODE HERE
.
3. Add error handling and ability to handle containers without custom networks.
In this iteration I added some more capability:
- If a
network
argument is passed the module will check if the docker network already exists. - If it does not, the module fails with a user-friendly error.
- If containers already exist with given name, the container’s ID is returned.
- If containers do not exist, logic is available to create them with or without a custom network.
And the output of this is:
changed: [TestVMWin] => {
"changed": true,
"container_id": "c300ba5743da877eea1f1ef129b135725ce6fa52beb32c19c0860bb068e62b73"
}
The true test of declarativeness would come when I ran it twice - so I did, and here’s the output
ok: [TestVMWin] => {
"changed": false,
"container_id": "c300ba5743da"
}
4. Add Port Forwarding
In this iteration, I:
- Added an additional parameter
publish_all_ports
which publishes all ports marked using theEXPOSE
keyword. The default value for this istrue
. - Refactored the way we invoke the command.
The invocation of this module now changes to:
Notes
- Port Forwarding seems to work only with the
nat
network created by default. Adding new networks does not seem to work with Docker on Windows. (In other words I have a little more understanding left for Docker on Windows) - The web service is exposed on a random port which we would need to find out by executing
docker ps -a
on the Docker Host. - Accessing the web service is possible through the IP of the virtual network -
localhost
does not work.
5. The complete module
The final output:
changed: [TestVMWin] => {
"changed": true,
"command": "docker run --net nat -P --name prometheus_container -d stefanscherer/prometheus-windows:2.2.0",
"container_id": "042c6aafd50fb74426b774533b5d3a687b292181af8ac8383b848faffb2d361a"
}