Standardising Configurations with Ansible

For one of my customers I need to clean up some infrastructure that has been manually maintained over time. As we have not yet made a decision on what tool to use, I still wanted to clean up some things first but not spend too much time rolling out a tool to help me do that.

We are using ssh keys to control access to the servers, and not all nodes have the right keys on them. In future I’d like to use something like cfengine to both apply and maintain the configuration, in an auditable fashion, but in the interim I need a low footprint, easy start tool that uses ssh and not much else to clean things up.

That tool is ansible.

Ansible is written in python, so let’s use a virtualenv to keep things clean. With most open source software, I tend to start off with installing from git master repos, and then retreat to stable versions if I have specific issues on the current branch. This also means any bug reports I make, or updates to documentation, can be contributed back directly to that project. First step is to install it.

virtualenv ansible
source ansible/bin/activate
cd ansible
pip install git+git://github.com/ansible/ansible

We’re done. Note if you need to upgrade this at a later stage, simply add --upgrade. Next you’ll need a list of hosts to work with. I have these names set up already in my .ssh/config file so I have a short filename to work with for each server. It also sets up the default username, and optionally a key if I want to use that:

# ~/.ssh/config
Host unknownho.st
  IdentityFile      ~/.ssh/keys/private_key
  IdentitiesOnly    yes
  Port              22
  hostname          123.45.67.89
  user              ec2-user

This means for anything using ssh as a transport layer, I can simply ssh unknownho.st and I’ll be automatically connected using the right key, port, and username. Custom options are also possible within that block if required.

Ansible assumes you’ve got an /etc/ansible/hosts file on your system to work with, but I prefer to keep this separate per environment, so I’ve got a local one inside my virtual env to work with. We can specify this file each time just by including --inventory-file=servers.lst in the ansible commands, or configure it in ./ansible.cfg.

By default, Ansible uses a native python ssh egg called paramiko, but for bootstrapping I have found using openssh gives me more control. Some of the infrastructure I use is kerberised, and this secure connection mode is not possible with paramiko alone. By using openssh as the transport, I can push most authentication and authorisation constraints to the openssh configuration as we saw above.

[hosts]
unknownho.st      ansible_connection=ssh ansible_ssh_user=azureuser

This example is an azure server, and we’ve told ansible to use ssh as the transport, as well as over-ridden the user – ansible uses the name of the currently logged in user by default.

If you are using services like AWS EC2 or Softlayer, then you’ll want a different user as a default. You can set this in ./ansible.cfg as usual. Let’s take this for a test-drive then! Let’s do a survey of our infrastructure, and collect the configuration details from each host.

ansible -i hosts all -m setup --tree configs/

The output is comprehensive, and easily parseable in JSON – this isn’t a full sample but you can see it’s comprehensive.

