Ansible 101

All the ansible code in this article can be found here https://gitlab.com/smartwavesa/open-source/ansible

 

Ansible

So what is Ansible?

If you’re working in the DevOps or in IT Operations area you may probably already have heard about Ansible, or you are already even using it. But If you don’t exactly know what it is, and what it’s used for, let’s try to have an overview of Ansible with some basic use cases.

Ansible is an Open Source automation tool best known for its simplicity and its low learning curve. It has been acquired by RedHat in 2015 and is since then heavily supported by them.

Ansible covers three kind of area:

  1. Provisioning, by allowing managing Servers (VMs) in your Infrastructure on-premise or in the Cloud.

  2. Configuration Management, by allowing to update or upgrade Operating Systems, configure applications software, or even use it for network automation by configuring network appliances.

  3. Deployment Tool, by being part of your software factory, by allowing to deploy and install your own applications on various servers.

Ansible can target Unix-like or Windows systems, but at that time of writing, Ansible can only be run from a Linux or MacOS instance, also called a control machine, or a control node.

In order to be able to try the lab part in this article, you would need at least a Linux or MacOS workstation.

Terraform

You’re already maybe wondering at this stage, why use Ansible and not Terraform that has a great hype since a few years now. What are the differences or what is the main purpose of those two great tools?

As described on the Terraform website “Terraform enables you to safely and predictably create, change, and improve infrastructure. It is an open source tool that codifies APIs into declarative configuration files that can be shared amongst team members, treated as code, edited, reviewed, and versioned.”

In other words, the main purpose of Terraform is to manage the lifecycle of your infrastructure by holding a state of it and allows to plan safely changes to your infrastructure. The main mission of Terraform is not to configure applications or services, although it can do it, but is mostly used to describe a repeatable and shareable infrastructure as code.

You can almost do the same as Terraform with Ansible, but not out of the box, and there is no mechanism such as state file that allows to know at any time the shape of your infrastructure. You would have to implement your own mechanism that mimics Terraform. The main area that Ansible is nowadays used is Configuration Management, and therefore Terraform and Ansible can be a great duo to consider in order to cover your needs. Let’s close this small Terraform aside and dive into your first steps with Ansible.

How does Ansible works?

Ansible is a small application written in Python, that can connect to one or thousands target hosts without the need to have any agent installed on the target hosts and without the need of a central server. Therefore it greatly simplifies its usage and minimizes the global footprint in your VMs.

The target hosts are managed by Ansible with the help of various communication protocols such as SSH for Unix-like systems and WinRM2 for Windows systems.

Playbook

In order to describe a configuration or change of a system, Ansible uses a Playbook written in YAML. This document defines multiple parts to define a state of your target hosts and can be seen as an instruction manual, written in a clear human readable format. Not to mention that those playbooks are best kept in a source control like git, to keep track of changes, and also to use it as a state of your infrastructure.

Example of a simple playbook that ensures latest nginx server is installed.

---

- hosts: webservers
vars:
http_port: 80
tasks:
- name: ensure nginx is at the latest version
apt:
name: nginx
state: latest
- name: write the apache config file
template:
src: templates/nginx/default.j2
dest: /etc/nginx/sites-available/default
notify:
- restart nginx
- name: ensure nginx is running
service:
name: nginx
state: started
handlers:
- name: restart nginx
service:
name: nginx
state: restarted

Let’s go through each part of this example playbook:

  • Hosts, is the starting point of your playbook, it is how you instruct Ansible on which target hosts this playbook should be applied. The definition of those hosts is defined in a specific inventory file in your Ansible project structure and will be covered later in this article.

  • Vars, is the definition of variables you can use along your playbook. They can either be defined directly in your playbook, passed as arguments when executing a playbook, or defined in your inventory hosts or group files. More info about variable precedence here

  • Tasks, are the different steps that will get sequentially executed on your target hosts in the order they are written.

  • Modules, are like tasks plugins or library plugins that consists of small portion of code that performs some specific tasks. You can see that as a kind of helpers for recurrent tasks, they are reusable and standalone scripts that Ansible will execute either locally or remotely.

