Set up automation with Ansible

Ansible is very flexible automation tools with many benefits. The free version is command-line based and here is an example to set it up.

Environment setup

  1. Ansible 2.8 is required or some command may not work.
  2. Ansible files (including playbooks, tasks and inventory files) are all located in /home/glowing/ansible
  3. Default inventory file needs to be referenced in Ansible configuration /etc/ansible/ansible.cfg. This ensures ansible or ansible-playbook command can pick up hosts or host patterns without requiring inventory file through -i on every execution. Here is what the inventory config looks like in ansible.cfg:
[defaults]
 
# some basic default values...
inventory = /etc/ansible/hosts,/home/glowing/ansible/inventories/glowing_inventory.yml
 
host_key_checking = False
  1. As best practice, servers involved should be able to ssh to each other on RSA key authentication. This can be achieved by adding a separate authorized keys file and reference it from /etc/ssh/sshd_config, at the line starting with AuthorizedKeysFile, and separated with the file name of existing authorized keys with a space. In this way, you may keep public keys of human user in one authorized key file and the public keys of machines in the other.
  2. Build inventory file in ~/ansible/inventories/glowing_inventory.yml. The inventory file can declare some variables to use across hosts. If password is involved (e.g. synchronize module still requires password), it can be stored here with base64 encoded. Here is an example of glowing_inventory.yml
---
all:
    vars:
        ansible_user:       glowing
        gh_sudo_pass:       qGS0bWVuu3Jr
        gh_dir:             /opt/glowing/etc
        tmp_dir:            /tmp
       
    children:
        dc1_front_end:
            hosts:
                e9a-ghfe01:
                e9a-ghfe03:
                e9a-ghfe05:
                e9a-ghfe07:
                e9a-ghfe09:
 
        dc2_front_end:
            hosts:
                e9a-ghfe02:
                e9a-ghfe04:
                e9a-ghfe06:
                e9a-ghfe08:
                e9a-ghfe10:
 
        dc1_back_end:
            hosts:
                e9a-ghbe01:
                e9a-ghbe03:
                e9a-ghbe05:
 
        dc2_back_end:
            hosts:
                e9a-ghbe02:
                e9a-ghbe04:
                e9a-ghbe06:
 
        dc1_database:
            hosts:
                e9a-ghdb01:
                e9a-ghdb03:
                e9a-ghdb05:
 
        dc2_database:
            hosts:
                e9a-ghdb02:
                e9a-ghdb04:
                e9a-ghdb06:

That is the basic steps to set up Ansible. Now we can run adhoc commands. The command below allows me to copy a file from executing server to all destination servers that match a pattern:

# ansible 'dc*_back_end:!'`hostname -s` -e "file_name={{gh_dir}}/test.zip" -m copy -a "src={{file_name}} dest={{file_name}}"

In this command:

  • variable {{gh_dir}} is declared in the inventory file. It must be referenced by placing variable name between double curly bracket;
  • An extra variable {{file_name}} is declared at run time because this is the dynamic part of the command;
  • This adhoc ansible command uses copy module. With copy module, the src anddest files are in the same absolute path here so we use this variable to save some typing;
  • reference to the host support wildcard such as dc*_back_end;
  • hostname -s returns the host name of the server where the adhoc command is run
  • :! excluds the running host from being matched as destination server. this is in case that running machine is already in the dc*_back_end group, where copy source and destination are identical

Here is another example for deleting a file from destination servers:

# ansible dc2_database -e "fn=/tmp/file_to_delete" -m file -a "path={{fn}} state=absent"

If we can run adhoc command, then we can start writing some playbook and roles. Below is a simple playbook test-conn.yml to ping each server:

---
  - name: measure mint retrieval time from {{ ansible_limit }}
    hosts: '{{ ansible_limit }}'
    serial: 1
    order: sorted
    gather_facts: no
    ignore_errors: yes
    tasks:
      - name: measure time
        command: curl -s -XGET http://{{ inventory_hostname }}:8080/index.html -o /dev/null
        delegate_to: localhost
        register: curlout
        no_log: true
      - name: display result
        debug:
          msg: "Time to load webpage from {{ inventory_hostname }} is {{ curlout.delta }}."

Then you can run that playbook with the following command:

# ansible-playbook -l dc2_front_end test-conn.yml

Ansible automation is essentially editing yaml files for playbooks. Writing playbook involves a lot of module interaction and one needs to follow best practices. Understanding Ansible roles can help reuse some code.

In addition here are some common playbook keywords you should be familiar with:

  • serial
  • order
  • gather_facts
  • ignore_errors
  • when
  • run_once
  • local_action
  • register

Here are common modules to know

  • set_fact
  • state
  • touch
  • fail
  • synchoronize
  • lineinfile