Ansible

Ansible is a configuration management software.

Connects via ssh or docker to a list of inventory machines and executes a series of tasks eventually grouped in playbooks.

At first, create an inventory file with all your hosts.


In [1]:
cd ansible


/notebooks/notebooks/ansible

In [2]:
cat inventory


#
# This inventory file contains a list of server to 
#  play with - divided in groups.
#
[course]
# this is the local machine where you run jupyter
# the tutorial just works with this one.
pythonforsysadmin_course_1  ansible_connection=local


# Another group of servers
# where we can pass optional arguments
# Homework: you can play with this group of host
#  once you exchange ssh-keys between the pythonforsysadmin_course_1
#  container and the pythonforsysadmin_ansible_* ones._
[ansible]
172.17.0.[5:7]   

Now check if you can ping the local host.


In [14]:
# Check connections versus the local host in the "course" group

!ansible -i inventory course  -msetup


pythonforsysadmin_course_1 | SUCCESS => {
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "172.17.0.3"
        ], 
        "ansible_all_ipv6_addresses": [
            "fe80::42:acff:fe11:3"
        ], 
        "ansible_architecture": "x86_64", 
        "ansible_bios_date": "06/30/2014", 
        "ansible_bios_version": "A00", 
        "ansible_cmdline": {
            "BOOT_IMAGE": "/vmlinuz-4.9.17-100.fc24.x86_64", 
            "LANG": "it_IT.UTF-8", 
            "i8042.nopnp": true, 
            "rd.lvm.lv": "vg0/root00", 
            "ro": true, 
            "root": "/dev/mapper/vg0-root00"
        }, 
        "ansible_date_time": {
            "date": "2017-04-05", 
            "day": "05", 
            "epoch": "1491399416", 
            "hour": "13", 
            "iso8601": "2017-04-05T13:36:56Z", 
            "iso8601_basic": "20170405T133656277588", 
            "iso8601_basic_short": "20170405T133656", 
            "iso8601_micro": "2017-04-05T13:36:56.277679Z", 
            "minute": "36", 
            "month": "04", 
            "second": "56", 
            "time": "13:36:56", 
            "tz": "UTC", 
            "tz_offset": "+0000", 
            "weekday": "Wednesday", 
            "weekday_number": "3", 
            "weeknumber": "14", 
            "year": "2017"
        }, 
        "ansible_default_ipv4": {
            "address": "172.17.0.3", 
            "alias": "eth0", 
            "broadcast": "global", 
            "gateway": "172.17.0.1", 
            "interface": "eth0", 
            "macaddress": "02:42:ac:11:00:03", 
            "mtu": 1500, 
            "netmask": "255.255.0.0", 
            "network": "172.17.0.0", 
            "type": "ether"
        }, 
        "ansible_default_ipv6": {}, 
        "ansible_devices": {
            "sda": {
                "holders": [], 
                "host": "", 
                "model": "ST500LM000-1EJ16", 
                "partitions": {
                    "sda1": {
                        "sectors": "1024000", 
                        "sectorsize": 512, 
                        "size": "500.00 MB", 
                        "start": "2048"
                    }, 
                    "sda2": {
                        "sectors": "81920", 
                        "sectorsize": 512, 
                        "size": "40.00 MB", 
                        "start": "1026048"
                    }, 
                    "sda3": {
                        "sectors": "262144", 
                        "sectorsize": 512, 
                        "size": "128.00 MB", 
                        "start": "1107968"
                    }, 
                    "sda4": {
                        "sectors": "1536000", 
                        "sectorsize": 512, 
                        "size": "750.00 MB", 
                        "start": "1370112"
                    }, 
                    "sda5": {
                        "sectors": "819200", 
                        "sectorsize": 512, 
                        "size": "400.00 MB", 
                        "start": "2906112"
                    }, 
                    "sda6": {
                        "sectors": "14827568", 
                        "sectorsize": 512, 
                        "size": "7.07 GB", 
                        "start": "961943552"
                    }, 
                    "sda7": {
                        "sectors": "1048576", 
                        "sectorsize": 512, 
                        "size": "512.00 MB", 
                        "start": "3725312"
                    }, 
                    "sda8": {
                        "sectors": "897589248", 
                        "sectorsize": 512, 
                        "size": "428.00 GB", 
                        "start": "4773888"
                    }, 
                    "sda9": {
                        "sectors": "8388608", 
                        "sectorsize": 512, 
                        "size": "4.00 GB", 
                        "start": "902363136"
                    }
                }, 
                "removable": "0", 
                "rotational": "1", 
                "sas_address": null, 
                "sas_device_handle": null, 
                "scheduler_mode": "cfq", 
                "sectors": "976773168", 
                "sectorsize": "512", 
                "size": "465.76 GB", 
                "support_discard": "0", 
                "vendor": "ATA"
            }
        }, 
        "ansible_distribution": "Debian", 
        "ansible_distribution_major_version": "8", 
        "ansible_distribution_release": "jessie", 
        "ansible_distribution_version": "8.5", 
        "ansible_dns": {
            "nameservers": [
                "10.0.11.254", 
                "#", 
                "removeme-babel-dns", 
                "10.0.11.254", 
                "#", 
                "removeme-babel-dns", 
                "10.0.11.254", 
                "#", 
                "removeme-babel-dns", 
                "10.0.11.254", 
                "10.0.252.5"
            ], 
            "search": [
                "babel.it"
            ]
        }, 
        "ansible_domain": "", 
        "ansible_env": {
            "CLICOLOR": "1", 
            "GIT_PAGER": "cat", 
            "GPG_KEY": "C01E1CAD5EA2C4F0B8E3571504C367C218ADD4FF", 
            "HOME": "/root", 
            "HOSTNAME": "7ddd5bc0dd90", 
            "JPY_PARENT_PID": "6", 
            "LANG": "C.UTF-8", 
            "LC_ALL": "C.UTF-8", 
            "LC_MESSAGES": "C.UTF-8", 
            "PAGER": "cat", 
            "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 
            "PWD": "/notebooks/notebooks/ansible", 
            "PYTHONPATH": "", 
            "PYTHON_PIP_VERSION": "8.1.2", 
            "PYTHON_VERSION": "2.7.12", 
            "TERM": "xterm-color"
        }, 
        "ansible_eth0": {
            "active": true, 
            "device": "eth0", 
            "ipv4": {
                "address": "172.17.0.3", 
                "broadcast": "global", 
                "netmask": "255.255.0.0", 
                "network": "172.17.0.0"
            }, 
            "ipv6": [
                {
                    "address": "fe80::42:acff:fe11:3", 
                    "prefix": "64", 
                    "scope": "link"
                }
            ], 
            "macaddress": "02:42:ac:11:00:03", 
            "mtu": 1500, 
            "promisc": false, 
            "type": "ether"
        }, 
        "ansible_fips": false, 
        "ansible_form_factor": "Portable", 
        "ansible_fqdn": "7ddd5bc0dd90", 
        "ansible_gather_subset": [
            "hardware", 
            "network", 
            "virtual"
        ], 
        "ansible_hostname": "7ddd5bc0dd90", 
        "ansible_interfaces": [
            "lo", 
            "eth0"
        ], 
        "ansible_kernel": "4.9.17-100.fc24.x86_64", 
        "ansible_lo": {
            "active": true, 
            "device": "lo", 
            "ipv4": {
                "address": "127.0.0.1", 
                "broadcast": "host", 
                "netmask": "255.0.0.0", 
                "network": "127.0.0.0"
            }, 
            "ipv6": [
                {
                    "address": "::1", 
                    "prefix": "128", 
                    "scope": "host"
                }
            ], 
            "mtu": 65536, 
            "promisc": false, 
            "type": "loopback"
        }, 
        "ansible_machine": "x86_64", 
        "ansible_machine_id": "748cfc5d0ac0444e85d1cccb08411403", 
        "ansible_memfree_mb": 706, 
        "ansible_memory_mb": {
            "nocache": {
                "free": 3707, 
                "used": 4200
            }, 
            "real": {
                "free": 706, 
                "total": 7907, 
                "used": 7201
            }, 
            "swap": {
                "cached": 0, 
                "free": 4095, 
                "total": 4095, 
                "used": 0
            }
        }, 
        "ansible_memtotal_mb": 7907, 
        "ansible_mounts": [
            {
                "device": "/dev/mapper/docker-253:2-537280731-c85cb1165b8c710d56b97bfa1bbd902f1dc2eed671a883f570d48a8ab23dc822", 
                "fstype": "xfs", 
                "mount": "/", 
                "options": "rw,context=\"system_u:object_r:svirt_sandbox_file_t:s0:c567,c688\",relatime,nouuid,attr2,inode64,logbsize=64k,sunit=128,swidth=128,noquota", 
                "size_available": 105882546176, 
                "size_total": 107320705024, 
                "uuid": "NA"
            }, 
            {
                "device": "/dev/mapper/vg0-home", 
                "fstype": "xfs", 
                "mount": "/notebooks", 
                "options": "rw,seclabel,relatime,attr2,inode64,noquota", 
                "size_available": 21948248064, 
                "size_total": 214643507200, 
                "uuid": "NA"
            }, 
            {
                "device": "/dev/mapper/vg0-data", 
                "fstype": "xfs", 
                "mount": "/etc/resolv.conf", 
                "options": "rw,seclabel,relatime,attr2,inode64,noquota", 
                "size_available": 4040863744, 
                "size_total": 214643507200, 
                "uuid": "NA"
            }, 
            {
                "device": "/dev/mapper/vg0-data", 
                "fstype": "xfs", 
                "mount": "/etc/hostname", 
                "options": "rw,seclabel,relatime,attr2,inode64,noquota", 
                "size_available": 4040863744, 
                "size_total": 214643507200, 
                "uuid": "NA"
            }, 
            {
                "device": "/dev/mapper/vg0-data", 
                "fstype": "xfs", 
                "mount": "/etc/hosts", 
                "options": "rw,seclabel,relatime,attr2,inode64,noquota", 
                "size_available": 4040863744, 
                "size_total": 214643507200, 
                "uuid": "NA"
            }
        ], 
        "ansible_nodename": "7ddd5bc0dd90", 
        "ansible_os_family": "Debian", 
        "ansible_pkg_mgr": "apt", 
        "ansible_processor": [
            "GenuineIntel", 
            "Intel(R) Core(TM) i5-4210U CPU @ 1.70GHz", 
            "GenuineIntel", 
            "Intel(R) Core(TM) i5-4210U CPU @ 1.70GHz", 
            "GenuineIntel", 
            "Intel(R) Core(TM) i5-4210U CPU @ 1.70GHz", 
            "GenuineIntel", 
            "Intel(R) Core(TM) i5-4210U CPU @ 1.70GHz"
        ], 
        "ansible_processor_cores": 2, 
        "ansible_processor_count": 1, 
        "ansible_processor_threads_per_core": 2, 
        "ansible_processor_vcpus": 4, 
        "ansible_product_name": "Inspiron 7347", 
        "ansible_product_serial": "54CK432", 
        "ansible_product_uuid": "4C4C4544-0034-4310-804B-B5C04F343332", 
        "ansible_product_version": "00h", 
        "ansible_python": {
            "executable": "/usr/bin/python", 
            "has_sslcontext": true, 
            "type": "CPython", 
            "version": {
                "major": 2, 
                "micro": 9, 
                "minor": 7, 
                "releaselevel": "final", 
                "serial": 0
            }, 
            "version_info": [
                2, 
                7, 
                9, 
                "final", 
                0
            ]
        }, 
        "ansible_python_version": "2.7.9", 
        "ansible_selinux": false, 
        "ansible_service_mgr": "upstart", 
        "ansible_swapfree_mb": 4095, 
        "ansible_swaptotal_mb": 4095, 
        "ansible_system": "Linux", 
        "ansible_system_capabilities": [
            "cap_chown", 
            "cap_dac_override", 
            "cap_fowner", 
            "cap_fsetid", 
            "cap_kill", 
            "cap_setgid", 
            "cap_setuid", 
            "cap_setpcap", 
            "cap_net_bind_service", 
            "cap_net_raw", 
            "cap_sys_chroot", 
            "cap_mknod", 
            "cap_audit_write", 
            "cap_setfcap+eip"
        ], 
        "ansible_system_capabilities_enforced": "True", 
        "ansible_system_vendor": "Dell Inc.", 
        "ansible_uptime_seconds": 12220, 
        "ansible_user_dir": "/root", 
        "ansible_user_gecos": "root", 
        "ansible_user_gid": 0, 
        "ansible_user_id": "root", 
        "ansible_user_shell": "/bin/bash", 
        "ansible_user_uid": 0, 
        "ansible_userspace_architecture": "x86_64", 
        "ansible_userspace_bits": "64", 
        "ansible_virtualization_role": "guest", 
        "ansible_virtualization_type": "docker", 
        "module_setup": true
    }, 
    "changed": false
}

