Combining Ansible and AWS Cloudformation for Windows Provisioning

Imagine an agentless “robot” user that you can program to configure servers, network equipment, public cloud resources, deploy applications, etc.

Ansible is an IT automation solution which was acquired by RedHat in 2015. Already popular before the RedHat takeover, Ansible is becoming and more more common in IT organisations.

Originally targeted at Linux hosts as a target for automated configuration management and orchestration, Ansible acquired in version 2.0 capabilities to automate network devices.

And in version 2.3, which has been just released, Ansible’s Windows friendly features have been seriously augmented with domain related modules and an experimental “runas” feature.

Ansible Windows 2.jpg

I would like to show in this post how Ansible can be used as the glue between public cloud provisioning features such as AWS cloudformation and Virtual Machines, with Windows in mind  for this particular example. This is a quick introduction which hopefully showcases some of the main features of Ansible with regards to configuration management (orchestration is a totally different kettle of fish).

Ansible Features

Ansible is agentless. It connects to Linux/UNIX hosts through ssh and to Windows hosts through WinRM. As such, it acts as a regular user. Windows authentication comprises of local accounts, Kerberos, CredSSP, etc. – see the Ansible Windows support page for more information.

Ansible’s syntax is YAML based and the basic element of an Ansible play is a task. A task invokes a module which usually performs a single change, for instance install packages, restart a service, modify firewall rules, etc. Tasks can be grouped in roles as a form of logical grouping, i.e. every task related to setting up a database server can be put together under a “database” role.

There are plenty of modules, the list can be found here: http://docs.ansible.com/ansible/modules.html.

A collection of plays is called a playbook. Ansible also comprises of a powerful templating system based on Jinja2. It allows to programmatically create complex configuration files that can then be copied onto the target nodes.

Configuration Management and Idempotency

One of the key advantages of configuration management frameworks such as Ansible (and Puppet, Chef, Saltstack, etc.) resides in idempotency. This means that no matter how many times you run the same code against a bunch of targets, you will always end up in the same state.

For instance, if an Ansible playbook installs a package and restarts a service, it might make changes the first time it runs but the second time you run it, it will check first if the package is already installed and the service started before doing anything. It makes it possible to regularly run the same code against targets to ensure state, something which can be hard to do with regular scripting languages.

A word of caution though: it is possible to break idempotency. A classic example involves running a non idempotent shell commands as part of an Ansible run. This is why it is always encouraged to use modules instead of ad-hoc shell commands in any configuration management effort.

Provisioning and Configuring a Windows Server

In a nutshell, the following Ansible code executes a first play comprising of three tasks which does the following:

  1. Create a cloudformation stack based on the windows_template.yml cloudformation template (full code here). This simple stack spins up an Ansible ready Windows 2012 R2 server.
  2. Retrieve the details of the newly created Windows 2012R2 server using the ec2_remote_facts module
  3. Retrieve the password of the Windows VM as Windows VMs in AWS are created with a new random password every time
  4. Update the Ansible inventory with the details of the new server.

The second play applies the role “webserver” to the newly created Windows 12R2 server. This role enables IIS using the win_feature Ansible module.

Ansible Code:

---
- hosts: localhost
  vars:
    - aws_region: "ap-southeast-2"
  tasks:
    - name:  launch Windows Ansible Test cloudformation stack
      cloudformation:
        stack_name: "AnsibleWindowsTest1"
        region: "{{ aws_region }}"
        state: "present"
        template: "windows_template.yml"
    - ec2_remote_facts:
        filters:
          instance-state-name: running
          "tag:Name": WindowsAnsibleTest
        region: "{{ aws_region }}"
      register: instance_details
    - name: getting windows password
      ec2_win_password:
        instance_id: "{{ instance_details.instances[0].id }}"
        key_file: "/media/psf/Home/Documents/Kloud/MyWindowsKey.pem"
        wait: yes
        region: "{{ aws_region }}"
      register: win_password_output
    - name: adding host to inventory
      add_host:
        name: windows_test_host
        ansible_host: "{{ instance_details.instances[0].public_dns_name }}"
        ansible_password: "{{ win_password_output.win_password }}"
        groups: ec2_windows
        ansible_winrm_server_cert_validation: ignore
        ansible_connection: 'winrm'
        ansible_user: Administrator
        ansible_port: 5986

- hosts: windows_test_host
  roles:
    - webserver

We are using WindRM over SSL here, on the standard port, 5986. A word about WinRM SSL certificates: the "ansible_winrm_server_cert_validation: ignore" setting is needed if Windows self-signed certificates are being used, this is a python related limitation. For production environments, creating your own certificates is a better alternative, find more about this in the documentation. For a production environment which uses AD, I also strongly advocate the use of Kerberos for user level authentication, which I have used successfully in customer environments.

Ansible Simple IIS Role

Here is the simple Ansible IIS role mentioned above:

---
  - name: Install IIS
    win_feature:
      name: "Web-Server"
      state: present
      restart: yes
      include_sub_features: yes
      include_management_tools: yes

Executing the Ansible Playbook

Ansible Core refers to a base installation of Ansible on a Linux/UNIX/MacOS machine.  It is free and open source. There is no Ansible version for Windows but it can run in the Windows 10 Linux subsystem, even though it is not fully supported for production workloads. See the installation documentation for the various ways to install Ansible Core.

For our example, Ansible interacts with AWS in order to create a cloudformation stack. It leverages authentication that you might have setup for the AWS cli. As such, it leverages the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables that you have setup in order to make programmatic calls to AWS (see the AWS cli documentation).

The last piece of the puzzle is pywinrm which can be installed by issuing "sudo -H pip install "pywinrm>=0.2.2" on a Linux/UNIX host. This will allow Ansible to interact over WinRM with Windows targets.

Once setup, the following command will trigger the cloudformation stack creation and will configure the newly created Windows 12R2 VM:

ansible-playbook -e "windows_aws_key_file=~/MyWindowsKey.pem" -i ec2.py provision.yml

ec2.py is a dynamic inventory script for AWS. It will query AWS and report back a JSON array of all AWS resources. The windows_aws_key_file extra variable is the path to your AWS Windows Key file. Not that the Windows key name is also found in the cloudformation template (windows_template.yml).

The Ansible run will show the output of every task, after which you will have a newly created Windows Server 2012R2 server created with IIS turned on. It is easy to imagine how this can be extended to automatically create complex environments.

Screen Shot 2017-06-13 at 16.21.58

Ansible in a DevOps landscape

Ansible Core can be part of a larger DevOps landscape for configuring VMs or deploying applications, combined with CI/CD software such as Jenkins or Bamboo.

Ansible Tower, a paid for product, is a scheduler for Ansible playbook runs with clustering features and a recording of the history of playbook executions. Its REST api allows for it to be easily integrated with a CI/CD systems to synchronise software builds with the automated deployment of virtual infrastructure and applications.

Code

The entire code for this example can be found here: https://github.com/rbigeard/ansible_clouformation_windows. Don't forget to specify the location of your AWS Windows keys in group_vars/all.yml