If you study Ansible’s documentation you’ll notice most of it refers to using Ansible in “push” mode. Push means, that a central Ansible “master server” connects via SSH to the machines (I call nodes) it wants to manage and does what it’s supposed to do. (The diagram I drew depicts this.)
If “push” mode is too restrictive, or if I have too many machines to push to (think: time) I might require a different strategy as “push” mode can be a bottleneck for simultaneous provisioning of many nodes: Ansible processes nodes with a certain amount of parallelism, but there are limits to that. The documentation on Ansible Playbooks mentions pull-mode playbooks, but doesn’t dig into them very deeply.
The ugly and the not so ugly
Ansible can run a playbook locally, rather than connecting over SSH. If you let that sink in for a bit, you’ll come to the conclusion that the local machine requires a copy of Ansible and its dependencies and modules. In other words, some of the great features of Ansible I mentioned when first discussing Ansible are invalidated. I said:
Managed nodes don’t require any software installation, i.e. there is no need to install ansible nor any of its dependencies and thus there are no daemons to manage.
In order to use pull-mode (see also: ansible-pull), we have to install Ansible and its dependencies on each node, some of which we can automate. A node should be OK for pull-mode, if you can run this on it:
$ ansible 127.0.0.1 -c local -m ping
127.0.0.1 | success >> {
"ping": "pong"
}
Storage of playbooks, templates, etc.
The example I give uses git with a public repository on Github.
In production I would not use a publicly accessible repository of any sort because sensitive data could slip into configuration files and become readily visible. (Not that I feel sensitive data has any place in configuration management repositories.) We have plenty of alternatives:
- Set up your own private git (or your favorite revision control, e.g. Subversion) repository on the local network using the git http backend protected by SSL client certificates.
- Use rsync over SSH
- etc., etc., etc.
Provisioning a “Pull”-node
Each of the machines (nodes) that will be using our pull-based deployment
system, need a working copy of Ansible and its dependencies (Jinja2 and
PyYAML; Ansible currently requires Paramiko SSH, but I’ve proposed that
Ansible run locally need not). This can be done
- by your bare-metal provisioning system
- through an initial Ansible run (push mode) from master to nodes (impossible if SSH from manager to nodes is forbidden)
- “manually”, via a glorified shell script provisioned as for the Ansible code in the sample n-repo.
As Ansible is still a work in progress, I propose to distribute its source
together with all other files I require on nodes; this is the reason for the
ansible-dist
directory in the repository. This also allows me to
ensure that whatever nodes will be pulling in actually works.
Remember that any modules you use in your playbooks must be available to
Ansible on your nodes as well: I store them together with the Ansible
distribution which gets cloned to the nodes.
Lets provision a node. We’ll be doing this manually once.
mkdir -p /etc/ansible
cd /etc/ansible
git clone git://github.com/jpmens/n-repo.git . # note the single period
You should probably look at what each file contains.
What each node does
Once the repository is cloned onto nodes, we periodically launch (e.g. via
cron) the node-runner.sh
program which pulls in a possibly updated copy of
the repository and then launches ansible-playbook localhost.yaml
to do the
work.
node-runner.sh
sources a trivial node-runner.cf
, in which
I can, say, change the playbook name from localhost.yaml
to
an architecture- or hostname-specific playbook, or do any other customization I need
on a particular node or group of nodes.
PLAY [127.0.0.1] *********************
SETUP PHASE *********************
ok: [127.0.0.1]
TASK: [Create JANED] *********************
ok: [127.0.0.1]
TASK: [Install Mutt] *********************
ok: [127.0.0.1]
TASK: [dot mutt] *********************
ok: [127.0.0.1]
TASK: [Muttconfig] *********************
ok: [127.0.0.1]
TASK: [Create config file] *********************
ok: [127.0.0.1]
PLAY RECAP *********************
127.0.0.1 : ok=6 changed=4 unreachable=0 failed=0
Some ideas for node-runner.sh
:
- Capture output (errors mainly) and send them via e-mail to an administrator. If the repository it accesses is writable, it could add output to that and push it back. (You get the idea.)
- Indicate to our monitoring system (Nagios, Icinga, whatever) that it has run and completed with code so-and-so.
- Verify it’s own MD5 sum before and after loading itself from the repository to determine whether it should re-exec itself.
Setting Ansible up for nodes to “pull from” instead of “pushing to” them has some disadvantages, such as requiring software installed on the nodes, but there are a few advantages I see:
- No central management server is required (depends on the type of repository)
- De-central repositories are possible. (Again: depends on type of repository.)
- Connections can be initiated by nodes (can be important if you’re not allowed to alter firewall policies)
- Increased parallelism
- Nodes can pull when they are available. (In a push-based model, if a node is unavailable it cannot be configured.)
- Very fast, because the SSH-connection overhead incurred for each task is avoided.
I’m liking Ansible more and more.