Ansible round 2, the awesome stuff

Ansible round 2

Last time we got a working Vagrant environment with 1 master node and 2 slave nodes.
We also made sure the VM’s could connect to each other, and Ansible was able to locate and ping the nodes.
This tutorial we’re going to do some Ansible commands on the fly to see some of the powers of Ansible.

Check out part 1 here: https://www.inpimation.com/ansible-vagrant-beginners-guide/

Eat, Sleep, Ansible, Repeat

To begin, login to your master node and become root.
The first command is to see if we can ping the nodes (just like we did last time, but to make sure everything is up and running)

ansible test -m ping

The output should be something like this:

root@master:/home/ubuntu# ansible test -m ping
10.0.0.11 | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}
10.0.0.12 | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}

Now that’s all great and all, but you still can’t read anything of your nodes. Let’s try to get the ip adresses, because some engineer requested it and we don’t want to manually login to both machines.

Run the following command:
ansible test -a "ip a"

The output should be:

root@master:/home/ubuntu# ansible test -a "ip a"
10.0.0.11 | SUCCESS | rc=0 >>
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 02:e5:38:c9:4b:72 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global enp0s3
       valid_lft forever preferred_lft forever
    inet6 fe80::e5:38ff:fec9:4b72/64 scope link 
       valid_lft forever preferred_lft forever
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:a3:5d:80 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.11/24 brd 10.0.0.255 scope global enp0s8
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fea3:5d80/64 scope link 
       valid_lft forever preferred_lft forever

10.0.0.12 | SUCCESS | rc=0 >>
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 02:e5:38:c9:4b:72 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global enp0s3
       valid_lft forever preferred_lft forever
    inet6 fe80::e5:38ff:fec9:4b72/64 scope link 
       valid_lft forever preferred_lft forever
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:88:bd:96 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.12/24 brd 10.0.0.255 scope global enp0s8
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe88:bd96/64 scope link 
       valid_lft forever preferred_lft forever

Okay cool! So now we can get information from multiple machine from our master host. Without having to manually login, that just saved us +-10 minutes!
But that’s not everything Ansible can do, it’s much more powerful.

Fun command to run is: ansible test -m setup test it with your current setup

More Ansible

Before we start the more advanced configuration it’s good to practice some more ad-hoc commands. Basically firing some commands from our master to our minions and see what happens.

Let’s copy a file from our master to our minions. Create a todo file in your tmp directory:
echo "Fix Ansible" > /tmp/todo.txt

Now let’s copy it to our minions using Ansible.

ansible test -m copy -a "src=/tmp/todo.txt dest=/home/ubuntu/todo.txt"

The output should be something like this:

root@master:/tmp# ansible test -m copy -a "src=/tmp/todo.txt dest=/home/ubuntu/todo.txt"
10.0.0.11 | SUCCESS => {
    "changed": true, 
    "checksum": "f43e9c7aef7b81e1141afbe365c0bcae643b1efe", 
    "dest": "/home/ubuntu/todo.txt", 
    "gid": 0, 
    "group": "root", 
    "md5sum": "aed3433f900729e3570c8fc5071d8232", 
    "mode": "0644", 
    "owner": "root", 
    "size": 12, 
    "src": "/root/.ansible/tmp/ansible-tmp-1495532142.55-163466538941129/source", 
    "state": "file", 
    "uid": 0
}
10.0.0.12 | SUCCESS => {
    "changed": true, 
    "checksum": "f43e9c7aef7b81e1141afbe365c0bcae643b1efe", 
    "dest": "/home/ubuntu/todo.txt", 
    "gid": 0, 
    "group": "root", 
    "md5sum": "aed3433f900729e3570c8fc5071d8232", 
    "mode": "0644", 
    "owner": "root", 
    "size": 12, 
    "src": "/root/.ansible/tmp/ansible-tmp-1495532142.55-43168954409322/source", 
    "state": "file", 
    "uid": 0
}

Wow! That was easy, we just copied our files to 2 machines running only 1 command. That saved us another +- 5 minutes.

But wait! Our user Ubuntu can’t edit the file because the owner is root. The user is sad cause he’s unable to finish his todo list. Let’s edit the permissions.
Run the following command on our master:

ansible test -m file -a "dest=/home/ubuntu/todo.txt mode=660 owner=ubuntu group=ubuntu"

The output should be something like this:

root@master:/tmp# ansible test -m file -a "dest=/home/ubuntu/todo.txt mode=660 owner=ubuntu group=ubuntu"
10.0.0.11 | SUCCESS => {
    "changed": true, 
    "gid": 1000, 
    "group": "ubuntu", 
    "mode": "0660", 
    "owner": "ubuntu", 
    "path": "/home/ubuntu/todo.txt", 
    "size": 12, 
    "state": "file", 
    "uid": 1000
}
10.0.0.12 | SUCCESS => {
    "changed": true, 
    "gid": 1000, 
    "group": "ubuntu", 
    "mode": "0660", 
    "owner": "ubuntu", 
    "path": "/home/ubuntu/todo.txt", 
    "size": 12, 
    "state": "file", 
    "uid": 1000
}

Now it’s time for the user to edit the file, since he’s done fixing Ansible right? Login to node1 and add -> done behind the text.

The file should now look something like this:

root@node1:~# cat /home/ubuntu/todo.txt 
Fix Ansible -> Done

Back to our master, because according to management the user isn’t ready yet! There is still a lot to do!
Let’s rerun the command:

test -m copy -a "src=/tmp/todo.txt dest=/home/ubuntu/todo.txt mode=660 owner=ubuntu group=ubuntu"

The output should be:

root@master:/tmp# ansible test -m copy -a "src=/tmp/todo.txt dest=/home/ubuntu/todo.txt mode=660 owner=ubuntu group=ubuntu"
10.0.0.11 | SUCCESS => {
    "changed": true, 
    "checksum": "f43e9c7aef7b81e1141afbe365c0bcae643b1efe", 
    "dest": "/home/ubuntu/todo.txt", 
    "gid": 1000, 
    "group": "ubuntu", 
    "md5sum": "aed3433f900729e3570c8fc5071d8232", 
    "mode": "0660", 
    "owner": "ubuntu", 
    "size": 12, 
    "src": "/root/.ansible/tmp/ansible-tmp-1495532780.2-52523839400815/source", 
    "state": "file", 
    "uid": 1000
}
10.0.0.12 | SUCCESS => {
    "changed": false, 
    "checksum": "f43e9c7aef7b81e1141afbe365c0bcae643b1efe", 
    "dest": "/home/ubuntu/todo.txt", 
    "gid": 1000, 
    "group": "ubuntu", 
    "mode": "0660", 
    "owner": "ubuntu", 
    "path": "/home/ubuntu/todo.txt", 
    "size": 12, 
    "state": "file", 
    "uid": 1000
}

We just fixed node1! Node2 was still in sync, so nothing was changed.
This is a key feature to keep in mind when provisioning with Ansible, any changes made to the minions will be overwritten by the master.

Next up, installing packages with Ansible.
Let’s say we want Apache2 installed on our test cluster, because the developers want a test server and production server.
Let’s run the following command:

ansible test -m apt -a "name=apache2 state=present"

This will ensure the package Apache2 is installed including dependencies.
The output is rather large, so I won’t paste it below. Just the part that’s interesting:

10.0.0.11 | SUCCESS => {
    "cache_update_time": 0, 
    "cache_updated": false, 
    "changed": true, 
    "stderr": "", 

As you can see, there are no errors in stderr, and stdout produced a lot of messages about what packages it’s installing etc.
Most important is that the state was successful.
Great! Now you know some of the basics of the Ansible tool.

You might have noticed that we’re using the -m addition. This stand for module, and is build in way to install packages for instance.
For a complete list, please see the official documentation: Module Index — Ansible Documentation

Please do note that this was a very basic introduction, and I didn’t cover everything there is to know.
If you’d like to know more, please visit the official documentation at: http://docs.ansible.com/ansible/intro_adhoc.html

No fun without Playbooks

Now it’s time to move on to the more advanced stuff: Playbooks.
What are playbooks? Playbooks are configuration for bootstrapping a set of servers with the power of YAML.

First, let’s create a simple playbook to install Apache2
Go to your /etc/ansible/ directory, and create a new directory called playbooks

mkdir playbooks

Now let’s go to the new directory and create our first playbook.
Open a new file named testbook in the directory with your editor, and write the following code:

---
- hosts: test
  remote_user: root
  tasks:
  - name: ensure apache is at the latest version
    apt: name=apache2 state=latest

The —- at the beginning is to define that it’s a YAML file

Next, let’s run our playbook and see what happens:

ansible-playbook testbook

The output should be something like this:

root@master:/etc/ansible/playbooks# ansible-playbook testbook 

PLAY ***************************************************************************

TASK [setup] *******************************************************************
ok: [10.0.0.12]
ok: [10.0.0.11]

TASK [ensure apache is at the latest version] **********************************
ok: [10.0.0.11]
ok: [10.0.0.12]

PLAY RECAP *********************************************************************
10.0.0.11                  : ok=2    changed=0    unreachable=0    failed=0   
10.0.0.12                  : ok=2    changed=0    unreachable=0    failed=0   


Now that’s weird right? In fact, it’s not.
We already installed apache2 earlier in the lesson, so this is a expected response.

So now let’s add a website to our /var/www/html/ directory.
First create our index.html:

echo "<html><h1>Hello Ansible</h1></html>" > /tmp/index.html

Now let’s tell our playbook to push the new file to our minions.
Open up the playbook and add the following lines:

- name: write the index file
    template: src=/tmp/index.html dest=/var/www/html/index.html

Now that’s not that hard, right?
But, it’s still possible for the system to fail, because Apache2 might not read the new file until a restart is initiated, or even worse Apache2 isn’t started at all. Luckily there is a fix, we can create handlers that do certain jobs after they have finished running. For instance, restart apache2 if the file is successfully placed. Let’s create a playbook like that.

- name: write the index file
    template: src=/tmp/index.html dest=/var/www/html/index.html
    notify:
    - restart apache

handlers:
    - name: restart apache
      service: name=apache2 state=restarted

Now you can see we created a Notify at the end of our placement. Later on in the script we create a handler to respond to that request.
We’re still missing the bit where we make sure Apache2 is running. Let’s take care of that:

---
- hosts: test
  remote_user: root
  tasks:
  - name: ensure apache is at the latest version
    apt: name=apache2 state=latest
  - name: write the index file
    template: src=/tmp/index.html dest=/var/www/html/index.html
    notify:
    - restart apache
  - name: ensure apache is running (and enable it at boot)
    service: name=apache2 state=started enabled=yes
  handlers:
    - name: restart apache
      service: name=apache2 state=restarted

This should be our finished playbook, in which me make sure that:
– Apache2 is installed
– Our own index.html is pushed
– Apache2 is running
– If the index.html is placed, Apache2 is restarted (only if the file is modified!)

Great! Now let’s test it out by running our playbook.

The output should be”

root@master:/etc/ansible/playbooks# ansible-playbook playbook 

PLAY ***************************************************************************

TASK [setup] *******************************************************************
ok: [10.0.0.11]
ok: [10.0.0.12]

TASK [ensure apache is at the latest version] **********************************
ok: [10.0.0.11]
ok: [10.0.0.12]

TASK [write the index file] ****************************************************
changed: [10.0.0.11]
changed: [10.0.0.12]

TASK [ensure apache is running (and enable it at boot)] ************************
ok: [10.0.0.12]
ok: [10.0.0.11]

RUNNING HANDLER [restart apache] ***********************************************
changed: [10.0.0.12]
changed: [10.0.0.11]

PLAY RECAP *********************************************************************
10.0.0.11                  : ok=5    changed=2    unreachable=0    failed=0   
10.0.0.12                  : ok=5    changed=2    unreachable=0    failed=0 

This means that if we go to our browser and open one of the IP’s, we should see the following:

What about the other host?

Congratulations you’ve just automated the deployment of 2 webservers using Ansible.

Next time we’re going to deploy users, deploy a custom resolv.conf file and add another Minion to our horde/farm/collection.

1 Comment

  1. Pingback: Ansible Vagrant beginners guide - inpimation.com

Leave a comment

Your email address will not be published. Required fields are marked *