1. Presentations

Get the presentations here.

2. Overview

After this lab you will have a basic understanding of how ansible works and how to use and modify the predefined ansible roles for installing SAP HANA.

This Lab uses the infrastructure of HPE and hence you have already received your VPN credentials to get access HPE Lab infrastructure

The servers you work on have already been prepared and you will get the additional information on how to access your personal environment here.

3. Your Lab Environment

You will work in a lab environment that has already been deployed for you.

You will have access to the following hosts:

Type

Hostname

Internal IP

Role

Control Host

hpi-vmjump.epc.ext.hpe.com

192.168.25.200

The host you will run Ansible on to manage the other hosts.

Managed Node 1

hpi-vmXX.epc.ext.hpe.com

192.168.25.XX

Managed host XX

Managed Node 2

hpi-vmYY.epc.ext.hpe.com

192.168.25.YY

Managed host YY (with YY=100+XX)

You will be able to SSH as user admXX into the control host, from here you need to SSH into your assigned managed nodes

4. Access the Lab Environment

Every user will get his own admin user admXX to login/manage the according hpi-vmXX

To access your control host:

  • Open the VPN with your received credentials

  • Log in to your control host as admXX using the IP address, the password will be given by the instructor.

The IP Adress is: 192.168.25.200

5. Ansible Deployment

In this lab we have preconfigured ansible on the control host.

If you want to configure your on Linux server or vm for use with ansible, please note that you can get your own personal RHEL subscription for free here: https://developers.redhat.com/products/sap/download/
For some time in order to use Ansible on RHEL you had to install it from the EPEL repository. There are other options to get Ansible on RHEL or other distributions. If you have a Red Hat Ansible Engine Subscription (the supported version of Ansible), you’ll have access to a dedicated Ansible repo, if you are a RHEL only Customer you need to enable the RHEL Extras repository. Please see the following Red Hat KB article for more options: How do I Download and Install Red Hat Ansible Engine?

5.1. Check Ansible has been installed correctly

Make sure you are now logged in on your controlhost (192.168.25.200) as user admXX.

[admXX@hpi-vmjump ~]$ ansible --version
ansible 2.5.2
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/adm20/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /bin/ansible
  python version = 2.7.5 (default, Feb 20 2018, 09:19:12) [GCC 4.8.5 20150623 (Red Hat 4.8.5-28)]
Ansible is keeping configuration management simple. Ansible requires no database or running daemons and can run easily on a laptop. On the managed hosts it needs no running agent.

5.2. Set up users and SSH

In the lab exercises Ansible connects to the managed hosts by SSH using key authentication. You need to set up the nodes correctly before proceeding. Later we’ll run Ansible commands that require root privileges. As we don’t want to run Ansible as root you have to setup sudo to enable privilege escalation on the managed nodes.

User ansible has already been setup for you on all nodes with the same password as user admXX.

To enable key authenticated SSH logins without password:

  • Make sure you are user admXX and create an SSH key without password protection:

[admXX@hpi-vmjump ~]$ whoami
admXX
[admXX@hpi-vmjump ~]$ ssh-keygen
  • Copy the SSH key to your managed node (password is the same as admXX on all nodes):

[admXX@hpi-vmjump ~]$ ssh-copy-id ansible@hpi-vmXX
  • Verify that the managed host now accepts password-less connections with key authentication from the control node as user admXX.

[admXX@hpi-vmjump ~]$ ssh ansible@hpi-vmXX

5.3. Setup sudo

To allow user ansible to execute commands on rhel-vm_XX__ as root you have to configure sudo on the managed nodes:

  • From the control node login as ansible to hpi-vmXX.epc.ext.hpe.com.

  • Use sudo and visudo to edit the sudoers file. Uncomment the line that allows people in group wheel to run all commands without password and comment the line that makes them require a password:

## Allows people in group wheel to run all commands
#%wheel ALL=(ALL)       ALL

## Same thing without a password
%wheel  ALL=(ALL)       NOPASSWD: ALL
user ansible is in group wheel so you can run sudo visudo to make the above changes
  • Save your changes

  • Test that the updated configuration allows ansible to run commands using sudo:

[ansible@hpi-vmXX ~]$ sudo cat /etc/shadow
  • When finished, exit the SSH session

In real world environments you would integrate these steps into you deployment process so your nodes are already configured for using Ansible over SSH.
if you don’t want to enable passphrase/passwordless access you can use -k/-K command line options of ansible.
In all subsequent exercises you should only work as user admXX on the control node if not explicitly told differently.

5.4. Working the Labs

You might have guessed by now this lab is pretty commandline-centric…​ :-)

  • Don’t type everything manually, use copy & paste from the browser when appropriate. But don’t stop to think and understand…​ ;-)

  • All labs where prepared using vi, but feel free to install nano or even emacs.

In the lab guide commands you are supposed to run are shown with or without the expected output, whatever makes more sense in the context.
The command line can wrap on the web page from time to time. Therefor the output is separated from the command line for better readability by an empty line. Anyway, the line you should actually run should be recognizable by the prompt. :-)

5.5. Challenge Labs

You will soon discover that many chapters in this lab guide come with a "Challenge Lab" section. These labs are meant to give you a small task to solve using what you have learned so far. The solution of the task is shown underneath a warning sign. == Getting Started with Ansible

5.6. The Inventory

To use the ansible command for host management, you need to provide an inventory file which defines a list of hosts to be managed from the control node. One way to do this is to specify the path to the inventory file with the -i option to the ansible command.

Make sure you are user admXX on hpi-vmjump.epc.ext.hpe.com. Create a directory for your Ansible files:

[admXX@hpi-vmjump ~]$ mkdir ansible-files
This is a project directory, you could of course name it like you want.

Now create a simple inventory file as ~/ansible-files/inventory with the following content:

hpi-vmjump.epc.ext.hpe.com
hpi-vmXX.epc.ext.hpe.com
Don’t forget to replace XX with the correct number!

To reference inventory hosts, you supply a host pattern to the ansible command. Ansible has a --list-hosts option which can be useful for clarifying which managed hosts are referenced by the host pattern in an ansible command.

The most basic host pattern is the name for a single managed host listed in the inventory file. This specifies that the host will be the only one in the inventory file that will be acted upon by the ansible command. Run:

[admXX@hpi-vmjump ansible-files]$ ansible "hpi-vmXX.epc.ext.hpe.com" -i ~/ansible-files/inventory --list-hosts
  hosts (1):
    hpi-vmXX.epc.ext.hpe.com

An inventory file can contain a lot more information. To organize your hosts in groups, change your inventory file so it looks like this:

[webserver]
hpi-vmXX.epc.ext.hpe.com

[ftpserver]
hpi-vmXX.epc.ext.hpe.com

[management]
hpi-vmjump.epc.ext.hpe.com

Now run Ansible with these host patterns and observe the output:

[admXX@hpi-vmjump ~]$ ansible webserver -i ~/ansible-files/inventory --list-hosts
[admXX@hpi-vmjump ~]$ ansible webserver,hpi-vmjump.epc.ext.hpe.com -i ~/ansible-files/inventory --list-hosts
[admXX@hpi-vmjump ~]$ ansible '*.epc.ext.hpe.com' -i ~/ansible-files/inventory --list-hosts
[admXX@hpi-vmjump ~]$ ansible all -i ~/ansible-files/inventory --list-hosts
It is ok to put systems in more than one group, for instance a server could be both a web server and a database server.
The inventory can contain more data. E.g. if you have hosts that run on non-standard SSH ports you can put the port number after the hostname with a colon. Or you could define names specific to Ansible and have them point to the "real" IP or hostname.

5.7. The Ansible Configuration Files

The behavior of Ansible can be customized by modifying settings in Ansible’s ini-style configuration file. Ansible will select its configuration file from one of several possible locations on the control node, please refer to the documentation.

The recommended practice is to create an ansible.cfg file in a directory from which you run Ansible commands. This directory would also contain any files used by your Ansible project, such as the inventory and Playbooks.

Make sure your inventory file is used by default when executing commands from the ~/ansible-files/ directory:

  • On hpi-vmjump.epc.ext.hpe.com as admXX create the file ~/ansible-files/ansible.cfg with the following content (replace XX by your id):

[defaults]
inventory=/home/admXX/ansible-files/inventory
  • Check with ansible --version, first from ansible’s home directory and then from ~/ansible-files/. You should find when run from ~/ansible-files/ your personal config settings override the main config file.

  • From ~/ansible-files/ run ansible all --list-hosts.

Your Ansible inventory was used without providing the -i option. To double-check, run the command again from outside ~/ansible-files/:

[admXX@hpi-vmjump ansible-files]$ cd ..
[admXX@hpi-vmjump ~]$ ansible all --list-hosts

 [WARNING]: provided hosts list is empty, only localhost is available

 [WARNING]: No hosts matched, nothing to do

  hosts (0):

6. Running Ansible Ad-Hoc Commands

Ansible allows administrators to execute on-demand tasks on managed hosts. These ad hoc commands are the most basic operations that can be performed with Ansible. They are great for learning about Ansible, for trying new things or for quick non-intrusive tasks like reporting. Let’s try something straight forward:

Don’t forget to run the commands from ~/ansible-files/ where your ansible.cfg file is located, otherwise it will complain about an empty host list.

ansible tries to use the default user to ssh to the remote host. Because the ansible admin user on your managed node is ansible and you are logged in as admXX yiu have to define the connection type which is different from ssh and the connection user which is ansible (see also Ansible documentation). Please change yout inventory file like this:

[webserver]
hpi-vmXX.epc.ext.hpe.com ansible_user=ansible

[ftpserver]
hpi-vmXX.epc.ext.hpe.com ansible_user=ansible

[management]
hpi-vmjump.epc.ext.hpe.com ansible_connection=local

Run the examples on hpi-vmjump.epc.ext.hpe.com from the ~/ansible-files/ directory as user admXX.

[admXX@hpi-vmjump ansible-files]$ ansible all -m ping

The -m option defines which Ansible module to use. Options can be passed to the specified modul using the -a option. BTW the ping module is not running an ICMP ping but does a simple connection test.

Think of a module as a tool which is designed to accomplish a specific task.

6.1. Listing Modules and Getting Help

Modules are programs that Ansible uses to perform operations on managed hosts. They are ready-to-use tools designed to perform specific operations. Modules can be executed from the commandline as Ansible ad hoc commands or used in Playbooks to execute tasks. When run, modules are copied to the managed host and executed there.

To list all modules run:

[admXX@hpi-vmjump ansible-files]$ ansible-doc -l
In ansible-doc use the up/down arrows to scroll through the content and leave with q.

To find a module try e.g.:

[admXX@hpi-vmjump ansible-files]$ ansible-doc -l | grep -i user

Get help for a specific module including usage examples:

[admXX@hpi-vmjump ansible-files]$ ansible-doc user
This will give all options ("=" shows a mandatory option), examples for usage and occasionally some notes.

6.2. More Ad Hoc Commands

Let’s try a simple module that just executes a command on a managed host:

[admXX@hpi-vmjump ansible-files]$ ansible hpi-vmXX.epc.ext.hpe.com -m command -a 'id'


hpi-vmXX.epc.ext.hpe.com | SUCCESS | rc=0 >>
uid=789(ansible) gid=10(wheel) groups=10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

In this case the module is called command and the option passed with -a is the actual command to run. Try to run this ad hoc command on both hosts using the all host pattern.

Another example: Have a quick look at the kernel versions your hosts are running:

[admXX@hpi-vmjump ansible-files]$ ansible all -m command -a 'uname -r'

Sometimes it’s desirable to have the output for a host on one line:

[admXX@hpi-vmjump ansible-files]$ ansible all -m command -a 'uname -r' -o

Using the copy module, execute an ad hoc command on hpi-vmjump.epc.ext.hpe.com to change the contents of the /etc/motd file on hpi-vmXX.epc.ext.hpe.com. The content is handed to the module through an option in this case.

Run:

[admXX@hpi-vmjump ansible-files]$ ansible hpi-vmXX.epc.ext.hpe.com -m copy -a 'content="Managed by Ansible\n" dest=/etc/motd'

Output:

hpi-vmXX.epc.ext.hpe.com | FAILED! => {
    "changed": false,
    "checksum": "a314620457effe3a1db7e02eacd2b3fe8a8badca",
    "failed": true,
    "msg": "Destination /etc not writable"
}

Should be all red for you, the ad hoc command failed. Why? Because user ansible is not allowed to write the motd file.

Now this is a case for privilege escalation and the reason sudo has to be setup properly. We need to instruct ansible to use sudo to run the command as root by using the parameter -b (think "become").

Ansible will connect to the machines using your current user name (ansible in this case), just like SSH would. To override the remote user name, you could use the -u parameter.

For us it’s okay to connect as ansible because sudo is set up. Change the command to use the -b parameter and run again:

[admXX@hpi-vmjump ansible-files]$ ansible hpi-vmXX.epc.ext.hpe.com -m copy -a 'content="Managed by Ansible\n" dest=/etc/motd' -b

Output:

hpi-vmXX.epc.ext.hpe.com | SUCCESS => {
    "changed": true,
    "checksum": "a314620457effe3a1db7e02eacd2b3fe8a8badca",
    "dest": "/etc/motd",
    "gid": 0,
    "group": "root",
    "md5sum": "7a924f6b4cbcbc7414eda7763dc0e43b",
    "mode": "0644",
    "owner": "root",
    "secontext": "system_u:object_r:etc_t:s0",
    "size": 19,
    "src": "/home/ansible/.ansible/tmp/ansible-tmp-1472132609.82-261447806330276/source",
    "state": "file",
    "uid": 0
}

Check the motd file:

[admXX@hpi-vmjump ansible-files]$ ansible hpi-vmXX.epc.ext.hpe.com -m command -a 'cat /etc/motd'

hpi-vmXX.epc.ext.hpe.com | SUCCESS | rc=0 >>
Managed by Ansible

Run the ansible hpi-vmXX.epc.ext.hpe.com -m copy ... command from above again. Note:

  • the different output color (proper terminal config provided)

  • the change from "changed": true, to "changed": false,.

This makes it a lot easier to spot changes and what Ansible actually did.

6.3. Challenge Lab: Modules

  • Using ansible-doc

    • Find a module that uses Yum to manage software packages.

    • Look up the help examples for the module to learn how to install a package in the latest version

  • Run an Ansible ad hoc command to install the package "screen" in the latest version on hpi-vmXX.epc.ext.hpe.com

Use the copy ad hoc command from above as a template and change the module and options.
Solution below!
[admXX@hpi-vmjump ansible-files]$ ansible-doc -l | grep -i yum
[admXX@hpi-vmjump ansible-files]$ ansible-doc yum
[admXX@hpi-vmjump ansible-files]$ ansible hpi-vmXX.epc.ext.hpe.com -m yum -a 'name=screen state=latest' -b

7. Ansible Playbooks

While Ansible ad hoc commands are useful for simple operations, they are not suited for complex configuration management or orchestration scenarios.

Playbooks are files which describe the desired configurations or steps to implement on managed hosts. Playbooks can change lengthy, complex administrative tasks into easily repeatable routines with predictable and successful outcomes.

Here is a nice analogy: When Ansible modules are the tools in your workshop, the inventory is the materials and the Playbooks are the instructions.

7.1. Playbook Basics

Playbooks are text files written in YAML format and therefore need:

  • to start with three dashes (---)

  • proper identation using spaces and not tabs!

There are some important concepts:

  • hosts: the managed hosts to perform the tasks on

  • tasks: the operations to be performed by invoking Ansible modules and passing them the necessary options.

  • become: privilege escalation in Playbooks, same as using -b in the ad hoc command.

The ordering of the contents within a Playbook is important, because Ansible executes plays and tasks in the order they are presented.

A Playbook should be idempotent, so if a Playbook is run once to put the hosts in the correct state, it should be safe to run it a second time and it should make no further changes to the hosts.

Most Ansible modules are idempotent, so it is relatively easy to ensure this is true.
Try to avoid the command, shell, and raw modules in Playbooks. Because these take arbitrary commands, it is very easy to end up with non-idempotent Playbooks with these modules.

7.2. Playbook Example

And here is a simple example of a Playbook:

---
# simple playbook with a single play
- name: a simple play
  hosts: managedhost.example.com
  user: remoteuser
  become: yes
  become_method: sudo
  become_user: root
  # First a name for the task, second entry invokes the service module and supplies its
arguments.
  tasks:
  - name: first task
    service: name=httpd enabled=true
  - name: second task
    service: name=sshd enabled=true

8. Your first Playbook

Enough theory, it’s time to create your first Playbook. In this lab you create a Playbook to set up an Apache webserver in three steps:

  • First step: Install httpd package

  • Second step: Enable/start httpd service

  • Third step: Create an index.html file

8.1. Playbook: Install Apache

This Playbook makes sure the package containing the Apache webserver is installed on hpi-vmXX.epc.ext.hpe.com.

You obviously need to use privilege escalation to install a package or run any other task that requires root permissions. This is done in the Playbook by become: yes.

On hpi-vmjump.epc.ext.hpe.com as user admXX create the file ~/ansible-files/apache.yml with the following content:

---
- name: Apache server installed
  hosts: hpi-vmXX.epc.ext.hpe.com
  become: yes
  tasks:
  - name: latest Apache version installed
    yum:
      name: httpd
      state: latest

This shows one of Ansible’s strenghts: The Playbook syntax is easy to read and understand. In this Playbook:

  • A name is given for the play

  • The host to run against and privilege escalation is configured

  • A task is defined and named, here it uses the module "yum" with the needed options.

8.2. Running Playbooks

Playbooks are executed using the ansible-playbook command on the control node. Before you run a new Playbook it’s a good idea to check for syntax errors:

[admXX@hpi-vmjump ansible-files]$ ansible-playbook --syntax-check apache.yml

Now you should be ready to run your Playbook:

[admXX@hpi-vmjump ansible-files]$ ansible-playbook apache.yml

Use SSH to make sure Apache has been installed on hpi-vmXX.epc.ext.hpe.com.

