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.) This is all fine and dandy, but I the places I get to work in typically don't allow that sort of deployment: tightly-firewalled security zones often forbid this kind of setup; workarounds forbidden, we have to think of a different mechanism. We need "pull" mode, where a node connects to a master for instructions on what to do.

Furthermore, "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.

Ansible PULL

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.

Flattr this
Ansible and configuration :: 14 Jul 2012 :: e-mail

Comments

blog comments powered by Disqus