Ansible presentation

Are you fed up to make again and again the same tasks during servers deployment ? So, Ansible is made for you.

To summarize, Ansible is an automated configuration management tool. It is mainly used to deploy configuration, applications, or for service orchestration (ex. Docker). Its goal is to be as simple as possible in day to day usage (SysAdmin are busy people).

Everything from the server to client goes through SSH or PowerShell, machines are declared in a simple hosts file of the configuration directory (/etc/ansible). This file can (and must) be divide in order to define broadcasting groups (ex. Databases, Debian, Nginx, …). Ansible works by connecting to declared nodes in  /etc/ansible/hosts and pushing little programs (modules), that the master will execute through SSH then command to delete them once finished.

There are a large amount of tools over the market (Puppet, Chef, Saltstack, …). So, think to take the necessary time in order to take informations regarding possibilities that every one of them can offers. And build some labs on 2-3 of them if necessary.

In my case, principals reasons that lead me to choose Ansible are:

  • OpenSource model
  • Agentless, nothing needs to be installed on remote hosts (SSH & PowerShell)
  • Advanced construction of playbooks (variables, conditions, loop, error mgmt, …)
  • Looooots of playbooks available over Github
  • Possibility to implement a web interface in order to delegated packages deployment (Ansible AWX)

After months of weekly usage for daily SysAdmin tasks, I told myself (Yes, I am thinking aloud ...) that it will be interresting to share at least a small documentation for basic to advanced usage of Ansible. So, another article will be written for a more advanced usage of it.

A classic deployment will runs through these steps:

  • We fill in an inventory file that contains hosts list (/etc/ansible/hosts)
  • Writing tasks that needs to be done and associated roles (playbook file)
  • (Writing roles, if task is more complex that a single action)
  • Launching ansible-playbook with playbook file as parameter (<name>.yml)
  • The server will establish connection to remote hosts (5 hosts by 5), and deployment will begin by SSH or PowerShell and result will be printed to terminal, with color (Yes! 2019 is a wonderful year !!!)

Test infrastructure presentation

Fore the sake of this tutorial, a simple infrastructure have been provisioned. It is composed of 4 machines (3xDebian9 + 1xCentOS7). Everyone of them are NATed from a principal physical host. Therefore we will have the following addressing:

  • 1 server: deb1 (192.168.254.128)
  • 1 client: deb2 (192.168.254.129)
  • 1 client: deb3 (192.168.254.130)
  • 1 client: cen1 (192.168.254.131)

Core server installation (Debian 9)

Here our Ansible server will run under Debian 9 (deb1). This package is now well knowned over major Linux distributions, so if you do not use a Debian style one, please refer to you package manager.

apt install -y ansible

Post installation prerequisites

Hosts management

As we are running a local test infrastructure, we will not use local DNS server, but instead we will fill up the /etc/hosts local file of the Ansible server.

cat << EOF >> /etc/hosts
192.168.254.128 deb1
192.168.254.129 deb2
192.168.254.130 deb3
192.168.254.131 cen1
EOF
/etc/hosts

SSH configuration

Ansible managed hosts directly through SSH (or PowerShell for Windows). So you need to generate a pair of public/private keys, then copy your public one on each target client. Once again, for simplicity and clarity related to the writing of the tutorial, root account will be used on each remote host, and there will not be any passphrase. Let us begin by generating the SSH key pairs for the server.

ssh-keygen -t rsa

ssh key generation


Now, server public key needs to be copied on each client. This step must be done for each single remote client that will be present in the /etc/ansible/hosts file. This is recommended to add the content of your public key to the inital deployment server (PXE, GPO Policy, …).

root@deb1:~# ssh-copy-id root@deb2
root@deb1:~# ssh-copy-id root@deb3
root@deb1:~# ssh-copy-id root@cen1
root@deb1:~# ssh-copy-id root@localhost
ssh-copy-id

(Yes, the last key copy, is for the server itself in order to be able to deploy configuration on it too.)

Ansible clients declaration (/etc/ansible/hosts)

What you need to know regarding this configuration file are:

  • Comments begin by the '#' symbol
  • Hosts group are delimited like this: [group_name]
  • Hostnames or IPs can be use
  • A single host can be member of several groups at a time
  • Multiple declarations can be done like this: web-0[01:09]


Let's begin by emptying the initial configuration file.

>/etc/ansible/hosts

Next, fill up the /etc/ansible/hosts file by creating groups linked to Operating Systems, then roles.

[debian]
deb1
deb2
deb3

[debian-web]
deb3

