Getting started with Ansible – first playbook

Ansible has stolen the hearts of many Ops people

Quite recently, during an AWS workshop, one of the topics was AWS OpsWorks. OpsWorks is actually a service that provides managed instances of Chef or Puppet. AWS admins and DevOps have been asked: “How do you automate configuration changes in your AWS environments?”. And, majority answered: not OpsWorks, Chef, Puppet or shell scripts – but Ansible.

It is obvious – Ansible has stolen hearts of many Ops people, so obviously it caught my attention as well. And as always the question is “how to get started with”… and this time it is Ansible.

A loop for ssh

Ansible is quite often called “a loop for ssh”. It is a bit an oversimplification, however – yes it allows you to loop over your multiple hosts (physical or virtual) and apply changes. And the major difference between Ansible and plain scripts is that in most cases you “declare” what the end result should look like rather giving exact commands what needs to be done to achieve certain state. So this time it is not “script” – it is a “playbook”. Playbook is a list of plays which consist of tasks that do something.

Shell scripts quite commonly required additional commands to check if something is in a particular state and for the change itself. Playbooks actually have a list of tasks, where task declare for instance that I want to have a package called httpd (task no. 1) and once I have it I want its corresponding service to be running (task no. 2). I don’t have to check if it is installed, updated or it is already running – Ansible will do that for me. This means that if I rerun this playbook later it will know that some actions have to be executed (for instance service start if it is not running), while others are not necessary (i.e. http already installed).

Ansible provides many modules that do what you need to do, i.e. install packages with yum or making sure that you have config file where you need it. And you’re also able to execute commands directly if you couldn’t find module that fits your needs.

So let’s start. We can install Ansible with commands as simple as brew install ansible (Mac) or yum install ansible (RHEL/CentOS). Just refer to the specific guide:

https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html

Commands in this guide were executed on the macOS, however, apart from installation process (which is slightly different), rest of the commands should be identical.

Your local machine will be your Ansible controller host, from which you’re going to run your playbooks. I could execute playbooks locally, but let’s make it a more realistic example – let’s assume that I have a remote host – in my case it is a local VM (but from Ansible/SSH perspective it is remote). First we need to have a host inventory – default path is /etc/ansible/hosts, but let’s create our separate file that looks like this:

192.168.155.193

where the above-mentioned IP happens to be IP of our small test VM with CentOS minimal installed and just replace bob with user you want to use to access the host. We should save this file in $HOME/.ansible/hosts (in my case it is /Users/marcinkubacki/.ansible/hosts). Now we need to set default location of our hosts inventory file, so that we don’t have to provide it every time we run playbook. Create a file $HOME/.ansible.cfg (in my case /Users/marcinkubacki/.ansible.cfg) with the following contents:

[defaults]
inventory = /Users/marcinkubacki/.ansible/hosts
remote_user = bob

where path to the inventory should be the one you used for hosts file, and bob will be the remote user that we login as over SSH when running a playbook. Now we can ask ansible to list hosts in the inventory:

$ ansible all --list-hosts
 hosts (1):
  192.168.155.193

Now you may we need an ssh key-pair to enable password-less authentication over ssh. If you need to generate you ssh key-pair – just run ssh-keygen and use ENTER to use defaults everywhere. Ansible will use this key enable ssh access with keys. Let’s assume that public key is in the default location $HOME/.ssh/id_rsa.pub as it is has just been generated with defaults. Assuming that we want to use user bob remotely – we can copy public key with the following command:

$ ssh-copy-id -i ~/.ssh/id_rsa.pub bob@192.168.155.193
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/Users/marcinkubacki/.ssh/id_rsa.pub"
/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
bob@192.168.155.193's password:
Number of key(s) added:        1

Now try logging into the machine, with: ssh 'bob@192.168.155.193 and check to make sure that only the key(s) you wanted were added. And the then we should be able to login without any password prompt.

$ ssh bob@192.168.155.193
[bob@localhost ~]$

Exit the remote SSH session and let’s try to check if Ansible is able to connect over SSH:

$ ansible all -m ping
192.168.155.193 | SUCCESS => {
   "changed": false,
   "ping": "pong"
}