[admXX@hpi-vmjump ansible-files]$ ssh ansible@hpi-vmXX.epc.ext.hpe.com rpm -qi httpd

Name        : httpd
Version     : 2.4.6
[...]

Or even better use an Ansible ad hoc command!

[admXX@hpi-vmjump ansible-files]$ ansible hpi-vmXX.epc.ext.hpe.com -m command -a 'rpm -qi httpd'

Run the Playbook a second time.

The different colors, the "ok" and "changed" counters and the "PLAY RECAP" make it easy to spot what Ansible actually did.

8.3. Extend your Playbook: Start & Enable Apache

The next part of the Playbook makes sure the Apache webserver is enabled and started on hpi-vmXX.epc.ext.hpe.com.

On hpi-vmjump.epc.ext.hpe.com as user admXX edit the file ~/ansible-files/apache.yml to add a second task using the service module. The Playbook should now look like this:

---
- name: Apache server installed
  hosts: hpi-vmXX.epc.ext.hpe.com
  become: yes
  tasks:
  - name: latest Apache version installed
    yum:
      name: httpd
      state: latest
  - name: Apache enabled and running
    service:
      name: httpd
      enabled: true
      state: started

And again what it does is easy to understand:

  • a second task is defined

  • a module is specified (service)

  • options are supplied

As this is YAML take care of the correct indentation when copy/pasting!

Run your extended Playbook:

[admXX@hpi-vmjump ansible-files]$ ansible-playbook apache.yml
  • Note some tasks are shown as "ok" in green and one is shown as "changed" in yellow.

  • Use an Ansible ad hoc command again to make sure Apache has been enabled and started, e.g.: systemctl status httpd

8.4. Extend your Playbook: Create an index.html

Check that the tasks where executed correctly and Apache is accepting connections: Make an HTTP request using Ansible’s uri module in an ad hoc command from the control node:

[admXX@hpi-vmjump ansible-files]$ ansible localhost -m uri -a "url=http://hpi-vmXX.epc.ext.hpe.com/"
Expect a lot of red lines and a 403 status!

As long as there is not at least an index.html file to be served by Apache, it will throw an ugly "HTTP Error 403: Forbidden" status and Ansible will report an error.

So why not use Ansible to deploy a simple index.html file? Create the file ~/ansible-files/index.html on the control node:

<body>
<h1>Apache is running fine</h1>
</body>

You already used Ansible’s copy module to write text supplied on the commandline into a file. Now you’ll use the module in your Playbook to actually copy a file:

On hpi-vmjump.epc.ext.hpe.com as user admXX edit the file ~/ansible-files/apache.yml and add a new task utilizing the copy module. It should now look like this:

---
- name: Apache server installed
  hosts: hpi-vmXX.epc.ext.hpe.com
  become: yes
  tasks:
  - name: latest Apache version installed
    yum:
      name: httpd
      state: latest
  - name: Apache enabled and running
    service:
      name: httpd
      enabled: true
      state: started
  - name: copy index.html
    copy:
      src: ~/ansible-files/index.html
      dest: /var/www/html/

You are getting used to the Playbook syntax, so what happens? The new task uses the copy module and defines the source and destination options for the copy operation.

Run your extended Playbook:

[admXX@hpi-vmjump ansible-files]$ ansible-playbook apache.yml
  • Have a good look at the output

  • Run the ad hoc command using the "uri" module to test Apache again.

The command should now return a friendly green "status: 200" line, amongst other information.

In case you want to install the same playbook on more than one server replace, edit your playbook and replace hpi-vmXX.epc.ext.hpe.com with webserver according to your inventory file.

9. Ansible Variables

9.1. Introduction

Ansible supports variables to store values that can be used in Playbooks. Variables can be defined in a variety of places and have a clear precedence. Ansible substitutes the variable with its value when a task is executed.

Variables are referenced in Playbooks by placing the variable name in double curly braces.

Here comes a variable {{ variable1 }}

The recommended practice is to define variables in files located in two directories named host_vars and group_vars:

  • To e.g. define variables for a group "servers", create a YAML file named group_vars/servers with the variable definitions.

  • To define variables specifically for a host "hpi-vmXX.epc.ext.hpe.com", create the file host_vars/hpi-vmXX.epc.ext.hpe.com with the variable definitions.

Host variables take precedence over group variables (more about precedence can be found in the docs).

9.2. Variables in Playbooks

9.2.1. Defining Variables in Playbooks

Administrators can define their own variables in Playbooks and use them in tasks. Playbook variables can be defined in multiple ways.

  • Place it directly in a vars block at the beginning of a Playbook:

- hosts: all
  vars:
    user: joe
    home: /home/joe
  • Define Playbook variables in external files:

- hosts: all
  vars_files:
    - vars/users.yml

The Playbook variables are then defined in that file in YAML format:

---
user: joe
home: /home/joe

9.3. Off to the Lab

For the follwing task you need 2 servers. The second server is named hpi-vmYY.epc.ext.hpe.com. Make sure to replace _YY with the correct number.

For understanding and practice let’s do a lab. Following up on the theme "Let’s build a webserver. Or two. Or even more…​" you will change the index.html to show the development environment (dev/prod) a server is deployed in.

On hpi-vmjump.epc.ext.hpe.com as user admXX create the directories to hold the variable definitions in ~/ansible-files/:

[admXX@hpi-vmjump ansible-files]$ mkdir {host_vars,group_vars}

9.4. Create the Variable Files

Now create two files containing variable definitions which point to an environment:

  • ~/ansible-files/group_vars/webserver with this content:

---
stage: dev
  • ~/ansible-files/host_vars/hpi-vmYY.epc.ext.hpe.com, content (replace YY accordingly):

---
stage: prod

What is this about?

  • All servers in the webserver group are defined as members of the dev environment by default.

  • For server hpi-vmYY.epc.ext.hpe.com this is overriden and the host is flagged as a production server.

9.5. Create index.html Files

Now create two files in ~/ansible-files/:

One called prod_index.html with the following content:

<body>
<h1>This is a production webserver, take care!</h1>
</body>

And the other called dev_index.html with the following content:

<body>
<h1>This is a development webserver, have fun!</h1>
</body>

9.6. Create the Playbook

Now you need a Playbook that copies the prod or dev index.html file according to the "stage" variable.

Create a new Playbook called deploy_index_html.yml in the ~/ansible-files/ directory.

Note how the variable "stage" is used in the name of the file to copy.
---
- name: Copy index.html
  hosts: webserver
  become: yes
  tasks:
  - name: copy index.html
    copy:
      src: ~/ansible-files/{{ stage }}_index.html
      dest: /var/www/html/index.html
  • Run the Playbook:

[admXX@hpi-vmjump ansible-files]$ ansible-playbook deploy_index_html.yml

9.7. Test the Result

The Playbook should copy different files as index.html to the hosts, use curl to test it:

[admXX@hpi-vmjump ansible-files]$ curl http://hpi-vmXX.epc.ext.hpe.com

<body>
<h1>This is a development webserver, have fun!</h1>
</body>
[admXX@hpi-vmjump ansible-files]$ curl http://hpi-vmYY.epc.ext.hpe.com

<body>
<h1>This is a production webserver, take care!</h1>
</body>
If by now you think: There has to be a smarter way to change content in files…​ you are absolutely right. This lab was done to introduce variables, you are about to learn about templates in one of the next labs.

9.8. Challenge Lab: Using Inventory Variables

A lab just for you without a lot of help…​ you should by now have all information to complete the follwing tasks:

  • In the variable file for the group webserver set a variable service to sshd.

  • In the host variable file for host hpi-vmYY.epc.ext.hpe.com set the variable service to httpd.

  • Create a Playbook check_service.yml to restart a service which name is defined by a variable service. Make it applicable to both hosts.

  • Run the Playbook with "-v" to see Ansible is actually checking different services according to the variables.

Solution below!
[admXX@hpi-vmjump ansible-files]$ cat host_vars/hpi-vmYY.epc.ext.hpe.com
---
stage: prod
service: httpd

[admXX@hpi-vmjump ansible-files]$ cat group_vars/webserver
---
stage: dev
service: sshd

[admXX@hpi-vmjump ansible-files]$ cat check_service.yml
---
- name: Check if  service is enabled and started
  hosts: rhel*.epc.ext.hpe.com
  become: yes
  tasks:
  - name: Check service is enabled and started
    service:
      name: "{{ service }}"
      enabled: true
      state: started

[admXX@hpi-vmjump ansible-files]$ ansible-playbook check_service.yml -v
You can now end the partnering with your neighbour and you can continue by your own.

9.9. Registered variables

Administrators can capture the output of a command by using the register statement. The output is saved into a variable that could be used later for either debugging purposes or in order to achieve something else, such as a particular configuration based on a command’s output.

The following Playbook demonstrates the use, create it as register_var.yml and run it:

---
- name: Installs a package and prints the result
  hosts: hpi-vmXX.epc.ext.hpe.com
  tasks:
    - name: Install the package
      yum:
        name: httpd
        state: installed
      register: install_result
    - debug: var=install_result

When this Playbook is run:

  • the debug module is used to dump the value of the install_result registered variable to the terminal.

