Ansible Dev Ops

Ansible Tutorial for Beginners

Ansible is a configuration management and orchestration tool. It works as an IT automation engine.

Ansible can be run directly from the command line without setting up any configuration files. You only need to install Ansible on the control server or node. It communicates and performs the required tasks using SSH. No other installation is required. This is different from other orchestration tools like Chef and Puppet where you have to install software both on the control and client nodes.

Ansible uses configuration files called playbooks for a series of tasks. The playbooks are written in YAML syntax.

The open source product is maintained by Ansible Inc. It was first released in 2012. Red Hat acquired Ansible in 2015. Red Hat Ansible Engine and Red Hat Ansible Tower are commercial products.

Due to the ease of use, Ansible is rising in popularity as an IT automation tool.

Simple Project to Demonstrate Ansible Capabilities

Project Objectives

Let’s run through a simple project to see the capabilities of Ansible. For this project, we will simulate a simple web server setup. We will have the following components:

  • Control Node (control) – It is the node that will have Ansible installed and it will control the other nodes.
  • Load Balancer (lb01) – A nginx based load balancer will be installed on this node.
  • Web Server 1 and Server 2 (app01 and app02) – These nodes will have Apache installed with a simple hello world web page. The load balancer will alternate traffic between these two nodes.

We will first install Ansible on the control node. Then, we will use the control node to set up the load balancer and application nodes.

Prerequisites

In order to follow the tutorial, you will need 4 Ubuntu machines. You can use VMs on Vagrant or containers on Docker. You should be able to ssh from the control node to the rest of the boxes. Also, you need to open the necessary ports depending on your setup and you need to have /usr/bin/python pointing to Python2.6 or higher on all the machines.

Installing Ansible and passwordless SSH on Control Node

For our Ubuntu control machine, we are going to install Ansible with the following commands:

$ sudo apt-get update
$ sudo apt-get install software-properties-common
$ sudo apt-add-repository ppa:ansible/ansible
$ sudo apt-get update
$ sudo apt-get install ansible

Once you have Ansible installed, you can check using the following command:

$ ansible --version

ansible 2.3.2.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides
  python version = 2.7.12 (default, Nov 19 2016, 06:48:10) [GCC 5.4.0 20160609]

To access the lb01, app01, and app02, you can generate ssh key on control and copy it to the other machines. Example commands for setting ssh key:

$ ssh-keygen -t rsa
$ ssh ansible@app01 mkdir -p .ssh
$ cat .ssh/id_rsa.pub|ssh ansible@app01 'cat >> .ssh/authorized_keys'
$ ssh ansible@app01

The last line should allow you to log in from the control machine to the app01 machine without asking for a password. You should repeat the process for all the machines.

Creating Inventory

In Ansible, the inventory represents the machines that Ansible will manage. The list of machines in the inventory can be found out through the following command:

$ ansible --list-hosts all

It should show all the inventory machines. If you see a lot of output, you can go to /etc/ansible/hosts and comment out all the listed inventory. We want to start with a clean slate.

To create your inventory, make a folder (eg. ansiblework) on control and inside the folder create a file development.txt. From now, this folder is going to be our work area. Put the following text inside development.txt:

[controller]
control ansible_connection=local

[loadbalancer]
lb01 ansible_user=ansible

[webserver]
app01 ansible_user=ansible
app02 ansible_user=ansible

Now you can run the command:

$ ansible -i development.txt --list-hosts all
hosts (4):
    control
    lb01
    app01
    app02

However, we don’t want to point to the development.txt file every time. In the same directory, create a ansible.cfg file and enter the following:

[defaults]
inventory = ./development.txt

Now we can run:

$ ansible --list-hosts all
  hosts (4):
    control
    lb01
    app01
    app02

In the development.txt file, the bracketed names create groups and below that we see the servers. The flag ansible_connection=local tells Ansible that control machine is a local server, so ansible doesn’t need to ssh into it. The ansible_user=ansible is telling that ssh username is ansible (in your case it could be ansible_user=john).

You can now select particular groups. For examples,