There are multiple modules shipped in the core of Ansible, like “apt” to handle apt installation or updates, “service” to interact with services on your target hosts, “template” to copy a template to the target host and render it to a file by interpolating some variables at runtime that are defined in the template. You can of course also write your own modules in any programming language such as Python or Ruby. See list of core modules here.

  • Handlers, allows to declare tasks that can be executed with the help of some notification (“notify”). In the example playbook above, the handler “restart nginx” will be executed when the template finishes to be rendered on the target host.

Inventory

An inventory, is a file that describes a list of your target instances. This file can be in a YAML or INI format. They can be grouped logically according to your infrastructure and be easily reached by an Ansible playbook by referencing the host name of a target instance defined in the inventory or a whole group.

Yaml Example :

myhost.intra.com

[webservers]
ws1.internal.com
ws2.internal.com

[databases]
db1.internal.com
db2.internal.com
db3.internal.com

In your playbook, in the “hosts” part, you will than be able to use “webservers” or “databases” or just one host value like db1.internal.com or ws2.internal.com

This list can be statically defined or dynamically by using dynamic inventories scripts like the Amazon Web Services (AWS) EC2 script that allows to build an inventory of every of your EC2 instances.

You can find more details about the definition of your inventory here and dynamic inventory here.

Roles

Ansible roles are a way to group specific tasks together and maximize reusability of tasks in playbooks.

If we reuse the example playbook above, with the use of an nginx role that we would create it could look like that:

--- 

- hosts: webservers
vars:
http_port: 80
roles:
- nginx

The roles are located in a specific folder in the Ansible project, commonly named “roles” with following structure:

playbook.yml
roles/
nginx/
tasks/
handlers/
files/
templates/
vars/
defaults/
meta/
 

Your role needs at least one sub directory to be valid, and you can omit any other directories that you do not want. Each sub directories must contain at least one main.yml file. Usually you will have at least the “tasks” subdirectory.

Sub directories definitions:

  • tasks, contains the main list of tasks to be executed by the role.
  • handlers, contains handlers which may be used by this role or even anywhere outside this role.
  • defaults, default variables for the role.
  • vars, other variables for the role.
  • files, contains static files which can be deployed via this role.
  • templates, contains templates which can be deployed via this role.
  • meta, defines some meta data for this role.

Run a Playbook

In order to run a playbook, you need to use the “ansible-playbook” command with some options, and the one or more playbook that will be executed sequentially.

ansible-playbook [options] -e some-variable=some-value playbook.yml [playbook2.yml ...]

You can list every available options with following command:

 
ansible-playbook --help

Idempotency

Ansible is idempotent, it means that configuration of the target hosts remains the same after one or several calls, in other words, if you apply a configuration once or multiple times, it will ensure that the resulting state is always what you expect.

Idempotency should be guaranted if you use most of the core modules of Ansible. If you write your own modules, you should ensure that idempotency is respected.

Beware that using Ansible shell or command module is difficult to ensure idempotency.

Ansible project structure

Prerequsites: Linux or MacOS workstation to run install and run Ansible

Lab preparation

Install following software in order to run the lab on your workstation

  • Vagrant, allows to provision easyily a Virtual Machine (ie: VirtualBox) or any other supported Virtual Machine on your Workstation or in the Cloud. Download Vagrant here

  • VirtualBox, allows to run Virtual Machine on your workstation Download VirtualBox here

  • Ansible, the Ansible binary that allows you to run ansible playbooks from your control node on the different target machines. You can read the installation instructions here

Create following directory structure:

ansible/101/ 
ansible/setup/vagrant

Create a file named “Vagrantfile” in ansible/setup/vagrant with following content:

 
Vagrant.configure("2") do |config|

config.vm.box_check_update = false

config.vm.network "forwarded_port", guest: 80, host: 10080, auto_correct: true # http port