This can be very useful while debugging, we’ll cover more of this later. In this case the package was already installed which is mirrored by the module output captured in the variable.

10. Ansible Facts

Ansible facts are variables that are automatically discovered by Ansible from a managed host. Facts are pulled by the setup module and contain useful information stored into variables that administrators can reuse.

To get an idea what facts Ansible collects by default, on hpi-vmjump.epc.ext.hpe.com as user admXX from the ~/ansible-files/ directory run:

[admXX@hpi-vmjump ansible-files]$ ansible hpi-vmXX.epc.ext.hpe.com -m setup
You still remember why you have to run ansible from this directory?

This might be a bit too much, you can use filters to limit the output to certain facts, the expression is shell-style wildcard:

[admXX@hpi-vmjump ansible-files]$ ansible hpi-vmXX.epc.ext.hpe.com -m setup -a 'filter=ansible_eth0'

Or what about only looking for memory related facts:

[admXX@hpi-vmjump ansible-files]$ ansible all -m setup -a 'filter=ansible_*_mb'

10.1. Challenge Lab: Facts

  • Try to find and print the distribution (Red Hat) of your managed hosts. On one line, please.

Use grep to find the fact, then apply a filter to only print this fact.
Solution below!
[admXX@hpi-vmjump ansible-files]$ ansible hpi-vmXX.epc.ext.hpe.com -m setup | grep distribution
[admXX@hpi-vmjump ansible-files]$ ansible all -m setup -a 'filter=ansible_distribution' -o

10.2. Using Facts in Playbooks

Facts can be used in a Playbook like variables, using the proper naming, of course. Create this Playbook as facts.yml in the ~/ansible-files/ directory:

---
- name: Output facts within a playbook
  hosts: all
  tasks:
  - name: Prints Ansible facts
    debug:
      msg: The default IPv4 address of {{ ansible_fqdn }} is {{ ansible_default_ipv4.address }}
The "debug" module is handy for e.g. debugging variables or expressions.

Execute it to see how the facts are printed:

[admXX@hpi-vmjump ansible-files]$ ansible-playbook facts.yml

PLAY [Output facts within a playbook] ******************************************************************************

TASK [Gathering Facts] ******************************************************************************
ok: [hpi-vmjump.epc.ext.hpe.com]
ok: [hpi-vmXX.epc.ext.hpe.com]

TASK [Prints Ansible facts] ******************************************************************************
ok: [hpi-vmjump.epc.ext.hpe.com] => {
    "msg": "The default IPv4 address of hpi-vmjump.epc.ext.hpe.com is 192.168.25.200"
}
ok: [hpi-vmXX.epc.ext.hpe.com] => {
    "msg": "The default IPv4 address of hpi-vmXX.epc.ext.hpe.com is 192.168.25.XX"
}

PLAY RECAP ******************************************************************************
hpi-vmjump.epc.ext.hpe.com : ok=2    changed=0    unreachable=0    failed=0
hpi-vmXX.epc.ext.hpe.com : ok=2    changed=0    unreachable=0    failed=0

10.3. Custom Facts

But Ansible wouldn’t be Ansible if facts could not be extended with own facts. Ansible looks for custom facts on managed nodes in /etc/ansible/facts.d/. Files containing fact definitions must have .fact as an extension. A facts file is a plain-text file in INI or JSON format. For both formats, the result returned by Ansible is the same and will be put in the ansible_local level.

To use custom facts:

  • A facts file must exist on a managed node.

  • After Ansible finds the custom facts, they can be used like regular facts.

What better then to have Ansible create the facts file on a host? But you could of course put it there using other mechanisms (e.g. Kickstart) as well.

  • Create a file custom.fact with the following content in your ~/ansible-files/ directory:

[general]
package = httpd
service = httpd
state = started
  • Create a setup_facts.yml Playbook to create the directory and copy the file to the managed nodes:

---
- name: Install remote facts
  hosts: hpi-vmXX.epc.ext.hpe.com
  vars:
    remote_dir: /etc/ansible/facts.d
    facts_file: custom.fact
  tasks:
    - name: Create the remote directory
      file:
        state: directory
        recurse: yes
        path: "{{ remote_dir }}"
    - name: Install the new facts
      copy:
        src: "{{ facts_file }}"
        dest: "{{ remote_dir }}"

Understand everything the Playbook does:

  • The host to run on is defined

  • Variables are defined

  • Two tasks are defined, the first makes sure the fact directory exists, the second copies the fact file

Before running the Playbook, have a look at the facts in the ansible_local level, it should be empty:

[admXX@hpi-vmjump ansible-files]$ ansible hpi-vmXX.epc.ext.hpe.com -m setup -a 'filter=ansible_local'
hpi-vmXX.epc.ext.hpe.com | SUCCESS => {
    "ansible_facts": {},
    "changed": false
}

Now run the Playbook to make the custom facts available on host1:

[admXX@hpi-vmjump ansible-files]$ ansible-playbook setup_facts.yml

Uhhh, that didn’t look good, lots of errors. But you should by now know whats missing in the Playbook, fix and run it again (hint: priviledges). After a successful run the custom facts directory and the fact file are available on the node.

Run the Ansible command to query the custom facts again:

[admXX@hpi-vmjump ansible-files]$ ansible hpi-vmXX.epc.ext.hpe.com -m setup -a 'filter=ansible_local'
hpi-vmXX.epc.ext.hpe.com | SUCCESS => {
    "ansible_facts": {
        "ansible_local": {
            "custom": {
                "general": {
                    "package": "httpd",
                    "service": "httpd",
                    "state": "started"
                }
            }
        }
    },
    "changed": false
}

You can see how your custom facts are now integrated in the facts output and how the section title ("general") from your facts file is integrated as well.

As an example of how custom facts can be used, create and run the following Playbook as use_facts.yml:

---
- name: Install package named by fact and start the service
  hosts: hpi-vmXX.epc.ext.hpe.com
  become: yes
  tasks:
   - name: Install the required package
     yum:
       name: "{{ ansible_local.custom.general.package }}"
       state: latest
   - name: Start the service
     service:
       name: "{{ ansible_local.custom.general.service }}"
       state: "{{ ansible_local.custom.general.state }}"

Of course there should be no changes because Apache was already installed and running. But you can see how you could use your custom facts as variables in a Playbook.

11. Controlling Task Execution

11.1. Loops

Often you’ll want to do many things in one task, such as

  • create a lot of users

  • install a lot of packages

  • repeat a polling step until a certain result is reached.

Ansible supports loops to iterate over a set of values and saves administrators from writing repetitive tasks that use the same module.

Ansible supports three types of loops: simple loops, list of hashes and nested loops. In this lab we’ll have a quick look at the first two:

11.1.1. Simple Loops

Simple loops are a list of items that Ansible iterates over. They are defined by providing a list of items to the with_items keyword. Create the following Playbook and run it:

---
- name: Loop demo
  hosts: hpi-vmXX.epc.ext.hpe.com
  tasks:
    - name: Check if service is started
      service:
        name: "{{ item }}"
        state: started
      with_items:
        - httpd
        - sshd

The list of items to loop over could also be supplied as an array in the vars section or in a file. In this example the array is called check_services. Create this Paybook and run it:

---
- name: Loop demo
  hosts: hpi-vmXX.epc.ext.hpe.com
  vars:
    check_services:
      - httpd
      - sshd
  tasks:
    - name: Check if service is started
      service:
        name: "{{ item }}"
        state: started
      with_items: "{{ check_services }}"

11.1.2. Hashes

When passing arrays as arguments, the array can be a list of hashes. The following Playbook shows how an array with key-pair values is passed to the user module:

---
- name: Hash demo
  hosts: hpi-vmXX.epc.ext.hpe.com
  become: yes
  tasks:
    - name: Create users from hash
      user:
        name: "{{ item.name }}"
        state: present
        groups: "{{ item.groups }}"
      with_items:
        - { name: 'jane', groups: 'wheel' }
        - { name: 'joe', groups: 'root' }

11.1.3. Nested Loops

A bit more complicated are nested loop. To get an idea of how they work here is an example you can run:

---
- name: Nested loop demo
  hosts: hpi-vmXX.epc.ext.hpe.com
  become: no
  tasks:
    - name: Loop over the nested items
      shell: echo "nested test a={{ item[0] }} b={{ item[1] }} c={{ item[2] }}"
      with_nested:
        - [ 'red', 'blue' ]
        - [ 1, 2 ]
        - [ 'up', 'down']
There is a lot more you can do with Ansible loops: http://docs.ansible.com/ansible/playbooks_loops.html

11.2. Conditionals

Often the result of a play may depend on the value of a variable, fact, or previous task result. In some cases, the values of variables may depend on other variables. Or additional groups can be created to manage hosts based on whether the hosts match other criteria.

  • Example: a conditional could be used to install a service only if a certain amount of memory is available.

11.2.1. Ansible when statement

To implement a conditional, the when statement is used, followed by the condition to test. The condition is expressed using one of the available operators like e.g. for comparison:

==

Compares two objects for equality.

!=

Compares two objects for inequality.

>

true if the left hand side is greater than the right hand side.

>=

true if the left hand side is greater or equal to the right hand side.