$ ansible --list-hosts webserver
 hosts (2):
    app01
    app02

Congratulations! You have created your first Ansible inventory.

First Ansible Task

You can ping all of your inventory machines using the following command:

$ ansible -m ping all

control | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}
lb01 | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}
app02 | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}
app01 | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}

Success means the control machine is able to run the ping command on all the machines in the inventory.

If we want to run the “ls” command on all the machines, we can do it like this:

$ ansible -m command -a "ls" all

app02 | SUCCESS | rc=0 >>
a2.txt
f1.txt
test.txt

app01 | SUCCESS | rc=0 >>
a1.txt
f1.txt
test
test.txt
test2

control | SUCCESS | rc=0 >>
ansible.cfg
development.txt
playbooks

lb01 | SUCCESS | rc=0 >>

Now you are set up to run commands on your inventory machines.

Writing Playbooks

Ansible command line is great for executing a single task. But in playbooks are more useful for multiple tasks. Playbooks are text files written in the YAML format. Let’s take our list example above and create a playbook.

First, create a folder playbooks and create a list.yml inside it with the following text:

---
- hosts: all
tasks:
- name: list files in folder
command: ls

The three dashes at the part of the YAML formatting. You can learn more about YAML formatting here.

Now if you execute the following command:

$ ansible-playbook playbooks/list.yml

PLAY [all] ******************************************

TASK [Gathering Facts] ******************************
ok: [lb01]
ok: [app02]
ok: [app01]
ok: [control]

TASK [list files in folder] ************************
changed: [lb01]
changed: [app02]
changed: [app01]
changed: [control]

PLAY RECAP ****************************************
app01                      : ok=2    changed=1    unreachable=0    failed=0   
app02                      : ok=2    changed=1    unreachable=0    failed=0   
control                    : ok=2    changed=1    unreachable=0    failed=0   
lb01                       : ok=2    changed=1    unreachable=0    failed=0   

You have executed your first playbook.

Setting Up the Nodes

Load Balancer

Let’s setup the load balancer. Create a file loadbalancer.yml with the following text:

N

– hosts: loadbalancer
become: true
tasks:
– name: install nginx
apt: name=nginx state=present update_cache=yes

– name: start nginx
service: name=nginx state=started enabled=yes
[/cc]

The playbook with install nginx on the lb01 machine and then start nginx.

$ ansible-playbook playbooks/loadbalancer.yml

PLAY [loadbalancer] *************************************

TASK [Gathering Facts] **********************************
ok: [lb01]

TASK [install nginx] ************************************
changed: [lb01]

TASK [start nginx] **************************************
changed: [lb01]

PLAY RECAP **********************************************
lb01                       : ok=3    changed=2    unreachable=0    failed=0   

If you port 80 on the lb01 machine is open, then you should be able go to http://localhost and see the following on a web browser:

Welcome to nginx!
If you see this page, the nginx web server is successfully installed and working. Further configuration is required.
For online documentation and support please refer to nginx.org. 
Commercial support is available at nginx.com.
Thank you for using nginx.

Web Server
Now create a following webserver.yml in the playbook folder and enter the following code:

--
- hosts: webserver
become: true
tasks:
- name: install apache
apt: name=apache2 state=present update_cache=yes
- name: deleted index.html
file: path=/var/www/html/index.html state=absent
notify: restart apache2

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

- hosts: app01
become: true
tasks:
- name: set up index.html for first web server
copy: content="<html>

<header><title>Welcome to Server 1</title></header>

<body>Hello from Server 1!</body></html>" dest=/var/www/html/index.html mode=0644
notify: restart apache2

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

- hosts: app02
become: true
tasks:
- name: set up index.html for second web server
copy: content="<html>

<header><title>Welcome to Server 2</title></header>

<body>Hello from Server 2!</body></html>" dest=/var/www/html/index.html mode=0644
notify: restart apache2

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

In the above code, first apache2 is getting installed on both app01 and app02. Then the /var/www/html/index.html is deleted from both of the servers.

Next individually app01 and app02 is given separate index.html. The reason for the separate html is to make sure they are distinguishable. The handlers restart the apache2 server after every change.

