ansible
outline
Ansible solves several problems. One of the problems it solves very well is centralising management of systems.
ssh
In most setups you'll be using ansible with ssh as transport for data and commands. In some cases normal problems prevent commands running on the remote machine from finishing in a timely manner or finishing at all. Normal ssh timeouts will not help you here.
timeouts
Linux and unix systems are great at process wrapping. The vast majority
of systems will let you specify which binaries to execute, and when then
don't you can customise your PATH
.
Ansible allows you to specify the ssh_command
, however, I found it
easiest to wrap my own:
#!/bin/sh
/bin/timeout 300 /bin/ssh "$@"
Was all that was needed.
This method is very effective at stopping long running commands, heavy work loads and remote filesystem blocks will not stop the controlling job.
tunnelling
In some cases you may need to funnel the connection through a 'jump' box (I don't like this term).
Host * !192.168.45.9
ProxyCommand ssh -W %h:%p -l ansible@192.168.45.9
In the above example we exclude 192.168.45.9
from the proxy rule so
that we can send the traffic via this host.
arbitrary scripts
If Ansible does not happen to have a module for your exact case, you can write your own inline scripts as follows:
- name: http port open
shell: |
ss -tln | egrep ':(80|443)\s'
args:
executable: /bin/bash
Python, which is white-space sensitive requires cmd
in order to maintain indentation:
- name: http port open
shell:
cmd: |
#!/usr/bin/python
for i in range(1, 3):
print i
args:
executable: /bin/bash
- name: http port open
shell: |
ss -tln | egrep ':(80|443)\s'
args:
executable: /bin/bash
system configuration stack
Sometimes you may wish to stack one set of system configuration on top
of another. You may be tempted (like me) to use rsync
to copy the files,
since file
tends to be slow when performing a presence check and copy.
Using rsync
you may come up with this:
- name: copy
shell: |
rsync -avP --chown=root:root --rsync-path="sudo rsync" -e 'ssh
-l ansibleuser' /usr/local/config/{{ config }}/* {{ inventory_hostname }}:/
args:
executable: /bin/bash
warn: False
become: yes
delegate_to: localhost
What we're doing here is running rsync
with a set of options.
Importantly we're telling rsync
to elevate with sudo
--rsync-path="sudo..."
.
If we were to put two of these blocks, one after the other to solve a
problem where 90% of the machines we config manage use /etc/hosts
, but
a small number must not have a few entries, we could end up with a race
condition. Yes, I'm fully aware that lineinfile
would be better for
this, but slow if there were 10,000-ish entries to manage that way.
What I have come up with is a stackable form of applying rsync
entries. In essence, we create a temporary directory, put the
configuration in there, stacked, then copy it as a single operation. You
can apply multiple configuration groups.
- name: configuration copy
hosts: all
tasks:
- name: temp directory
shell: |
mktemp -d -t `date +%Y%m%d_%H%M%S`_XXXXXXXX
register: temp_directory
delegate_to: localhost
- name: copy ansible
vars:
config: none
shell: |
#!/bin/sh
rsync -avP --chown=root:root --rsync-path="sudo rsync" \
-e 'ssh -l ansibleuser' \
/usr/local/config/{{ item }}/* {{ temp_directory.stdout }}:/
with_items: "{{ config.split( ' ' ) }}"
delegate_to: localhost
- name: copy result
shell: |
#!/bin/sh
rsync -avP {{ temp_directory.stdout }}/ {{ inventory_hostname }}:/
delegate_to: localhost
- name: clean temp
shell: |
#!/bin/sh
rmdir "{{ temp_directory.stdout }}"
args:
executable: /bin/bash
warn: False
delegate_to: localhost
What we're doing here is looping through a config variable named
config
and copying each of the directories that match the list under
/usr/local/config
to a temporary directory. Once that is done we copy
the result to the destination machine.
We can define config
with the following playbook command:
ansible-playbook -vv --extra-vars 'config="global web db ftp"' -i destwww, config_copy
[Errno 7] Argument list too long
I've been known in the past to set jobs up that include registers that may not be tiny, sometimes several megabytes.
When this happens, the shell code may be too large to be handed to the remote side in argument format, which is the way Ansible does things normally.
To get around this you can inform the shell task to take input via
stdin
, here's how:
- name: shell input
shell: |
#!/bin/sh
cat >/tmp/large_file
args:
executable: /bin/bash
stdin: "{{ large_output.stdout }}"
The snippet above will write the large_output.stdout
contents into
/tmp/large_file
on the remote system.