# Infrastructure@Home: Configuration Management with Ansible
Tags: devops, ansible, hashicorp, raspberrypi
Published: 2020-04-19 08:18:20


In the 2nd article about Infrastructure@Home, I will show how to use the configuration management tool [Ansible](https://www.ansible.com/products/engine). We will see how to setup Ansible and how to execute a simple command that connects to all nodes.

_This article originally appeared in my [blog](https://admantium.com/blog/ic02_configuration_management_with_ansible/)._

## Ansible: Basic Concepts

Ansible is an open source configuration, deployment and provisioning tool. Invented in 2012 and battle tested at companies with very complex IT systems, it showed a scalable approach how infrastructure can be expressed as code.

Ansible has three essential requirements: It needs to be installed on one _control node_ from which ansible commands are executed, it needs an SSH access to its _managed nodes_, and pythons needs to be installed on all nodes. The managed nodes do not need any additional software, such as an agent.

An Ansible project defines the _inventory_, a list of managed nodes, and the _tasks_ which are to be executed on the nodes. Tasks rely on _modules_, wrapper for programs in the managed nodes, such as `apt` or `yum` for installing packages, `copy` or `template` for copying files, and `command` for executing shell commands.  Variables can be defined and used in the tasks or files. And finally, the _playbook_ is the ordered collection of all tasks that are executed against the nodes.

## Ansible: Basic Directory Layout

Before we can start to use Ansible, we need to:

* Install Ansible on the control machine
* Copy our public SSH key to all nodes (see [this guide](https://www.ssh.com/ssh/copy-id))

Then, we create a new folder and use the following simplified directory layout.

```bash
infrastructure/
  ansible.cfg
  hosts.ini
  connection_test.yml
```

As we see, there is a central configuration file _ansible.cfg_. This file defines the general behavior of task execution.  For now, we just need to define where our inventory comes from:

```ini
[defaults]
inventory=hosts
```

The _hosts_ file contains grouped entries of all nodes that we want to manage. We will use the hostnames for our machines. I differentiate into _raspis_ and _server_ to remind myself that different Linux distributions are running on these servers. The file looks as follows:

```ini
[raspis]
raspi-3-1
raspi-3-2
raspi-4-1
raspi-4-2
raspi-0

[server]
linux-workstation
```

Unfortunately, hostname lookup did not work, so I need to add explicit IP addresses. Be sure to give your devices static IP addresses with your router/dhcp server.

```ini
raspi-3-1 ansible_host=192.168.2.107
raspi-3-2 ansible_host=192.168.2.104
raspi-4-1 ansible_host=192.168.2.102
raspi-4-2 ansible_host=192.168.2.105
```

Also, on the server I use a different username to execute the ansible commands.

```ini
[server]
linux-workstation ansible_host=192.168.2.200 ansible_user=devcon
```

Now we have created the Ansible configuration file and our inventory. Keep in mind that we use a simplified directory structure: Ansible documents several [best practices](https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html#content-organization)  which are helpful for experienced users that need to share Ansible files with other colleagues. And also, the configuration file can contain other options that help to boost the task execution speed, like using SSH multiplexing, pipelining and executing tasks asynchronous.

## Test: Connect to all Nodes

With Ansible, you can execute [ad-hoc commands](https://ansible-tips-and-tricks.readthedocs.io/en/latest/ansible/commands/) on the nodes simply in the command line. Lets ping our nodes:

```bash
ansible -i hosts -m ping raspis
```

We see this. Output:

```bash
raspi-3-1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}
raspi-3-2 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}
…
```

This looks good, we can reach all machines. Now we will put this command into a task file and execute the task. Open the file _connection_test.yml_ and input this text:

```yaml
- name: Test if we can connect to all nodes
  hosts: all
  tasks:
    - name: Ping the node
      ping:
```

Let’s go through all of these lines:

* Line 1: A descriptive name for this playbook
* Line 2: A reference to the nodes to which this playbook is applied. We reference the group names that we defined in the _hosts.ini_ file
* Line 5: The tasks that we will execute on the node
  * Each task has a descriptive _name_ and it uses an Ansible module
  * We execute the command _ping_

Let’s execute the playbook:

```bash
ansible-playbook connect.yml —limit "raspi-3-1"
```

We will see the same output as before. If we only want to connect to one specific node, we can use this command:

```bash
ansible-playbook connect.yml —limit "raspi-3-1"
```

Now, let’s reorder the directory as follows:

```bash
infrastructure/
  ansible.cfg
  hosts.ini
  system/
    connection_test.yml
```

## Conclusion

This article shows how to setup Ansible.  The requirements are to install Ansible on the control machine and to have SSH access to all nodes. Then, we create a basic directory layout with an Ansible conjuration file, an inventory of all nodes, and the first playbook. We also learned how to execute Ad-Hoc commands via the command line and how to write a playbook that tests if we can connect to all nodes.