The above-mentioned command asks Ansible to execute ping module on all hosts in the inventory. As our inventory has only one host – we have single result being returned. Ping module actually connects to SSH – it is not an ICMP request like in ping command. The all argument actually can be replaced with host pattern, i.e. just a name of a group of hosts in my inventory. Let’s assume that my test VM should belong to a group called web – we need to change our repository a bit to make it look like this:

[web]
192.168.155.193

Just added [web] before my host. In order to invoke an echo command on all of your web servers you just need to provide web as host pattern argument and use command module like this:

$ ansible web -m command -a "echo Hello" 192.168.155.193 | CHANGED | rc=0 >> Hello

So far so good. Now it is time to have our first playbook – let’s imagine that we have a classic example case: we need to install Apache HTTP server, make sure it is started and open port 80 on our firewall. Notice that our bob user also needs to be able to do it, so to keep it simple let’s added him to the wheel group with gpasswd -a bob wheel command on our remote machine.

Now – the playbook itself – save the following contents in your working directory as httpd.yaml (on your control machine, not the remote one):

- name: This playbook installs httpd webserver, starts it and opens port 80 on firewall
 hosts: web
 become: yes
 tasks:
 - name: Install Apache
   yum:
     name: httpd
     state: present
 - name: Make sure httpd service is running
   service:
     name: httpd
     state: started
 - name: Open port 80 on firewall and make this rule permanent
   firewalld:
     service: http
     permanent: true
     immediate: yes
     state: enabled

As you can see playbook is written in YAML language. It is almost self-explanatory, but let me comment on this. This playbook actually has a single play, which is supposed to be run on web host group (hosts variable). Flag become is used to tell Ansible that it needs to use sudo to run the tasks. We see 3 steps, first we use yum module and ask it to install (if it hasn’t been done earlier) httpd package. Then make sure that service is started (again, if it is it won’t have to be started). And finally we open http port on the firewall. Note, that we need to ask firewalld to make the rule permanent and immediate.

Now we can run this playbook as user bob, and provide password for sudo (-K switch)

$ ansible-playbook -K httpd.yaml
SUDO password:
PLAY [This playbook installs httpd webserver, starts it and opens port 80 on firewall] **********
TASK [Gathering Facts] **********
ok: [192.168.155.193]
TASK [Install Apache] **********
changed: [192.168.155.193]
TASK [Make sure httpd service is running] **********
changed: [192.168.155.193]
TASK [Open port 80 on firewall and make this rule permanent] **********
changed: [192.168.155.193]
PLAY RECAP **********
192.168.155.193   : ok=4 changed=3 unreachable=0    failed=0

If you point your browser to http://192.168.155.193 (replace IP accordingly) then you should see a default Apache page:

The thing about descriptive approach of the change is that when you invoke the playbook for the second time you’ll see that Ansible noticed that currently service is installed, started and firewall rules are present, so tasks return ok instead of changed:

$ ansible-playbook -K httpd.yaml
SUDO password:
PLAY [This playbook installs httpd webserver, starts it and opens port 80 on firewall] **********
TASK [Gathering Facts] **********
ok: [192.168.155.193]
TASK [Install Apache] **********
ok: [192.168.155.193]
TASK [Make sure httpd service is running] **********
ok: [192.168.155.193]
TASK [Open port 80 on firewall and make this rule permanent] **********
ok: [192.168.155.193]
PLAY RECAP **********
192.168.155.193   : ok=4 changed=0 unreachable=0    failed=0

To sum up

We have successfully setup our Ansible controller host, added test host to our inventory and executed our first playbook which installed Apache HTTP server. Ansible provides many more sophisticated mechanisms such as roles, variable handling, loops (like with_items clause) to execute task multiple times with different arguments and many more. One of the very interesting place that you really need to visit is Ansible Galaxy website: https://galaxy.ansible.com

It is a huge repository of all kinds of ready playbooks, so you even don’t have to figure out how to create your own playbook, maybe you can use an existing one. A very “getting started” video course is provided by Red Hat itself: https://www.redhat.com/rhtapps/promo-do007. I hope that you found this short guide useful – stay tuned for future posts!

0 Comments

Leave a Reply

avatar