<

true if the left hand side is lower than the right hand side.

< =

true if the left hand side is lower or equal to the right hand side.

For more on this, please refer to the documentation: http://jinja.pocoo.org/docs/2.9/templates/

11.2.2. Conditionals Example

As an example you would like to install an FTP server, but only on hosts that are in the "ftpserver" inventory group.

as user admXX create this Playbook on hpi-vmjump.epc.ext.hpe.com as ftpserver.yml in the ~/ansible-files/ directory, run it and examine the output:

---
- name: Install vsftpd on ftpservers
  hosts: all
  become: yes
  tasks:
    - name: Install FTP server when host in ftpserver group
      yum:
        name: vsftpd
        state: latest
      when: inventory_hostname in groups["ftpserver"]
The when statement must be placed "outside" of the module by being indented at the top level of the task.

Expected outcome: The task is skipped on hpi-vmjump.epc.ext.hpe.com because it is not in the ftpserver group in your inventory file:

[...]
TASK [Install FTP server when host in ftpserver group] *************************
skipping: [hpi-vmjump.epc.ext.hpe.com]
changed: [hpi-vmXX.epc.ext.hpe.com]
[...]

11.2.3. Challenge Lab: Fact in Conditional

Admittedly using an inventory group as a condition is the most basic case you would expect to just work. Let’s try something a bit more interesting:

You might have noticed host1 and host2 have different amounts of RAM. If not have another look at the facts:

[admXX@hpi-vmjump ansible-files]$ ansible all -m setup -a 'filter=ansible_*_mb'

Write a Playbook mariadb.yml that installs MariaDB but only if the host has more then, say, 6000 MB of RAM.

  • Find the fact for memtotal in MB (look at the ad hoc command output and feel free to use "grep").

  • Use this Playbook as a template and create the when statement by replacing the upper case placeholders:

In a when statement facts and variables are not to be inclosed in double curly braces like you would do for variables!
---
- name: MariaDB server installation
  hosts: all
  become: yes
  tasks:
  - name: Install latest MariaDB server when host RAM greater 6000 MB
    yum:
      name: mariadb-server
      state: latest
    when: FACT COMPARISON_OPERATOR NUMBER
  • Run the Playbook. As a result the installation task should be skipped on host2.

Solution below!
---
- name: MariaDB server installation
  hosts: all
  become: yes
  tasks:
  - name: Install latest MariaDB server when host RAM greater 6000 MB
    yum:
      name: mariadb-server
      state: latest
    when: ansible_memtotal_mb > 6000

11.3. Ansible Handlers

Sometimes when a task does make a change to the system, a further task may need to be run. For example, a change to a service’s configuration file may then require that the service be reloaded so that the changed configuration takes effect.

Here Ansible’s handlers come into play. Handlers can be seen as inactive tasks that only get triggered when explicitly invoked using the "notify" statement.

As a an example, let’s write a Playbook that:

  • manages Apache’s configuration file httpd.conf on all hosts in the webserver group

  • restarts Apache when the file has changed

First we need the file Ansible will deploy, let’s just take the one from hpi-vmjump.epc.ext.hpe.com:

[admXX@hpi-vmjump ansible-files]$ cp /etc/httpd/conf/httpd.conf .

Then create the Playbook httpd_conf.yml:

---
- name: manage httpd.conf
  hosts: webserver
  become: yes
  tasks:
  - name: Copy Apache configuration file
    copy:
      src: httpd.conf
      dest: /etc/httpd/conf/
    notify:
       - restart_apache
  handlers:
    - name: restart_apache
      service:
        name: httpd
        state: restarted

So what’s new here?

  • The "notify" section calls the handler only when the copy task changed the file.

  • The "handlers" section defines a task that is only run on notification.

Run the Playbook. We didn’t change anything in the file yet so there should not be any changed lines in the output and of course the handler shouldn’t have fired.

  • Now change the Listen 80 line in httpd.conf to:

Listen 8080
  • Run the Playbook again. Now the Ansible’s output should be a lot more interesting:

    • httpd.conf should have been copied over

    • The handler should have restarted Apache

Apache should now listen on port 8080. Easy enough to verify:

[admXX@hpi-vmjump ansible-files]$ curl http://hpi-vmXX.epc.ext.hpe.com

curl: (7) Failed connect to hpi-vmXX.epc.ext.hpe.com:80; Connection refused
[admXX@hpi-vmjump ansible-files]$ curl http://hpi-vmXX.epc.ext.hpe.com:8080

<body>
<h1>This is a production webserver, take care!</h1>
</body>

Feel free to change the httpd.conf file again and run the Playbook.

11.4. Ansible Blocks

Ansible 2.0 added the "block" feature to allow for grouping of tasks and to help in play error handling. Most of what you can apply to a single task can be applied at the block level.

Example: Combined with a "when" conditional blocks can be used to execute a set of tasks with different parameters depending on operating system or memory size.

Create block_example2.yml and run it:

---
- hosts: all
  tasks:
    - block:
      - yum:
          name: httpd
          state: installed
      when: ansible_os_family == 'RedHat'
      become: yes

    - block:
      - apt:
          name: apache2
          state: installed
      when: ansible_os_family == 'Debian'
      become: yes

11.4.1. Blocks in Error Handling

Maybe even more important is the ability to use blocks for error handling, much like exceptions in programming. This is done by defining a main set of tasks and an extra set of tasks that will be only executed if the main set fails.

Here is a nice example for block error handling right from the Ansible documentation (It’s not a complete Playbook, so don’t try to run it as is):

  tasks:
   - name: Attempt and gracefull roll back demo
     block:
       - debug: msg=I execute normally
       - command: /bin/false
       - debug: msg=I never execute, due to the above task failing
     rescue:
       - debug: msg=I caught an error
       - command: /bin/false
       - debug: msg=I also never execute :-(
     always:
       - debug: msg="this always executes"
  • The tasks in the block would execute normally

  • If there is any error (and there will because of the /bin/false command) the rescue section would get executed

  • The always section runs no matter what previous error did or did not occur in the block and rescue sections.

11.4.2. Error Handling Lab

As an example of how blocks can help with error handling write a Playbook that:

  • tries to activate an not-installed Apache module with apache2_module

  • on fail stops Apache via rescue

Create the Playbook as block_error.yml and run it.

---
- name: Enable Apache2 module
  hosts: hpi-vmXX.epc.ext.hpe.com
  become: yes
  vars:
    apache_module: wsgi
  tasks:
  - block:
      - name: Enable Apache module
        apache2_module:
          state: present
          name: "{{apache_module}}"
    rescue:
      - name: rescue stop Apache
        service:
          name: httpd
          state: stopped
    always:
      - debug:
          msg: >
            "This is always executed"

Expected outcome:

  • The block with the apache2_module task fails

  • The rescue task is executed and Apache gets stopped

  • The always task is executed

Why not go and check that Apache is actually stopped using an quick ad hoc command? Just to stay busy? :-)

12. Ansible Templates

Ansible uses Jinja2 templating to modify files before they are distributed to managed hosts. Jinja2 is a templating language for Python with a wealth of features. In the context of Ansible loops and conditionals can be used in templates, but probably the most common use is to reference variables which get replaced with the values of facts.

12.1. Using Templates in Playbooks

When a template for a file has been created, it can be deployed to the managed hosts using the template module, which supports the transfer of a local file from the control node to the managed hosts.

As an example of using templates you will change the motd file to contain host-specific data.

In the ~/ansible-files/ directory on hpi-vmjump.epc.ext.hpe.com as user admXX create the template file motd-facts.j2:

Welcome to {{ ansible_hostname }}.
{{ ansible_distribution }} {{ ansible_distribution_version}}
deployed on {{ ansible_architecture }} architecture.

In the ~/ansible-files/ directory on hpi-vmjump.epc.ext.hpe.com as user admXX create the Playbook motd-facts.yml:

---
- name: Fill motd file with host data
  hosts: hpi-vmXX.epc.ext.hpe.com
  become: yes
  tasks:
    - template:
        src: motd-facts.j2
        dest: /etc/motd
        owner: root
        group: root
        mode: 0644

You have done this a couple of times by now:

  • Understand what the Playbook does.

  • Execute the Playbook motd-facts.yml

  • Login to hpi-vmXX.epc.ext.hpe.com via SSH and check the motto of the day message.

  • Log out of hpi-vmXX.epc.ext.hpe.com

You should see how Ansible replaces the variables with the facts it discovered from the system.

12.2. Challenge Lab: Templates

Change the template to use the FQDN hostname:

  • Find a fact that contains the fully qualified hostname using the commands you learned in the "Ansible Facts" chapter.

Do a grep -i for fqdn
  • Change the template to use the fact you found.

  • Run the Playbook again.

  • Check motd by logging in to hpi-vmXX.epc.ext.hpe.com

Solution below!
  • Find the fact:

[admXX@hpi-vmjump ansible-files]$ ansible hpi-vmXX.epc.ext.hpe.com -m setup | grep -i fqdn
  • Use the ansible_fqdn fact in the template motd-facts.j2.

12.3. Advanced Templates

