Whether keeping your computer ready for a last-minute format (who hasn’t?) or keeping your server free from risks of blackouts and data corruption, Ansible brings an easy and fun way to deal with these issues in an automated manner.
An automation system for configuration management, provisioning, and application deployment, Ansible makes it easy with just one command to reconfigure your server or your personal computer the way you want using commands defined in yaml files. Ansible takes away the fear of losing that obscure configuration you made to fix that problem you don’t even remember anymore. You just need to know how to keep our playbooks (see more below) updated!
Installing Ansible
To install Ansible simply run:
python3 -m pip install --user ansibleFor a more complete guide, access the documentation
You can confirm your installation by running:
ansible --versionCreating Your inventory.yaml
The inventory.yaml file will be the file where we’ll define the hosts we want to configure. In it, it’s possible to define the IP, user, connection type, and other configurations.
In this article we’ll simulate automated configuration with Ansible on a Fedora Server virtual machine that we created in the article Creating a Fedora Server 36 Virtual Machine, so be sure to check it out!
To start, create a file called inventory.yaml and inside it define:
vms:
hosts:
fedora:
ansible_host: 192.168.122.143
ansible_connection: ssh
ansible_user: youruseransible_host will define the IP of the machine you want to configure and ansible_user the user. If you’re doing this process to be executed on your own computer, change it to localhost.
After that, run the command below to list all hosts and verify that our new host was found by Ansible:
[user@localhost ~]# ansible -i inventory.yaml all --list-hosts
hosts (1):
fedoraWith the remote host configured, we now need to configure SSH access. To do this, add your computer’s public key to the authorized_keys of each remote system you want to configure, in this case our virtual machine represented by the IP 192.168.122.143:
[user@localhost]# ssh-copy-id -i ~/.ssh/id_rsa.pub [email protected]
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/youruser/.ssh/id_rsa.pub"
The authenticity of host '192.168.122.143 (192.168.122.143)' can't be established.
ED25519 key fingerprint is ...
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
[email protected]'s password:
bash: warning: setlocale: LC_ALL: cannot change locale (pt_BR.UTF-8)
/usr/bin/sh: warning: setlocale: LC_ALL: cannot change locale (pt_BR.UTF-8)
/usr/bin/sh: warning: setlocale: LC_ALL: cannot change locale (pt_BR.UTF-8)
/usr/bin/sh: warning: setlocale: LC_ALL: cannot change locale (pt_BR.UTF-8)
Number of key(s) added: 1
Now try logging into the machine, with: "ssh '[email protected]'"
and check to make sure that only the key(s) you wanted were added.youruserWith the SSH public key configured, we can ask Ansible to ping our remote machine and verify that everything went well. If it did, you’ll see a message like the one below:
[user@localhost]# ansible -i inventory.yaml vms -m ping
fedora | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}Creating Your Playbook
Playbooks are files that Ansible will use to configure and deploy your remote hosts. It’s in these files that we’ll define what we want and how we want to configure our remote machine.
Create a new file called playbook.yaml with the following content:
- hosts: vms
become: true
tasks:
- name: Update packages with DNF
package:
name: '*'
state: latest
- name: Message
ansible.builtin.debug:
msg: Packages were updated!Then, run the playbook with the following command. Note that we’re passing the --ask-become-pass flag because we’re running a command that needs elevated permissions. This way Ansible will ask for the password before running the command.
[user@localhost]# ansible-playbook -i inventory.yaml --ask-become-pass playbook.yaml
BECOME password:
PLAY [vms] ***********************************************************************************************
TASK [Gathering Facts] ***********************************************************************************
ok: [fedora]
TASK [DNF Update] ****************************************************************************************
ok: [fedora]
TASK [Print message] *************************************************************************************
ok: [fedora] => {
"msg": "Done!"
}
PLAY RECAP ***********************************************************************************************
fedora : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0With the output we can note that our playbook was executed successfully and Ansible brings us some useful information about the execution.
Working with Variables
We also have the flexibility to work with variables, where we can define different values for different remote hosts. Variables can be defined in playbooks or in the inventory itself, however, depending on where they’re defined, their scope can be different.
Defining Variables in the Inventory
To define a variable inside an inventory and make it available to all your playbooks, simply edit our inventory file inventory.yaml and add:
vms:
hosts:
fedora:
ansible_host: 192.168.122.143
ansible_connection: ssh
ansible_user: youruser
remote_install_path: /usr/binAnd to use them in your playbook, simply reference them like this:
- hosts: vms
become: true
tasks:
- name: Copy installation script
ansible.builtin.copy:
src: /home/user/Downlodas/my-script.sh
dest: '/my-script.sh'Defining List Type Variables
We can also define variables in list form:
vms:
hosts:
fedora:
ansible_host: 192.168.122.143
ansible_connection: ssh
ansible_user: youruser
folders:
- /usr/bin
- /usr/local/binAnd to use them:
folder: ""Return Variables
Ansible offers a very interesting way to register variables, which is by creating them through the output of other commands and using them later, for example:
- hosts: vms
become: true
tasks:
- name: Check architecture
ansible.builtin.shell: uname -m
register: host_arch
- name: Display architecture
ansible.builtin.debug:
msg: ''
- name: Display error if not supported
ansible.builtin.debug:
msg: No support for aarch64
when: host_arch.stdout == 'aarch64' It’s important to note that variables loaded by Ansible have an order of precedence. To know what this order is, access Understanding variable precedence in the official documentation.
Playbook Organization
As we saw above, we can define a single playbook and all its tasks inside it. But what if we have many tasks that we want to keep separate and organized by context? In this case we can import tasks from other files and define them separately:
- hosts: vms
become: true
tasks:
- name: Configure DNF
ansible.builtin.import_tasks: tasks/configure_dnf.yaml
- name: Install basic packages
ansible.builtin.import_tasks: tasks/install_base_packages.yaml
- name: Configure docker
ansible.builtin.import_tasks: tasks/configure_docker.yamlAnd your tasks file, for example tasks/configure_dnf.yaml, would look like this:
- name: Configure DNF for better performance
ansible.builtin.blockinfile:
backup: yes
path: /etc/dnf/dnf.conf
block: |
fastestmirror=true
max_parallel_downloads=20Or we can also import other playbooks:
- hosts: vms
- import_playbook: webservers.yaml
- import_playbook: databases.yml
- import_playbook: containers.ymlModules
Ansible comes with some modules (many, actually) to facilitate provisioning and configuration management of your remote hosts. Some of them follow below so you can get an idea of what’s possible to accomplish:
- ansible.builtin.copy: Copy local or remote host files to some location on the remote host;
- ansible.builtin.find: Returns a list of files using a criterion;
- ansible.builtin.cron: Manages crons and environment variables;
- ansible.builtin.dnf: Manages packages with the
dnfpackage manager; - ansible.builtin.git: Deploys software using git checkout;
- ansible.builtin.pause: Pauses playbook execution;
- ansible.builtin.lineinfile: Deals with lines in files, changes individual lines in files.
To see the complete list of modules that come integrated with Ansible, click here.
A Complete Solution
As we can see above in a very basic way, Ansible has a lot to offer, it’s a complete tool to deploy servers, configure them, and keep that homelab of yours safe and always ready for a new setup if something happens.
I recommend reading the documentation which is quite complete and also this article by Martin Fowler titled SnowflakeServer.