In [4]:
# Pinging all hosts gives some errors too, due to missing hosts or no ssh-key exchange
!ansible -i inventory -m ping all


pythonforsysadmin_course_1 | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}
172.17.0.5 | UNREACHABLE! => {
    "changed": false, 
    "msg": "Failed to connect to the host via ssh.", 
    "unreachable": true
}
172.17.0.6 | UNREACHABLE! => {
    "changed": false, 
    "msg": "Failed to connect to the host via ssh.", 
    "unreachable": true
}
172.17.0.7 | UNREACHABLE! => {
    "changed": false, 
    "msg": "Failed to connect to the host via ssh.", 
    "unreachable": true
}

Further on inventories

You can split your servers in many inventory files, like

  • staging
# staging inventory file
# run with
# ansible -i staging ...
 [ws]
 staging-ws-[0:3]

 [jboss]
 staging-boss-[0:6]
  • production
# production inventory file
# run with
# ansible -i production ...
 [ws]
 ws-[0:3]

 [jboss]
 boss-[0:6]

Playbooks

To run a group of tasks with ansible, just:

  • create a playbook.yml
  • run ansible-playbook -i inventory playbook.yml

A playbook is a list of tasks in yml format, something like

#
# playbook.yml
#
- name: All public traffic is redirected via https
  uri:
    url: http://{{server_host}}/
    validate_certs: false
    follow_redirects: none
    status_code: 301