You can use the following command to run the playbook

$ ansible-playbook playbooks/webserver.yml

PLAY [webserver] ******************************

TASK [Gathering Facts] ************************
ok: [app02]
ok: [app01]

TASK [install apache] *************************
ok: [app02]
ok: [app01]

TASK [deleted index.html] *********************
changed: [app02]
changed: [app01]

RUNNING HANDLER [restart apache2] *************
changed: [app02]
changed: [app01]

PLAY [app01] *********************************

TASK [Gathering Facts] ***********************
ok: [app01]

TASK [set up index.html for first web server] ****************************
changed: [app01]

RUNNING HANDLER [restart apache2] ***************************************
changed: [app01]

PLAY [app02] ************************************************************

TASK [Gathering Facts] **************************************************
ok: [app02]

TASK [set up index.html for second web server] **************************
changed: [app02]

RUNNING HANDLER [restart apache2] ***************************************
changed: [app02]

PLAY RECAP **************************************************************
app01                      : ok=7    changed=4    unreachable=0    failed=0   
app02                      : ok=7    changed=4    unreachable=0    failed=0   

Now both application servers should be running. You can use curl command to see if the servers are up.

$ curl app01
Welcome to Server 1

Hello from Server 1! $ curl app02

Welcome to Server 2

Hello from Server 2!

Running the Load Balancer

Inside playbook folder, create a templates folder with nginx.conf.j2 file. The file should have the following code:

upstream test {
{% for server in groups.webserver %}
server {{ server }};
{% endfor %}
}

server {
listen 80;

location / {
proxy_pass http://test;
}
}

Now update the loadbalancer.yml file with the following code:

---
- hosts: loadbalancer
become: true
tasks:
- name: install nginx
apt: name=nginx state=present update_cache=yes

- name: start nginx
service: name=nginx state=started enabled=yes

- name: configure nginx
template: src=templates/nginx.conf.j2 dest=/etc/nginx/sites-available/test mode=0644
notify: restart nginx

- name: delete old link
file: path=/etc/nginx/sites-enabled/default state=absent
notify: restart nginx

- name: activate test site
file: src=/etc/nginx/sites-available/test dest=/etc/nginx/sites-enabled/test state=link
notify: restart nginx

handlers:
- name: restart nginx
service: name=nginx state=restarted

The above code will copy the load balancer code to the lb01 server and then make it the default page for the nginx server. As a result, nginx will alternatively display app01 and app02 pages.

Run the load balancer playbook with the following command:

$ ansible-playbook playbooks/loadbalancer.yml

PLAY [loadbalancer] ***************************************************

TASK [Gathering Facts] ************************************************
ok: [lb01]

TASK [install nginx] **************************************************
ok: [lb01]

TASK [start nginx] ****************************************************
ok: [lb01]

TASK [configure nginx] ************************************************
ok: [lb01]

TASK [delete old link] ************************************************
ok: [lb01]

TASK [activate test site] *********************************************
ok: [lb01]

PLAY RECAP ************************************************************
lb01                       : ok=6    changed=0    unreachable=0    failed=0 

Now you should be able to connect to http://localhost and every time you reload the page the message should alternate between “Hello from Server 1!” and “Hello from Server 2!”.

Conclusion

In this project, we started with 4 Ubuntu servers. On the control machine, we set up Ansible. Then from the control machine, we installed various components on load balancer node lb01 and the two web servers app01 and app02. From a single node, we were able to manage 3 nodes lb01, app01 and app02. We can use similar ideas to manage a large number of servers.

Advanced Topics

Roles and Ansible Galaxy — Roles can be used with Ansible Galaxy for better reuse of configurations. Roles allow ansible code to be organized in multiple nested folders to make the code scalable.  LinuxHint tutorial on Ansible Roles can be found here.  Ansible Galaxy website allows users to share roles with each other.

References:

About the author

Zak H

Zak H

Zak H. lives in Los Angeles. He enjoys the California sunshine and loves working in emerging technologies and writing about Linux and DevOps topics.