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
andvisudo
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/
runansible 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 thewebserver
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 templatemotd-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 subdirectory51052325
contains HANA 2.0 SPS2install_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 valueshostname: "{{ 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.
-
Install the insights role from galaxy:
# ansible-galaxy install redhataccess.redhat-access-insights-client
-
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' }
-
Run the playbook
# ansible-playbook install-insights.yml
-
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
|
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
16.4. Recommended Practices for Playbooks
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.