config.vm.box = "ubuntu/xenial64"

config.vm.provider "virtualbox" do |v|
v.name = "ansible_target_vagrant_vm"
v.memory = 6144
v.cpu = 1
end

config.vm.provision "shell", inline: "apt-get update && apt-get upgrade -y"
config.vm.provision "shell", inline: "apt-get install -y aptitude"

end

This Vagrantfile describes the state of the VirtualBox VM we want to create. We will not get into the details of this file, but in order to understand the next parts of this lab, please notice that a forwarded port has been defined in the Vagrantfile.

The port 80 of the Virtual Machine is fowarded on the port 10080 on the host (control machine), so we will be able to access it later in the lab.

The SSH port (22) is automatically forwarded on the host (control machine) on port 2222.

Start Vagrant machine

Open a terminal or cmd window and cd into your ansible/setup/vagrant directory, then type:

vagrant up 

You should see a bunch of logs indicating you that it is starting the new VM and downloading a base Ubuntu VirtualBox box. If you open Virtual Box, you should see after a few minutes a new VM named “ansible_target_vagrant_vm”.

The VM is now ready to be configured with Ansible.

Ansible 101 Playbook

We will create a simple Ansible playbook that will install nginx on the target host. This will allow us to go through the setting of a very basic ansible project.

So let’s start by navigating into ansible/101/ directory

Create Inventory

In the ansible/101/ create a new directory named “inventory” with following file named “hosts”

[webservers]
webserver1 ansible_ssh_host=127.0.0.1 ansible_ssh_port=2222 ansible_ssh_user=vagrant ansible_ssh_private_key_file=[Path-To-Your-Ansible-Dir]/ansible/setup/vagrant/.vagrant/machines/default/virtualbox/private_key

The “ansible/101//inventory/hosts” file defines a “[webservers]” group and one target machine named “webserver1” with following attributes:

  • ansible_ssh_host, 127.0.0.1 or localhost, as the target instance forwards the ssh port on the control node
  • ansible_ssh_port, the ssh port of the target instance, the VirtualBox VM automatically forwarded on the control machine on port 2222
  • ansible_ssh_user, vagrant, is the default user that is created when we start a virtual machine with Vagrant
  • ansible_ssh_private_key_file, the path to the private key automatically generated by Vagrant when running “vagrant up” command. You need to adapt the path to your control node.

Create Ansible Configuration file

In the ansible/101/ create a new file named “ansible.cfg” with following content:

[defaults]
inventory = ./inventory
library = ./library
filter_plugins = ./filters
roles_path = ./roles
callback_plugins = ./callbacks

remote_tmp = $HOME/.ansible/tmp
pattern = *
forks = 16
poll_interval = 15
transport = smart
remote_port = 22
# #module_lang = C
nocows = 1

# display_skipped_hosts=0

# gathering = implicit
# retry_files_enabled = False

# # uncomment this to disable SSH key host checking
host_key_checking = False

# # what flags to pass to sudo
# #sudo_flags = -H

# # SSH timeout
# timeout = 38

remote_user = vagrant

log_path = ~/.ansible/ansible.log
executable = /bin/sh

# # format of string {{ ansible_managed }} available within Jinja2
# # templates indicates to users editing templates files will be replaced.
# # replacing {file}, {host} and {uid} and strftime codes with proper values.
# ansible_managed = Ansible managed: {file} modified on %Y-%m-%d %H:%M:%S by {uid} on {host}
# #

[privilege_escalation]
become=True
become_method=sudo
become_user=root
# #become_ask_pass=False

[ssh_connection]
pipelining = True
control_path = /tmp/ansible-ssh-%%h-%%p-%%r
# ssh_args = -F .ssh/config -o ControlMaster=auto -o ControlPersist=60m
# scp_if_ssh = True

This configuration file allows to define several Ansible configurations and will determine the directory structure and Ansible behaviour.

The full list of options can be found here or listed by typing following command on your control machine:

ansible-config list

You can also compare your current configuration settings towards the default ones by running following command:

ansible-config dump

Security Note: Please be aware that putting an ansible.cfg file in the current directory is not safe and not recommended and it should be placed into a non world-writable directory (more details here), but for our lab we will define the ansible.cf file in the current working directory for the sake of simplicity.

Create Ansible Playbook

In the ansible/101/ create a new file named “101-playbook” with following content:

---

- hosts: webservers
vars:
http_port: 80
tasks:
- name: ensure nginx is at the latest version
apt:
name: nginx
state: latest
- name: write the apache config file
template:
src: templates/nginx/default.j2
dest: /etc/nginx/sites-available/default
notify:
- restart nginx
- name: ensure nginx is running
service:
name: nginx
state: started
handlers:
- name: restart nginx
service:
name: nginx
state: restarted

The playbook content is the same as the one that has been described previously in this article. It will simply install nginx on the target instance with a basic nginx configuration defined as a template.

Create Template for Nginx directory

In the ansible/101/ create a new directory named “templates”, a sub-directory named “nginx”, with one file named “default.j2” with the following content:

# Added by Ansible - {{ now() }}
# Default server configuration
server {
listen {{ http_port }} default_server;
listen [::]:{{ http_port }} default_server;

root /var/www/html;

index index.html index.htm index.nginx-debian.html;

server_name _;

location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}
}

The template engine used by Ansible is Jinja, a very well known Python templating engine. You can find the documentation here.

As you can see, the template describes a very basic nginx template with some placeholders {{ now() }} and {{ http_port }}.

The core module of Ansible to use templates is “template”, and in our example playbook is defined like this:

- name: write the apache config file
template:
src: templates/nginx/default.j2
dest: /etc/nginx/sites-available/default

The template module needs at least a source (src) that is set to templates/nginx/default.j2 that we just created, and a destination (dest). This is the location and name of the final rendered file on the target host.

Run the Playbook

Now that you’re all set, you can now execute the Ansible playbook. Navigate to ansible/101/ and execute following command:

ansible-playbook 101-playbook.yml

You should see similar output:

ansible-playbook 101-playbook.yml

PLAY [webservers] **************************************************************************************************************************************************************************************

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

TASK [ensure nginx is at the latest version] ***********************************************************************************************************************************************************
changed: [webserver1]

TASK [write the apache config file] ********************************************************************************************************************************************************************
changed: [webserver1]

TASK [ensure nginx is running] *************************************************************************************************************************************************************************
ok: [webserver1]

RUNNING HANDLER [restart nginx] ************************************************************************************************************************************************************************
changed: [webserver1]

PLAY RECAP *********************************************************************************************************************************************************************************************
webserver1 : ok=5 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

As you can see in the output, you can follow each steps of your playbook execution.

Let’s validate that Nginx has been correctly setup by opening a webbrowser and navigate to “http://localhost:10080“. You should see the default Nginx landing page “Welcome to nginx!”

Conclusion

This very simple lab shows a basic overview of an Ansible project with its directory structure and a playbook to install Nginx. The next steps would be to explore the creation of roles and modules, and dive deeper in the inventory usage with the use of dynamic inventories on cloud resources for example. If we have to remember some keypoint among many it would be :

  • Simplicity, human readable playbook in a comprehensive format (YAML), low learning curve
  • Agentless, no need to install any agent on the target hosts with the burden of updating them
  • No Central Server, simplify infrastructure and remove single point of failure
  • Infrastructure as Code and Configuration as Code with versionning in Source Control
  • Reproducible environments

Read also

-

Les Tutos Docker – Episode 2

BIENVENUE DANS CE 2ÈME ÉPISODE DES TUTOS DOCKER ! Dans …

-

SmartWave hosted its second Docker Geneva MeetUp

On the 31st of May 2018, SmartWave hosted its Docker Geneva …

-

Git : quelle stratégie de branching ?

Git est devenu le standard pour la gestion de code …