{
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "100.89.110.71"
        ],
        "ansible_all_ipv6_addresses": [
            "fe80::215:5dff:fe58:7913"
        ],
        "ansible_architecture": "x86_64",
        "ansible_bios_date": "05/23/2012",
        "ansible_bios_version": "090006",
        "ansible_cmdline": {
            "BOOT_IMAGE": "/boot/vmlinuz-3.8.0-25-generic",
            "console": "ttyS0",
            "earlyprintk": "ttyS0",
            "ro": true,
            "root": "UUID=d200911a-b8bb-412f-8721-a2566da43d0d",
            "rootdelay": "300"
        },
        "ansible_date_time": {
            "date": "2013-08-21",
            "day": "21",
            "epoch": "1377122566",
            "hour": "22",
            "iso8601": "2013-08-21T22:02:46Z",
            "iso8601_micro": "2013-08-21T22:02:46.199259Z",
            "minute": "02",
            "month": "08",
            "second": "46",
            "time": "22:02:46",
            "tz": "UTC",
            "year": "2013"
        },
        "ansible_default_ipv4": {
            "address": "100.89.110.71",
            "alias": "eth0",
            "gateway": "100.89.110.1",
            "interface": "eth0",
            "macaddress": "00:15:5d:58:79:13",
            "mtu": 1500,
            "netmask": "255.255.254.0",
            "network": "100.89.110.0",
            "type": "ether"
        },
        "ansible_default_ipv6": {},
        "ansible_devices": {
            "fd0": {
                "holders": [],
                "host": "",
                "model": null,
                "partitions": {},
                "removable": "1",
                "rotational": "1",
                "scheduler_mode": "deadline",
                "sectors": "0",
                "sectorsize": "512",
                "size": "0.00 Bytes",
                "support_discard": "0",
                "vendor": null
            },
            "sda": {
                "holders": [],
                "host": "",
                "model": "Virtual Disk",
                "partitions": {
                    "sda1": {
                        "sectors": "61416495",
                        "sectorsize": 512,
                        "size": "29.29 GB",
                        "start": "16065"
                    }
                },
                "removable": "0",
                "rotational": "1",
                "scheduler_mode": "deadline",
                "sectors": "61440000",
                "sectorsize": "512",
                "size": "29.30 GB",
                "support_discard": "0",
                "vendor": "Msft"
            },
            "sdb": {
                "holders": [],
                "host": "",
                "model": "Virtual Disk",
                "partitions": {
                    "sdb1": {
                        "sectors": "41938944",
                        "sectorsize": 512,
                        "size": "20.00 GB",
                        "start": "2048"
                    }
                },
                "removable": "0",
                "rotational": "1",
                "scheduler_mode": "deadline",
                "sectors": "41943040",
                "sectorsize": "512",
                "size": "20.00 GB",
                "support_discard": "0",
                "vendor": "Msft"
            },
            "sr0": {
                "holders": [],
                "host": "IDE interface: Intel Corporation 82371AB/EB/MB PIIX4 IDE (rev 01)",
                "model": "Virtual CD/ROM",
                "partitions": {},
                "removable": "1",
                "rotational": "1",
                "scheduler_mode": "deadline",
                "sectors": "2097151",
                "sectorsize": "512",
                "size": "1024.00 MB",
                "support_discard": "0",
                "vendor": "Msft"
            }
        },
        "ansible_distribution": "Ubuntu",
        "ansible_distribution_release": "raring",
        "ansible_distribution_version": "13.04",
        "ansible_domain": "jsonified.com",
        "ansible_eth0": {
            "active": true,
            "device": "eth0",
            "ipv4": {
                "address": "100.89.110.71",
                "netmask": "255.255.254.0",
                "network": "100.89.110.0"
            },
            "ipv6": [
                {
                    "address": "fe80::215:5dff:fe58:7913",
                    "prefix": "64",
                    "scope": "link"
                }
            ],
            "macaddress": "00:15:5d:58:79:13",
            "module": "hv_netvsc",
            "mtu": 1500,
            "type": "ether"
        },
        "ansible_form_factor": "Desktop",
        "ansible_fqdn": "az1.jsonified.com",
        "ansible_hostname": "az1",
        "ansible_interfaces": [
            "lo",
            "eth0"
        ],
        "ansible_kernel": "3.8.0-25-generic",
        "ansible_lo": {
            "active": true,
            "device": "lo",
            "ipv4": {
                "address": "127.0.0.1",
                "netmask": "255.0.0.0",
                "network": "127.0.0.0"
            },
            "ipv6": [
                {
                    "address": "::1",
                    "prefix": "128",
                    "scope": "host"
                }
            ],
            "mtu": 65536,
            "type": "loopback"
        },
        "ansible_lsb": {
            "codename": "raring",
            "description": "Ubuntu 13.04",
            "id": "Ubuntu",
            "major_release": "13",
            "release": "13.04"
        },
        "ansible_machine": "x86_64",
        "ansible_memfree_mb": 184,
        "ansible_memtotal_mb": 671,
        "ansible_mounts": [
            {
                "device": "/dev/sda1",
                "fstype": "ext4",
                "mount": "/",
                "options": "rw,discard",
                "size_available": 28337504256,
                "size_total": 30916796416
            },
            {
                "device": "/dev/sdb1",
                "fstype": "ext4",
                "mount": "/var/cache/openafs",
                "options": "rw",
                "size_available": 19822489600,
                "size_total": 21001531392
            }
        ],
        "ansible_os_family": "Debian",
        "ansible_pkg_mgr": "apt",
        "ansible_processor": [
            "AMD Opteron(tm) Processor 4171 HE"
        ],
        "ansible_processor_cores": 1,
        "ansible_processor_count": 1,
        "ansible_processor_threads_per_core": 1,
        "ansible_processor_vcpus": 1,
        "ansible_product_name": "Virtual Machine",
        "ansible_product_serial": "NA",
        "ansible_product_uuid": "NA",
        "ansible_product_version": "7.0",
        "ansible_python_version": "2.7.4",
        "ansible_selinux": false,
        "ansible_ssh_host_key_dsa_public": "AAAA....RVDw=",
        "ansible_ssh_host_key_ecdsa_public": "AAAAi...DVw=",
        "ansible_ssh_host_key_rsa_public": "AAAA....RVDw=",
        "ansible_swapfree_mb": 0,
        "ansible_swaptotal_mb": 0,
        "ansible_system": "Linux",
        "ansible_system_vendor": "Microsoft Corporation",
        "ansible_user_id": "ansible",
        "ansible_userspace_architecture": "x86_64",
        "ansible_userspace_bits": "64",
        "ansible_virtualization_role": "guest",
        "ansible_virtualization_type": "VirtualPC"
    },
    "changed": false
}

More complex examples next time.