As mentioned Jinja2 supports a lot more then just referencing variables. The following template showcases some of these advanced features.

  • ansible_managed is a special variable that can be added to a file by a template to show this file was created by ansible.

  • You can lookup values for other hosts

  • You can loop over values, e.g. over all hosts in an Ansible groups

### {{ ansible_managed }}

### My host name
My non-FQDN hostname is {{ ansible_hostname }}.

### What is the management host
 IP is: {{ hostvars['hpi-vmjump.epc.ext.hpe.com']['ansible_default_ipv4']['address'] }}

### What else is here?
The machines in group webserver are:
{% for host in groups['webserver'] %}
- {{ hostvars[host]['inventory_hostname'] }}

{% if host | match('hpi-vmXX.epc.ext.hpe.com') %}
hpi-vmXX.epc.ext.hpe.com MEM: {{ ansible_memfree_mb }}
{% endif %}
{% endfor %}

We also support external lookups: {{ lookup('dig', 'redhat.com./MX') }}
  • Create this template as advanced_template.j2 on hpi-vmjump.epc.ext.hpe.com.

  • Create a Playbook to test it:

---
- name: advanced template test
  hosts: all
  tasks:
  - name: template vhost file
    template:
      src: advanced_template.j2
      dest: "{{ ansible_user_dir }}/advanced_template.out"
      owner: "{{ ansible_user_id }}"
      group: "{{ ansible_user_gid }}"
      mode: 0644
  • Run the Playbook

  • Use an Ansible ad hoc command to have a look at the output file.

13. Ansible Inclusions & Roles

13.1. Structuring Ansible Playbooks with includes and roles

While it is possible to write a Playbook in one very large file, eventually you’ll want to reuse files and start to organize things. At a basic level, including task files allows you to break up bits of configuration policy into smaller files. Task includes pull in tasks from other files. Since handlers are tasks too, you can also include handler files.

When you start to think about it – tasks, handlers, variables, and so on – begin to form larger concepts. You start to think about modeling what something is,

  • It’s no longer "apply THIS to these hosts"

  • You say "these hosts are dbservers" or "these hosts are webservers".

Roles build on the idea of include files and provide Ansible with a way to load tasks, handlers, and variables from external files. The files that define a role have specific names and are organized in a rigid directory structure.

Use of Ansible roles has the following benefits:

  • Roles group content, allowing easy sharing of code with others

  • Roles can be written that define the essential elements of a system type: web server, database server…​

  • Roles make larger projects more manageable

  • Roles can be developed in parallel by different administrators

We’ll start with understanding includes so roles make more sense, but our ultimate goal should be understanding roles – roles are great and you should use them every time you write Playbooks.

13.2. Ansible Inclusions

Tasks can be included in a Playbook from an external file by using the include directive.

tasks:
  - name: Include tasks to install the database server
    include: tasks/db_server.yml

The include_vars module can include variables defined in either JSON or YAML files, overriding host variables and Playbook variables already defined.

tasks:
  - name: Include the variables from a YAML or JSON file
    include_vars: vars/variables.yml

13.2.1. Lab: Inclusions

Define the paths.yml variables file that sets some system paths and uses a fact.

---
paths:
  fileserver: /home/ansible/srv/filer/{{ ansible_fqdn }}
  dbpath: /home/ansible/srv/database/{{ ansible_fqdn }}

Create the fileservers.yml Playbook and include the paths.yml variables file. The fileserver structure will be created using the variable defined previously in the paths.yml variables file.

---
- hosts: webserver
  tasks:
  - name: Imports the variables file
    include_vars: paths.yml
  - name: Creates the remote directory
    file:
      path: "{{ paths.fileserver }}"
      state: directory
      mode: 0755
    register: result
  - name: Debugs the results
    debug:
      var: result

Run the fileservers.yml Playbook and examine the output.

The output shows the directory structure that has been created by Ansible, which matches the path that has been set by the paths.fileserver variable.

13.3. Working with Roles

13.3.1. Ansible Role Structure

Roles are basically automation around include directives as described above, and really don’t contain much additional magic beyond some improvements to search path handling for referenced files.

Roles follow a defined directory structure, a role is named by the top level directory. Some of the subdirectories contain YAML files, named main.yml. The files and templates subdirectories can contain objects referenced by the YAML files.

An example project structure could look like this, the name of the role would be "user":

user/
├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── README.md
├── tasks
│   └── main.yml
├── templates
├── tests
│   ├── inventory
│   └── test.yml
└── vars
    └── main.yml

13.3.2. Using Variables and Defaults in Roles

Role variables are defined by creating a vars/main.yml file. They are referenced in the role YAML file like any other variable: {{ var }}.

These variables have a high priority and can not be overridden by inventory variables.

Default variables allow default values to be set for variables of included or dependent roles. They are defined by creating a defaults/main.yml.

Default variables have the lowest priority of any variable. They can be overridden by other variable.
Define a specific variable in either vars/main.yml or defaults/main.yml, but not in both places. Default variables should be used when it is intended that their values will be overridden.

13.3.3. Using Roles in a Playbook

Using roles in a Playbook is straight forward:

---
- hosts: remote.example.com
  roles:
    - role1
    - role2

For each role, the role tasks, role handlers, role variables, and role dependencies will be included in the Playbook, in that order. Any copy, script, template, or include tasks in the role can reference the relevant files, templates, or tasks without absolute or relative path names. Ansible will look for them in the role’s files, templates, or tasks respectively, based on their use.

13.3.4. Lab: Roles

Ansible looks for roles in a subdirectory called roles in the project directory. This can be overridden in the Ansible configuration. Each role has its own directory. To ease creation of a new role the tool ansible-galaxy can be used.

Ansible Galaxy is your hub for finding, reusing and sharing the best Ansible content. ansible-galaxy helps to interact with Ansible Galaxy. For now we’ll just using it as a helper to build the directory structure.

Okay, lets start to build a role. We’ll build a role that installs and configures Apache to serve a virtual host. Run these commands in your "ansible-files" directory:

[admXX@hpi-vmjump ansible-files]$ mkdir roles
[admXX@hpi-vmjump ansible-files]$ ansible-galaxy init --offline roles/apache_vhost

Have a look at the role directories and their content:

[admXX@hpi-vmjump ~]$ cd ansible-files
[admXX@hpi-vmjump ansible-files]$ tree roles
Create the tasks file

The main.yml file in the tasks subdirectory of the role should do the following:

  • Make sure httpd is installed

  • Make sure httpd is started and enabled

  • Put HTML content into the Apache document root

  • Install the template provided to configure the vhost

The main.yml (and other files possibly included by main.yml) can only contain tasks, not complete Playbooks!
Some of these tasks have been done in other parts of the lab, don’t worry, it’s about the learning experience…​ ;-)

You have already learned about the modules and how to use them, so try to get the tasks file done alone. With some hints from your friendly lab guide. ;-)

Edit the tasks/main.yml file

[admXX@hpi-vmjump ansible-files]$ vi roles/apache_vhost/tasks/main.yml

The structure of the file should be like this, you will have to fill in the BLANKs:

---
# tasks file for setting up Apache vhost
- name: install httpd
<BLANK>

- name: start and enable httpd service
<BLANK>

- name: deliver html content
<BLANK>

- name: template vhost file
<BLANK>

Add the instructions needed to:

  • Install the httpd package using the yum module

  • Use the service module to enable and start httpd

  • Make sure the needed directory structure exists (/var/www/vhosts/{{ ansible_hostname }})

    • Hint: Use the file module)

  • Use the copy module to copy HTML from the role to the vhost DocumentRoot, example:

src: html/index.html
dest: "/var/www/vhosts/{{ ansible_hostname }}"
  • Use the template module to create the vhost configuration file from a j2-template, hints:

  src: vhost.conf.j2
  dest: /etc/httpd/conf.d/vhost.conf
  owner: root
  group: root
  mode: 0644
notify:
  - restart_httpd

Note it is using a handler to restart httpd after an confguration update.

Do not forget this is just the tasks and not a complete Playbook!

The solution for the tasks file can be found below

Create the handler

Create the handler in the file handlers/main.yml to restart httpd when notified by the template task:

---
# handlers file for apache_vhost
- name: restart_httpd
  service:
    name: httpd
    state: restarted
Create the index.html and vhost configuration file template

Create the HTML content that will be served by the webserver.

  • The role task that called the copy module referred to an html directory "src/". Create this directory below the files subdirectory of the role:

[admXX@hpi-vmjump ansible-files]$ mkdir roles/apache_vhost/files/html
  • Create an index.html file in the "src" directory:

[admXX@hpi-vmjump ansible-files]$ echo 'simple vhost index' > roles/apache_vhost/files/html/index.html
  • Create the vhost.conf.j2 template file in the role’s templates subdirectory.

# {{ ansible_managed }}

Listen 8080 # If not in /etc/httpd/conf/httpd.conf

<VirtualHost *:8080>
    ServerAdmin webmaster@{{ ansible_fqdn }}
    ServerName {{ ansible_fqdn }}
    ErrorLog logs/{{ ansible_hostname }}-error.log
    CustomLog logs/{{ ansible_hostname }}-common.log common
    DocumentRoot /var/www/vhosts/{{ ansible_hostname }}/

    <Directory /var/www/vhosts/{{ ansible_hostname }}/>
	Options +Indexes +FollowSymlinks +Includes
	Order allow,deny
	Allow from all
    </Directory>
