Using Ansible to automate local tasks and setup Jun 19 2019
We can use an automation tool like Ansible to configure servers, set up CI/CD pipelines, and many more DevOpsy tasks.
This same tool can be used to maintain the setup of our day to day environment (as suggested by my friend Gerardo Santoveña) or automate tasks. We can create
playbooks that contain the instructions for completing tasks or defining the state you want your environment to have. In this post, I'll show you Ansible's basics. By the end of the article, you'll have enough knowledge to start automating some of your everyday tasks or maybe even create a playbook to set up your environment.
Table of Contents
Let's go through some basic concepts that will help us better understand the Ansible workflow.
Ansible uses an
inventory file to keep track of the hosts that you want to manage so they can be easily referenced by name in your ansible
Playbooks are, in simple terms, files that describe the hosts, the tasks to execute on the hosts and some other properties.
If you want to work with remote servers, you add them in your
inventory file, located by default in
/etc/ansible/hosts. We are not going to work with remote servers on our examples to keep things simple, but everything applies the same to remote hosts, you just need to reference the remote servers instead of using
localhost. Because we won't define any remote servers, we don't need to add anything to our
inventory. But for the sake of completeness, if you want to have a glimpse of what information is contained on the inventory file, let's have a look.
The default location for our inventory file is:
/etc/ansible/hosts if it's not created you can create it. The inventory contains sever-names, group definitions, ports and variables. The inventory file can be in INI format or in YML. Let's look at an inventory file in
ini format :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #makes Ansible aware of the server mail.test.com mail.test.com # makes Ansible aware of db.test.com that uses the non-standard port for SSH 49001 db.test.com:49001 # we can create an alias for an IP, ansible_port is not required if we use the default ssh port 22 but just added it as an example loadbalancer ansible_host:10.0.10.1 ansible_port:22 #make a group of all server named infrastructure [infrastructure] mail.test.com db.test.com loadbalancer # Create a group of two servers that run the main app [app] 10.0.10.2 PGPASSWORD=test123 # don't use a password like that 10.0.10.3 PGPASSWORD=dUV7$g9%PdTTLnkFo!I
Now you can reference those hosts on your Playbooks or via the
ansible command-line tool. If you want to learn more visit the official documentation working with inventory.
Run commands on local connection
Normally, we would want to manage remote hosts, Ansible uses ssh for this. But we want to work on our local computer, to do this we can directly execute commands passing the flag
-c local to the
ansible command. First, let's see the command to obtain the disk space of a remote computer called
remote_server, it would look something like this:
1 $ ansible remote_server -a "df -h"
Because we will be working on our local computer, as I mentioned before, we will use a local connection, and the command will look like the following:
1 $ ansible localhost -c local -a "df -h"
-c flag with the argument
local makes it run in a local connection. There are other types of connections(for ssh:
docker) but we'll use
local for the current example. After running the command, we should see something similar to the following:
1 2 3 4 5 6 7 8 localhost | CHANGED | rc=0 >> Filesystem Size Used Avail Capacity iused ifree %iused Mounted on /dev/disk1s1 932Gi 550Gi 374Gi 60% 3841305 9223372036850934502 0% / devfs 202Ki 202Ki 0Bi 100% 698 0 100% /dev /dev/disk1s4 932Gi 7.0Gi 374Gi 2% 8 9223372036854775799 0% /private/var/vm map -hosts 0Bi 0Bi 0Bi 100% 0 0 100% /net map auto_home 0Bi 0Bi 0Bi 100% 0 0 100% /home /dev/disk1s3 932Gi 972Mi 374Gi 1% 42 9223372036854775765 0% /Volumes/Recovery
What we've seen is useful to run commands manually, but we don't want to have to run each command manually. What we want is to create a
playbook with all the tasks listed so they can be run and rerun at any time. The benefit of using Ansible is that commands are idempotent, this means that we can re-run commands and if the effect we desire is already there nothing will change, this is an improvement over creating our own scripts that will probably have unforeseen consequences when we run them multiple times.
Let's create a simple
playbook. Ansible playbooks are written in YML an easy to read data serialization format. Let's start by creating a directory to have all our files:
1 $ mkdir local_setup
Now let's create our playbook, name it
main.yml, it will contain the following lines:
1 2 3 4 5 6 7 8 9 10 --- # main.yml - hosts: localhost connection: local tasks: - name: Check disk space command: df -h register: df_result - debug: var=df_result.stdout_lines
playbook we define the task we want Ansible to run on the
localhost. The only strange part is the last two lines, they are there only to capture the output of the
df command so we can see it on screen. Usually, this is not necessary we just want to run a command and Ansible will notify us of the status of the command's execution.
Let’s run the command:
1 $ ansible-playbook main.yml
You should see the output of the
df -h command on your screen along with the additional information from Ansible.
Modules are packages that allow Ansible to perform tasks on the hosts. We could have a module that interacts with a specific service and abstracts all the commands and more important most modules strive to make the tasks idempotent. This means that no matter how many times a task is executed it will always produce the same effect, that is, the module has the logic to avoid making changes if the module verifies that the current state is the same as the desired final state.
Modules can be developed on any programming language it doesn't have to be in python, the only expectation is that they return JSON objects. To see a list of available Ansible modules click here to visit the official list, and to learn more about modules see the official documentation.
I'll show an example of how to use the module
command, you can check all the parameter it accepts in the command module documentation
1 $ ansible localhost -c local -m command -a "chdir=/Users ls -l"
That command will change directories to
/Users and list all the content of that directory. Let's see the same example using a playbook:
1 2 3 4 5 6 7 8 9 10 11 --- - hosts: localhost connection: local tasks: - name: List users on the system. command: ls -l register: user_list args: chdir: /Users - debug: var=user_list.stdout_lines
Package manager modules
Depending on your Operating System, you'll have access to a package manager that you can use to install libraries, binaries, and in some cases applications. I'll give a brief example of how to use Homebrew a package manager for macOS that has an Ansible module that we can use to install packages on our system.
Let's assume you have a simple setup, you want to make sure you have
tmux on your computer. The following
playbook that uses the Homebrew module will help you accomplish this:
1 2 3 4 5 6 7 8 9 10 #my dev setup --- - hosts: localhost connection: local tasks: - name: Make sure my dev tools are installed homebrew: name: htop,tmux state: present
That is a trivial example, but it opens new possibilities. Now you can create a playbook that contains all the tools you need to start working on a machine, and every time you change computers and want to make sure you have all the tools, you only need to run your playbook. You will probably find a package manager module for your specific Operating System, read the documentation it will be full of examples that can help you automate your setup.
If we are only dealing with trivial configuration or automation, a single playbook is enough, but when you want to combine tasks in playbooks and take advantage of playbooks other people have created, you'll end up using roles. Let's look at Ansible Roles.
Roles, in simple terms, are packaged playbooks that can be included on other playbooks, this allows you to avoid repeating code. You can reuse your roles and roles from other people using Ansible Galaxy. Ansible Galaxy is an online repository where people can publish their
roles, it comes with a command line tool to interact with the online repository.
The filesystem structure of a role looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ├── rderik.my_role │ ├── README.md │ ├── defaults │ │ └── main.yml │ ├── files │ ├── handlers │ │ └── main.yml │ ├── meta │ │ └── main.yml │ ├── tasks │ │ └── main.yml │ ├── templates │ ├── tests │ │ ├── inventory │ │ └── test.yml │ └── vars │ └── main.yml
Not all of the files and directories are needed, if you are going to deploy your roles to Ansible-Galaxy, you'll need
meta as the minimum structure. But because we're working only on local, we can begin with just the essential role structure, like the following:
1 2 3 rderik.my_role └── tasks └── main.yml
Let's see an example. First, create a
roles directory that will contain all our roles. When naming a role we generally follow the following format:
galaxy-username.role-name. Following that convention, our role will be named:
rderik.uptime. Let's create the directory:
1 $ mkdir -p roles/rderik.uptime/tasks`
mkdir creates the directory structure if it doesn't exist. In the same command, we are telling it to create
roles, because none of those exists
mkdir will create them all.
Now let's edit our
rderik.uptime main task:
1 2 3 4 5 # rderik.uptime/tasks/main.yml - name: Check system uptime command: uptime register: uptime_output - debug: var=uptime_output.stdout_lines
Now to include that role in our ansible playbook, we just add it to the
main.yml file that we were working on at the beginning. This is how the content of
main.yml should look:
1 2 3 4 5 6 7 8 9 10 11 12 --- # main.yml - hosts: localhost connection: local roles: - rderik.uptime tasks: - name: Check disk space command: df -h register: df_results - debug: var=df_results.stdout_lines
You can see, we added a new section
roles that contains the role we wish to include. Now we can run the command:
1 $ ansible-playbook main.yml
You should see the output of both tasks.
With these examples, we know enough of Ansible to use it to automate our local setup or any server you manage.
This was a 10,000 feet view of Ansible so you can get started. With this information and examples, you can now continue exploring in more depth any area of your interest. I would recommend setting up some VMs using Vagrant or maybe spawn some containers with Docker and play with them, first gathering facts then changing configurations.
Hope this is useful, and if you have any questions, send them my way.
Related topics/notes of interest
- To have a better understaing of how to use Ansible check the following book: Ansible for DevOps - by Jeff Geerling
- Homebrew module to install libraries, and utilities on macOS.
- Homebrew cask module, you can also use
brewto install apps on macOS via
brew cask, so have a look at the module documentation.
- Very interesting Mac Appstore Role by Jeff Geerling