[deb-tools]
deb2
deb3

[centos]
cen1

[centos-web]
cen1

[centos-tools]
cen1
/etc/ansible/hosts

Communication test

It is now time to test communication between the server and hosts. To do this, Ansible embed a large quantity of modules that we can call with the '-m' option. Ping will send ICMP requests to every single hosts decalred in the file.

ansible -m ping all

ansible ping

If we look at logs on the target system (/var/log/messages), we get the following output generated by the remote module executed through SSH.

Sep  9 13:22:20 deb2 ansible-ping: Invoked with data=None


Package installation (inline mode)

Debian (tmux - terminal emulator)

ansible -m apt -a 'name=tmux' debian

Screen output will be similar to this one.

deb1 | SUCCESS => {
"cache_update_time": 1568030717,
"cache_updated": false,
"changed": true,
"stderr": "",
"stdout": "Reading package lists
...
Building dependency tree...
Reading state information...
The following additional packages will be installed:
libevent-2.0-5 libutempter0
The following NEW packages will be installed:
libevent-2.0-5 libutempter0 
...,
"stdout_lines": [
"Reading package lists...",
"Building dependency tree...",
"Reading state information...",
"The following additional packages will be installed:",
"  libevent-2.0-5 libutempter0",
"The following NEW packages will be installed:",
"  libevent-2.0-5 libutempter0 tmux",
"0 upgraded, 3 newly installed, 0 to remove and 27 not upgraded.",
"Need to get 425 kB of archives.",
"After this operation, 1026 kB of additional disk space will be used.",
"Get:1 http://ftp.fr.debian.org/debian stretch/main amd64 libevent-2.0-5 amd64 2.0.21-stable-3 [152 kB]",
"Get:2 http://ftp.fr.debian.org/debian stretch/main amd64 libutempter0 amd64 1.1.6-3 [7812 B]",
"Get:3 http://ftp.fr.debian.org/debian stretch/main amd64 tmux amd64 2.3-4 [265 kB]",
"Fetched 425 kB in 0s (5877 kB/s)",
"Selecting previously unselected package libevent-2.0-5:amd64.",
"(Reading database ... ",
"(Reading database ... 5%",
"(Reading database ... 10%",
"(Reading database ... 15%",
...
"(Reading database ... 95%",
"(Reading database ... 100%",
"(Reading database ... 37460 files and directories currently installed.)",
"Preparing to unpack .../libevent-2.0-5_2.0.21-stable-3_amd64.deb ...",
"Unpacking libevent-2.0-5:amd64 (2.0.21-stable-3) ...",
"Selecting previously unselected package libutempter0:amd64.",
"Preparing to unpack .../libutempter0_1.1.6-3_amd64.deb ...",
"Unpacking libutempter0:amd64 (1.1.6-3) ...",
"Selecting previously unselected package tmux.",
"Preparing to unpack .../archives/tmux_2.3-4_amd64.deb ...",
"Unpacking tmux (2.3-4) ...",
"Setting up libutempter0:amd64 (1.1.6-3) ...",
"Processing triggers for libc-bin (2.24-11+deb9u4) ...",
"Processing triggers for man-db (2.7.6.1-2) ...",
"Setting up libevent-2.0-5:amd64 (2.0.21-stable-3) ...",
"Setting up tmux (2.3-4) ...",
"Processing triggers for libc-bin (2.24-11+deb9u4) ..."
]
}
apt install tmux equivalent

CentOS (tmux - terminal emulator)

ansible -m yum -a 'name=tmux' centos

Same thing for CentOS, we simply have to change the module type (from apt to yum), and the host group too.


Playbooks

Definition

Playbooks are a group of tasks that will be executed in a sequential way, this will avoid to launch manually 15 times the same Ansible command. They will allow to define a state in which we want the system finish its course (start / stop / file presence / …).

TIPS: Even for simple packages installation (Apache, LAMP, …), take some minutes in order to list every single steps that will need to be done to achieve your goal. This will avoid, among other, to retry several time the same deployment. (Free LifeStyle tips brought to you by Captain Obvious)

Package installation (Apache2)

In this first cast, we will install a single package. Here Apache2 on the hostgroup debian-web previously defined in the hosts file.

---
- hosts: debian-web
vars:
remote_user: root

tasks:
- name: 1. Apache2 installation (latest version)
apt:
name: apache2
state: latest
- name: 2. Apache2 start and enable
service:
name: apache2
state: started
enabled: yes
apache2 playbook installation

Launch playbook execution.

ansible-playbook debian-apache2.yml