</VirtualHost>
Test the role

You are ready to test the role against hpi-vmXX.epc.ext.hpe.com. First create the following Playbook as test_apache_role.yml:

---
- name: use apache_vhost role playbook
  hosts: hpi-vmXX.epc.ext.hpe.com
  become: yes

  pre_tasks:
    - debug:
        msg: 'Beginning web server configuration.'

  roles:
    - apache_vhost

  post_tasks:
    - debug:
        msg: 'Web server has been configured.'
Note the pre_tasks and post_tasks tasks. Normally, the tasks of roles execute before the tasks of the Playbook. To control order of execution pre_tasks tasks are performed before any roles are applied. The post_tasks tasks are performed after all the roles have completed.

Now you are ready to run your Playbook:

[admXX@hpi-vmjump ansible-files]$ ansible-playbook test_apache_role.yml

Run ad hoc commands to confirm that the role worked:

[admXX@hpi-vmjump ansible-files]$ ansible hpi-vmXX.epc.ext.hpe.com -a 'yum list installed httpd'
[admXX@hpi-vmjump ansible-files]$ ansible hpi-vmXX.epc.ext.hpe.com -a 'cat /etc/httpd/conf.d/vhost.conf'
[admXX@hpi-vmjump ansible-files]$ ansible hpi-vmXX.epc.ext.hpe.com -a 'cat /var/www/vhosts/host2/index.html'
[admXX@hpi-vmjump ansible-files]$ curl -s http://hpi-vmXX.epc.ext.hpe.com:8080

All looking good? Congratulations!

Solution Below!
[admXX@hpi-vmjump ansible-files]$ cat roles/apache_vhost/tasks/main.yml
---
# tasks file for setting up Apache vhost
- name: install httpd
  yum:
    name: httpd
    state: latest

- name: deliver html content
  copy:
    src: html/
    dest: "/var/www/vhosts/{{ ansible_hostname }}"

- name: template vhost file
  template:
    src: vhost.conf.j2
    dest: /etc/httpd/conf.d/vhost.conf
    owner: root
    group: root
    mode: 0644
  notify:
    - restart_httpd

- name: start and enable httpd service
  service:
    name: httpd
    state: started
    enabled: true

14. Continue with available roles

14.1. Find available Roles

On Ansible Galaxy a lot of ready to use roles exist. Red Hat maintaines the linux-system-roles, which are upstream to supported RHEL System Roles.

See the follwoing pages for more details:

Now find the roles that are useful for installing SAP HANA on http://galaxy.ansible.com

Search for "SAP" and you will find the following roles:

These and some other userful useful roles can be found here: Roles Overview

14.2. Install SAP HANA using these Roles

Install the above roles on hpi-vmjump.epc.ext.hpe.com as admXX. Get familiar with these roles. e.g. read the documentation of each of the above role and browse the roles itself

[admXX@hpi-vmjump ~]$ ansible-galaxy install mk-ansible-roles.saphana-deploy

[admXX@hpi-vmjump ~]$ ansible-galaxy list
- linux-system-roles.kdump, (unknown version)
- linux-system-roles.network, (unknown version)
- linux-system-roles.postfix, (unknown version)
- linux-system-roles.selinux, (unknown version)
- linux-system-roles.timesync, (unknown version)
- rhel-system-roles.kdump, (unknown version)
- rhel-system-roles.network, (unknown version)
- rhel-system-roles.postfix, (unknown version)
- rhel-system-roles.selinux, (unknown version)
- rhel-system-roles.timesync, (unknown version)
- mk-ansible-roles.saphana-deploy, master
- mk-ansible-roles.saphana-preconfigure, master
the role saphana-deploy is dependant on saphana-preconfigured. So it is installed automatically
If you run ansible-galaxy install from the directory that contains an ansible.cfg file the default installation path is ${HOME}/.ansible/roles, otherwise the roles_path setting from /etc/ansible/ansible.cfg is used.
Global roles are installed (as root) to /usr/share/ansible/roles, while local roles are installed to ${HOME}/.ansible/roles

Now go ahead an read the Readme of saphana-preconfigure role either on the web (easier to read) or on the commandline:

[admXX@hpi-vmjump ~]$ ansible-galaxy info mk-ansible-roles.saphana-preconfigure
[admXX@hpi-vmjump ~]$ ansible-galaxy info mk-ansible-roles.saphana-deploy

14.2.1. Information you should use in your playbooks_loops

Now write playbook to prepare hpi-vmXX.epc.ext.hpe.com for HANA installation. The following preconditions are

  • The servers are already suscribed to the correct channels. You can verify this with yum repolist (use can the ansible ad-hoc command)

  • Timeservers can be ignored (single instance install)

  • SAP Installation media is served via NFS from infshare.epc.ext.hpe.com:/Install/SAP/HANA and mount it to /install. Then the subdirectory 51052325 contains HANA 2.0 SPS2

    install_nfs: infshare.epc.ext.hpe.com:/Install/SAP/HANA
    installroot: /install
    hana_installdir: /install/51052325
  • Use the following information for installation:

    hana_pw_hostagent_ssl: "Ab01%%bA"
    id_user_sapadm: "30200"
    id_group_shm: "30220"
    id_group_sapsys: "30200"
    pw_user_sapadm_clear: "Adm12356"
This information can also go to an appropriate group_vars file
  • With that information you can run the prepare of the hana installation in your playbook.

  • for the deploy role you need to add the instance specific parameters in the according host_vars file:

    • The first parameter to set is the hostname/interfacename of the interface SAP hostagent will use to talk. If you just have one interface use "{{ ansible_hostname }}" as default values

      hostname: "{{ ansible_hostname }}"
    • The second parameter to set, is whether you want to prepare the installation or execute the installation.

      deployment_instance: true
    • Now describe your instance. These variables are similar to the unattended install file:

      instances:
        hxe:
          id_user_sidadm: "30210"
          pw_user_sidadm: "Adm12356"
          hana_pw_system_user_clear: "System123"
          hana_components: "client,server"
          hana_system_type: "Master"
          id_group_shm: "30220"
          hana_instance_hostname: "{{ ansible_hostname }}"
          hana_addhosts:
          hana_sid: HXE
          hana_instance_number: 90
          hana_system_usage: custom
we use SID HXE and instance number 90 as this is the default for HANA Express, in case you want to deploy HANA Express with these playbooks
In case of deploying a HANA scale-out cluster only one server must have deployment_instance: true, all other
If you want to install multiple HANA instances on one server you can add mor than one instance here and the installer will loop over these instances.

Now create your playbook and run the installation. Log into hpi-vmXX.epc.ext.hpe.com and assume user hxeadm to see if Hana is running:

hxeadm@hpi-vmXX:/usr/sap/HXE/HDB90> HDB info
USER       PID  PPID %CPU    VSZ   RSS COMMAND
hxeadm   30265 30264  0.2 116188  2796 -bash
hxeadm   30340 30265  0.0 113268  1632  \_ /bin/sh /usr/sap/HXE/HDB90/HDB info
hxeadm   30371 30340  0.0 151068  1792      \_ ps fx -U hxeadm -o user,pid,ppid,pcpu,vsz,rss,args
hxeadm   28886     1  0.0  23640  1668 sapstart pf=/hana/shared/HXE/profile/HXE_HDB90_hpi-vmXX
hxeadm   28895 28886  0.1 541684 31480  \_ /usr/sap/HXE/HDB90/hpi-vmXX/daemon.ini p
hxeadm   28911 28895  109 7352956 5635720      \_ hdbnameserver
hxeadm   29130 28895  5.9 1715224 270664      \_ hdbcompileserver
hxeadm   29132 28895 67.9 2492092 1144700      \_ hdbpreprocessor
hxeadm   29207 28895  112 7812176 6165120      \_ hdbindexserver -port 39003
hxeadm   29209 28895  4.2 2715764 964916      \_ hdbxsengine -port 39007
hxeadm   29544 28895  3.8 2014788 280896      \_ hdbwebdispatcher
hxeadm   28794     1  1.3 502428 22792 /usr/sap/HXE/HDB90/exe/sapstartsrv pf=/hana/shared/HXE/profile/HXE_HDB90_hpi-vmXX -D -u hxeadm
Solution Below
  • The required playbook: ./install-hana.yml:

- name: Install SAP HANA
  hosts: hpi-vmXX.epc.ext.hpe.com
  become: yes

  vars:
              # SAP Precoonfigure role
              # SAP-Media Check
              install_nfs: infshare.epc.ext.hpe.com:/Install/SAP/HANA
              installroot: /install
              hana_installdir: /install/51052325

              hana_pw_hostagent_ssl: "MyS3cret!"
              id_user_sapadm: "30200"
              id_group_shm: "30220"
              id_group_sapsys: "30200"
              pw_user_sapadm_clear: "MyS3cret!"

  roles:
              - { role: mk-ansible-roles.saphana-preconfigure }
              - { role: mk-ansible-roles.saphana-deploy }
  • The required host_vars file: ./host_vars/hpi-vm__XX__.epc.ext.hpe.com:

---
hostname: "{{ ansible_hostname }}"
deployment_instance: true

instances:
  hxe:
    id_user_sidadm: "30210"
    pw_user_sidadm: "Adm12356"
    hana_pw_system_user_clear: "System123"
    hana_components: "client,server"
    hana_system_type: "Master"
    id_group_shm: "30220"
    hana_instance_hostname: "{{ ansible_hostname }}"
    hana_addhosts:
    hana_sid: HXE
    hana_instance_number: 90
    hana_system_usage: custom
...

15. Bonus Labs

15.1. Install and configure Insights

If you have access to your own subscription run subscription-manager unregister and register the server against your subscription. Then follow the instructions of the getting started guide

Solution Below

As we are in an ansible training we use an ansible playbook to add insights.

  1. Install the insights role from galaxy:

    # ansible-galaxy install redhataccess.redhat-access-insights-client
  2. Create a playbook install-insights.yml to install to configure Insights

    # Playbook installing Insights
    ---
    - hosts: hpi-vmXX.epc.ext.hpe.com
      become: yes
      roles:
      - { role: redhataccess.redhat-access-insights-client, when: ansible_os_family == 'RedHat' }
  3. Run the playbook

    # ansible-playbook install-insights.yml
  4. Goto the portal to see the results or in case you use Satellite, ask your instructor to show the result

15.2. Update HANA Server

When you update a HANA server it is recommended to shutdown the database before rebooting the server.

So basically follow these steps:

  • Make sure you have your release set to the current minor release:

    [root@hana1-GUID ~]# subscription-manager release
    Release: 7.4
  • update the system

    [root@hpi-vmXX ~]# yum -y update
  • stop the HANA database

    [root@hpi-vmXX ~]# su - hxeadm
    hxeadm@hpi-vmXX:/usr/sap/HXE/HDB90> HDB stop
  • reboot

    [root@hpi-vmXX ~]# reboot
  • login and start HANA again (in case it is not started automatically, which is the default)

    [root@hpi-vmXX ~]# su - hxeadm
    hxeadm@hpi-vmXX:/usr/sap/HXE/HDB90> HDB start

Write your playbook to update the HANA Server.

- name: Update Hana Server
  hosts: hpi-vmXX.epc.ext.hpe.com
  become: yes

  vars:
              sid: hxe

    tasks:
              ## update the system
              - name: ensure the the system is updated
                yum: name=* state=latest

              ## stop database
              - name: ensure HANA is stopped
                command: su - "{{ sid + 'adm' }}" -c "HDB stop"

              # Reboot the server now and wait until it is back
              # inspired by https://support.ansible.com/hc/en-us/articles/201958037-Reboot-a-server-and-wait-for-it-to-come-back
              - name: restart machine
                shell: sleep 2 && shutdown -r now "Ansible updates triggered"
                async: 1
                poll: 0
                become: true
                ignore_errors: true

              - name: waiting for server to come back
                local_action: wait_for host={{ inventory_hostname }} port=22 state=started delay=90 sleep=2 timeout=900
                become: false

              ## start database again
              - name: ensure HANA is started
                command: su - "{{ sid + 'adm' }}" -c "HDB start"

15.3. Configure System Replication for HANA

Another galaxy role is available to automatically configure system replication. To use it run

[admXX@hpi-vmjump ansible-files]$ ansible-galaxy install mk-ansible-roles.saphana-hsr
- downloading role 'saphana-hsr', owned by mk-ansible-roles
- downloading role from https://github.com/mk-ansible-roles/saphana-hsr/archive/master.tar.gz
- extracting mk-ansible-roles.saphana-hsr to /home/admXX/.ansible/roles/mk-ansible-roles.saphana-hsr

Read the documentation of this role at https://galaxy.ansible.com/mk-ansible-roles/saphana-hsr/ and add the appropriate variables to your playbook and to your group_vars and host_vars files.

Solution below

First of all you need to update your inventory to contain both hana servers:

[all:vars]
ansible_ssh_user=ansible

[hana]
hpi-vmXX
hpi-vmYY

You can also use the short names because DNS is setup correctly.

Now you need to add the role to your playbook and change the hosts line from hpi-vmXX to hana (now it runs against a group):

- name: Install SAP HANA
  hosts: hana
  become: yes

  vars:
              # SAP Precoonfigure role
              # SAP-Media Check
              install_nfs: infshare.epc.ext.hpe.com:/Install/SAP/HANA
              installroot: /install
              hana_installdir: /install/51052325

              hana_pw_hostagent_ssl: "MyS3cret!"
              id_user_sapadm: "30200"
              id_group_shm: "30220"
              id_group_sapsys: "30200"
              pw_user_sapadm_clear: "MyS3cret!"

  roles:
              - { role: mk-ansible-roles.saphana-preconfigure }
              - { role: mk-ansible-roles.saphana-deploy }
              - { role: mk-ansible-roles.saphana-hsr }

and then update the host_vars files with the addtional configuration parameters:

For hpi-vmXX add to the host_vars config file:

hsr_deploy_type: enable

hsr:
  hxe:
    hana_instance_hostname: hpi-vmXX
    hana_sid: HXE
    hana_instance_number: 90
    hana_pw_system_user_clear: "System123"
    hsr_name: DC1
    hsr_type: PRIMARY
    hsr_configure: yes
    hsr_type_remote_host: hpi-vmYY
    hsr_operation_mode: logreplay
    hsr_replicationmode: sync
    hsr_backup_directory: /hana/shared/HXE/HDB90/backup/data

and for hpi-vmYY add to the host_vars config file :

hsr_deploy_type: register

hsr:
  hxe:
    hana_instance_hostname: hpi-vmYY
    hana_sid: HXE
    hana_instance_number: 90
    hana_pw_system_user_clear: "System123"
    hsr_name: DC2
    hsr_type: SECONDARY
    hsr_configure: yes
    hsr_type_remote_host: hpi-vmXX
    hsr_operation_mode: logreplay
    hsr_replicationmode: sync
    hsr_backup_directory: /hana/shared/HXE/HDB90/backup/data
{{ ansible_hostname }} resolves to the short name, so you should use short names here as well

You can now use these two systems to setup clustering. If you want to use pacemaker you can continue here, if you want to use ServiceGuard click here

16. Troubleshooting and Debugging Ansible

16.1. Debug module

You have used the Ansible debug module in some lab tasks already. It can provide the value for a certain variable at Playbook execution time.

The following examples show the use of the msg statement inside of the debug statement:

---
- name: test debug
  hosts: localhost
  gather_facts: yes
  vars:
    - myvar: variable_content

  tasks:
    - name: output myvar
      debug:
        msg: "myvar: {{ myvar }}"
    - name: output myvar only at higher verbosity
      debug:
        msg: "myvar verbosity 2 {{ myvar }}"
        verbosity: 2
    - name: output fact
      debug:
        msg="The free memory for this system is {{ ansible_memfree_mb }}"

Create and run the Playbook as debug.yml. The way we call the Playbook here is useful for testing purposes because we don’t need an inventory:

[admXX@hpi-vmjump ~]$ ansible-playbook -i localhost, -c local debug.yml

Now set the verbosity to "2" to see the additional output:

[admXX@hpi-vmjump ~]$ ansible-playbook -i localhost, -c local debug.yml -vv

16.2. Playbook Errors

Getting errors from Playbooks at execution time will happen frequently to you, mostly because of syntax errors in Playbooks or templates or network connectivity issues. To help with finding errors there are a couple of options:

Have Ansible step through the tasks interactively:

[admXX@hpi-vmjump ansible-files]$ ansible-playbook play.yml --step

Start execution from a given task:

[admXX@hpi-vmjump ansible-files]$ ansible-playbook play.yml --start-at-task="start httpd service"

Check the syntax of a Playbook:

[admXX@hpi-vmjump ansible-files]$ ansible-playbook play.yml --syntax-check

16.3. Keeping Ansible Files on Host

When Ansible executes a command on a remote host, usually a Python script is copied, executed and removed immediately. However, for debugging it might make sense to keep the script and execute it locally. Ansible can be persuaded to keep a script by setting the variable ANSIBLE_KEEP_REMOTE_FILES to true at the command line:

[admXX@hpi-vmjump ansible-files]$ ANSIBLE_KEEP_REMOTE_FILES=1 ansible-playbook playbook.yml -vvv
[ansible@host1]$ python /home/ansible/.ansible/tmp/ansible-tmp-<id>/command

Although the previously discussed tools can help to identify and fix issues in Playbooks, when developing Playbooks it is important to keep in mind some recommended practices for Playbook development:

  • Always name tasks, providing a description of the task’s purpose.

  • Include comments to add additional inline documentation about tasks.

  • Make use of vertical whitespace effectively. YAML syntax is mostly based on spaces, so avoid the usage of tabs in order to avoid errors.

  • Try to keep the Playbook as simple as possible. Only use the features that you need.

17. The End

Congratulations, you finished your labs! We hope you enjoyed your first steps using Ansible as much as we enjoyed creating the labs.