You started with Ansible, and for a long time it was the only thing you needed. With a handful of playbooks and an inventory file, you got the job done. However, as the fleet grew, runs went from taking minutes to hours, and parts of the inventory were unreachable at any given moment.
This is not an Ansible flaw. Push-based configuration and continuous state enforcement are simply two different jobs. This blog post is all about getting you started with a hybrid system. A system where you can leverage the best of both worlds by using CFEngine along side Ansible.
The first step is to install and bootstrap CFEngine on all your hosts. And since Ansible already has a working path to the hosts, it is the obvious tool to get it done.
For illustrative purposes, I’ve set up a small inventory with one host (the hub) being responsible for distributing CFEngine policies and Ansible playbooks to the other hosts (the clients). This post only covers the installation. See the follow-up reading at the end for how to distribute playbooks with CFEngine.
[hub]
localhost ansible_connection=local
[clients]
172.31.0.20 ansible_user=admin
172.31.2.234 ansible_user=ec2-user
[all:vars]
ansible_python_interpreter=/usr/bin/python3Before proceeding with the CFEngine installation, I find it useful to ping all the hosts to make sure they are all reachable.
ansible all -i ansible/inventory.ini -m ping
localhost | SUCCESS => {
"changed": false,
"ping": "pong"
}
172.31.0.20 | SUCCESS => {
"changed": false,
"ping": "pong"
}
172.31.2.234 | SUCCESS => {
"changed": false,
"ping": "pong"
}
To download and install the correct packages, as well as bootstrap all the hosts, there is a neat CFEngine Ansible Collection that can help us out (written by Vratislav Podzimek).
ansible-galaxy collection install cfengine.cfengine
Starting galaxy collection install process
Process install dependency map
Starting collection install process
Downloading https://galaxy.ansible.com/api/v3/plugin/ansible/content/published/collections/artifacts/cfengine-cfengine-1.1.0.tar.gz to /home/ubuntu/.ansible/tmp/ansible-local-2452gwlze5dl/tmpqsw5ntqd/cfengine-cfengine-1.1.0-cfdnq5iz
Installing 'cfengine.cfengine:1.1.0' to '/home/ubuntu/.ansible/collections/ansible_collections/cfengine/cfengine'
cfengine.cfengine:1.1.0 was installed successfully
Next we’ll need to write the actual playbook that does the work. It’s fairly
straightforward. One thing to watch out for, though, is that we need to make
sure we wait for
cf-serverd
on the hub to start listening on port 5308 before trying to bootstrap any of the
other hosts. This can easily be achieved with the
ansible.builtin.wait_for
module. In the playbook below, 172.31.10.16 is the hub’s own address. This is
the address the clients use to reach it.
- name: Bootstrap the CFEngine hub
hosts: hub
become: true
tasks:
- name: Install and bootstrap the hub
cfengine.cfengine.cfengine:
policy_server: 172.31.10.16
- name: Bootstrap the CFEngine clients
hosts: clients
become: true
tasks:
- name: Wait for cf-serverd on the hub
ansible.builtin.wait_for:
host: 172.31.10.16
port: 5308
timeout: 120
- name: Install and bootstrap the clients
cfengine.cfengine.cfengine:
policy_server: 172.31.10.16The only thing that remains now is to run the playbook.
ansible-playbook -i ansible/inventory.ini ansible/playbook.yaml
PLAY [Bootstrap the CFEngine hub] ********************************************************************************
TASK [Gathering Facts] *******************************************************************************************
ok: [localhost]
TASK [Install and bootstrap the hub] *****************************************************************************
changed: [localhost]
PLAY [Bootstrap the CFEngine clients] ****************************************************************************
TASK [Gathering Facts] *******************************************************************************************
ok: [172.31.0.20]
ok: [172.31.2.234]
TASK [Wait for cf-serverd on the hub] ****************************************************************************
ok: [172.31.0.20]
ok: [172.31.2.234]
TASK [Install and bootstrap the clients] *************************************************************************
changed: [172.31.2.234]
changed: [172.31.0.20]
PLAY RECAP *******************************************************************************************************
172.31.0.20 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
172.31.2.234 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
It all looks good. Let’s verify.
ansible all -i ansible/inventory.ini -b -m command -a "cf-agent -V"
localhost | CHANGED | rc=0 >>
CFEngine Core 3.27.1
CFEngine Enterprise 3.27.1
172.31.0.20 | CHANGED | rc=0 >>
CFEngine Core 3.27.1
CFEngine Enterprise 3.27.1
172.31.2.234 | CHANGED | rc=0 >>
CFEngine Core 3.27.1
CFEngine Enterprise 3.27.1
Notice that the output says Enterprise. That is what the collection installs by
default. CFEngine Enterprise is free for up to 25 hosts. If you’d rather run
Community, set the edition option:
...
- name: Install and bootstrap the clients
cfengine.cfengine.cfengine:
policy_server: 172.31.10.16
edition: community
...And that concludes this blog post. Where to go next? Check out Getting started with Ansible playbooks in CFEngine, which takes the easy route. Or check out Use Ansible playbooks in CFEngine policy with promise-type-ansible module for something more production ready.
If you have any questions about the CFEngine Ansible collection, let us know at GitHub Discussions. We are eager to hear from you.