apache2 installation output

Package deletion (Apache2)

In ordor to uninstall and disable the previously deployed package, here is the content of the Ansible playbook:

---
- hosts: debian-web
vars:
remote_user: root

tasks:
- name: Apache2 stop and disable
service:
name: apache2
state: stopped
enabled: no
- name: Apache2 uninstall
apt:
name: apache2
state: absent
apache2 playbook unintall

Apache / MariaDB / PHP

Let's begin by enumerating some of the best practices recommended for the usage of Ansible. You can retrieve all of them on their official documentation. Here is the configuration schema will will use for our Apache2 / PHP / MariaDB stack.

  • A folder for the playbooks (ex.playbooks/debian-lamp.yml)
  • A folder for roles. In order to stay ordered, it is recommanded to name roles the same way hostgroups are in the Ansible hosts file. As example, it can be composed of subdirectories like this: debian-apache2 / debian-mariadb / debian-php-fpm

Every single subfolder will be composed of 2 others subfolders too (files & tasks), that will allow to deploy files during task execution (file), or will define the task list to do (tasks).

Here is in practice what we get for the /etc/ansible/ folder content:

├── ansible.cfg
├── hosts
├── playbooks
│   ├── debian-apache2-remove.yml
│   ├── debian-apache2.yml
│   └── debian-lamp.yml
└── roles
    ├── debian-apache2
    │   ├── files
    │   └── tasks
    │       └── main.yml
    ├── debian-mariadb
    │   ├── files
    │   └── tasks
    │       └── main.yml
    └── debian-php-fpm
        ├── files
        └── tasks
            └── main.yml
/etc/ansible folder tree


Apache2 + MariaDB + php stack deployment will be composed of the following files:

  • playbooks/debian-lamp.yml
  • roles/debian-apache2/tasks/main.yml
  • roles/debian-mariadb/tasks/main.yml
  • roles/debian-php-fpm/tasks/main.yml

playbooks/debian-lamp.yml: This file will define hosts list or hosts group that will be concerned by the deployment. It defines roles that needs to be deployed on each host of the list too.

---
- hosts: deb2,deb3
roles:
- debian-apache2
- debian-mariadb
- debian-php-fpm
LAMP playbook


roles/debian-apache2/tasks/main.yml: This file install Apache2 thanks to the 'apt' module, then restart the service and add it to the OS startup process.

---
- name: 1. install Apache2
apt: name=apache2 state=installed
- name: 2. Apache2 start and enable
service:
name: apache2
state: started
enabled: yes
Apache2 debian role

roles/debian-mariadb/tasks/main.yml: 4 steps are here needed for an installation and minimal configuration of the MariaDB database.

- name: 1. Update the package repository
command: apt update

- name: 2. Install MariaDB
apt: pkg={{ item }} state=installed
with_items:
- mariadb-server
- mariadb-client
- python-mysqldb

- name: 3. Start and Enable MariaDB
service: name=mariadb state=started enabled=yes

- name: 4. Update MariaDB root password for all root accounts
mysql_user: name=root
host={{ item }}
password=sb00bDesb0is152InflateDanke
login_user=root
login_password=""
state=present
with_items:
- 127.0.0.1
- ::1
- localhost
MariaDB debian role

Step 1: Update the package list, because after a fresh install of a Debian system depots list and URI for MariaDB are not always up2date.

Step 2: Install the three needed packages: server + client + python-mysqldb (in order to be able to made changes from the Ansible client to database). Pay attention to the usage of the {{ item }} variable that will parse listed elements after 'with_items'.

Step 3: Start MariaDB process and add it to the system startup process.

Step 4: Define a new password for the Database root account, default one is empty, on each way localhost is declared in the DB. That is here where the python-mysqldb package is used. If you want to make these operations manually, you can delete step 4 from the .yml file and remove associated package in step 2.

roles/debian-php-fpm/tasks/main.yml: We finish the installation with the php-fpm package and its dependencies.

- name: 1. install PHP modules
apt: pkg={{ item }} state=installed
with_items:
- php-fpm
- php-gd
- php-curl
- php-mysql
- php-dom
- php-xml
- name: 2. start php-fpm
service: name=php7.0-fpm state=started enabled=yes
- name: 3. restart apache2
service: name=apache2 state=restarted
php debian role

Deployment can now be started !!

ansible-playbook playbooks/debian-lamp.yml

LAMP playbook running

You are now having a fresh LAMP stack installed. You can find all the playbooks and roles used during this tutorial on our GitHub repository.