- name: This webapp  is served
  uri:
    url: https://{{server_host}}/webapp-1
    validate_certs: false
    status_code: 200
    HEADER_testflag: test

- name: The WS is serverd and requires authentication
  uri:
    url: https://{{server_host}}/rest/v1/method
    validate_certs: false
    status_code: 401

In this case, instead of making actual installation|setup tasks, we just created a testsuite validating our deployment. Now we must write another playbook which takes care of deployng the actual machine.

Testing our course environment

We can write a playbook to test our course environment.


In [12]:
!cat python-course-test.yml


# Run this with
#
#   #ansible-playbook -i inventory python-course-test.yml
#
- hosts: course
  tasks:
    - name: The /notebooks directory should exist
      file: path="/notebooks" state=directory

    - name: jupyter is responding on 8888
      uri:
        url: http://0.0.0.0:8888/notebooks
        validate_certs: false
        status_code: 200  # modify this line to simulate an error and see the outcome!

    - name: The template.conf is in place
      file: path="/tmp/template.conf" state=file

In [19]:
!ansible-playbook -i inventory python-course-test.yml


PLAY [course] ******************************************************************

TASK [setup] *******************************************************************
ok: [pythonforsysadmin_course_1]

TASK [The /notebooks directory should exist] ***********************************
ok: [pythonforsysadmin_course_1]

TASK [jupyter is responding on 8888] *******************************************
ok: [pythonforsysadmin_course_1]

TASK [The template.conf is in place] *******************************************
ok: [pythonforsysadmin_course_1]

PLAY RECAP *********************************************************************
pythonforsysadmin_course_1 : ok=4    changed=0    unreachable=0    failed=0   

As you can see something is missing: this playbook is not going to modify our machine but only test that everything is in place.

See ansible-playbook --check and --diff for further infos.

We can run a setup playbook, conventionally named site.yml.


In [15]:
!cat site.yml


# Run this with
#
#   #ansible-playbook -i inventory site.yml
#
- hosts: course
  tasks:
    
    - name: Create a file from a template
      template: src=mytemplate.j2 dest=/tmp/template.conf

    - name: Ensure needed packages are present and eventually install them
      apt: name={{item}} state=present
      with_items:
        - python
        - python-dev
#      ignore_errors: yes  #  ignore errors during the course (eg. connectivity)



In [20]:
!ansible-playbook -i inventory site.yml --diff --limit=course  # in this case the --limit does not change anything ;)


PLAY [course] ******************************************************************

TASK [setup] *******************************************************************
ok: [pythonforsysadmin_course_1]

TASK [Create a file from a template] *******************************************
ok: [pythonforsysadmin_course_1]

TASK [Ensure needed packages are present and eventually install them] **********
ok: [pythonforsysadmin_course_1] => (item=[u'python', u'python-dev'])

PLAY RECAP *********************************************************************
pythonforsysadmin_course_1 : ok=3    changed=0    unreachable=0    failed=0   


In [23]:
!cat mytemplate.j2


#
# A simple templated file for ansible.
# See the documentation to use this flexible
#  jinja template.

GW=10.0.0.254			# a static line
HOSTNAME={{ansible_hostname}}   # a dynamic one