When dealing with extensive playbooks, it is easier to split the tasks into roles. This also helps in reusing the roles in the future. Roles are a collection of tasks, which can be moved from one playbook to another, can be run independently but only through a playbook file. In other words, roles are a feature of Ansible that facilitate reuse and further promote modularization of configuration.
The role is the primary mechanism for breaking a playbook into multiple files. Therefore, this simplifies writing complex playbooks, and it makes them easier to reuse. The breaking of playbook allows you to logically break the playbook into reusable components.
The ansible-galaxy init
command creates a directory structure for a new role that will be developed. The author and name of the role is specified as an argument to the command and it creates the directory structure in the current directory. ansible-galaxy
interacts with the Ansible Galaxy website API when it performs most operations. The --offline
option permits the init command to be used when Internet access is unavailable. To create a role using the ansible-galaxy command, we can simply use the below syntax in our terminal:
1 |
# ansible-galaxy init <ROLE_NAME> |
or
1 |
# ansible-galaxy init --offline <ROLE_NAME> |
Let’s create a role apache
in the control node:
1 2 |
[root@controlnode]# cd /etc/ansible/roles [root@controlnode roles]# ansible-galaxy init apache |
Running this command will give us a basic directory structure in our current working directory and this directory structure will be like the one below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
[root@controlnode apache]# tree . ├── defaults │ └── main.yml ├── files ├── handlers │ └── main.yml ├── meta │ └── main.yml ├── README.md ├── tasks │ └── main.yml ├── templates ├── tests │ ├── inventory │ └── test.yml └── vars └── main.yml |
defaults
: contains default variables for the role. Variables in default have the lowest priority so they are easy to override.files
: contains files which we want to be copied to the remote host. We don’t need to specify a path of resources stored in this directory.handlers
: contains handlers which can be invoked by “notify” directives and are associated with service.meta
: contains metadata of role like an author, support platforms, dependencies. Defines certain meta data for the role. Relevant meta data includes role dependencies and various role level configurations such asallow duplicates
. The meta-directory is entered via a main.ymitasks
: contains the main list of steps to be executed by the role.templates
: contains file template which supports modifications from the role. We use the Jinja2 templating language for creating templates.tests
: This directory can contain an inventory andtest.yml
playbook that can be used to test the role.vars
: contains variables for the role. Variables in vars have higher priority than variables in defaults directory.
Let’s create tasks for apache role:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
[miro@controlnode roles]$ cat /etc/ansible/roles/apache/tasks/main.yml --- # tasks file for apache - name: install apache yum: name=httpd state=latest - name: copy httpd.conf template template: src: httpd.conf.j2 dest: /etc/httpd/conf/httpd.conf notify: restart httpd - name: enable and start service service: name: httpd enabled: yes state: started |
Lets’s copy httpd.conf
file from apache and save as template httpd.conf.j2
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
[miro@controlnode apache]$ cat /etc/ansible/roles/apache/templates/httpd.conf.j2 ... # Example: # LoadModule foo_module modules/mod_foo.so # Include conf.modules.d/*.conf # # If you wish httpd to run as a different user or group, you must run # httpd as root initially and it will switch. # # User/Group: The name (or #number) of the user/group to run httpd as. # It is usually good practice to create a dedicated user and group for # running httpd, as with most system services. # User apache Group apache # 'Main' server configuration # # The directives in this section set up the values used by the 'main' # server, which responds to any requests that aren't handled by a # <VirtualHost> definition. These values also provide defaults for # any <VirtualHost> containers you may define later in the file. # # All of these directives may appear inside <VirtualHost> containers, # in which case these default settings will be overridden for the # virtual host being defined. # # # ServerAdmin: Your address, where problems with the server should be # e-mailed. This address appears on some server-generated pages, such # as error documents. e.g. admin@your-domain.com # ServerAdmin {{ apache_server_admin }} # # ServerName gives the name and port that the server uses to identify itself. # This can often be determined automatically, but we recommend you specify # it explicitly to prevent problems during startup. # # If your host doesn't have a registered DNS name, enter its IP address here. # #ServerName www.example.com:80 ... |
As we see the template has variable:
ServerAdmin {{ apache_server_admin }}
Now we can create a file with default variables:
1 2 3 4 5 |
[miro@controlnode apache]$ cat /etc/ansible/roles/apache/defaults/main.yml --- # defaults file for apache apache_server_admin: admin@example.com |
And we can create a handler:
1 2 3 4 5 6 |
[miro@controlnode apache]$ cat /etc/ansible/roles/apache/handlers/main.yml --- # handlers file for apache - name: restart apache service service: name=httpd state=restarted listen: "restart httpd" |
Now we can create a playbook which use the apache role:
1 2 3 4 5 6 7 |
[miro@controlnode apache]$ cd .. [miro@controlnode roles]$ cat install.yml --- - hosts: managedhost1 become: yes roles: - apache |
Let’s run the install.yml playbook:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
[miro@controlnode roles]$ ansible-playbook install.yml PLAY [managedhost1] **************************************************************************************************************************************************************** TASK [Gathering Facts] ************************************************************************************************************************************************************* ok: [managedhost1] TASK [apache : install apache] ***************************************************************************************************************************************************** ok: [managedhost1] TASK [apache : copy httpd.conf template] ******************************************************************************************************************************************* changed: [managedhost1] TASK [apache : enable and start service] ******************************************************************************************************************************************* ok: [managedhost1] RUNNING HANDLER [apache : restart apache service] ********************************************************************************************************************************** changed: [managedhost1] PLAY RECAP ************************************************************************************************************************************************************************* managedhost1 : ok=5 changed=2 unreachable=0 failed=0 |
As we see apache service has been installed and started on managedhost1 with http.conf.j2 template:
1 2 3 |
[root@managedhost1 conf]# grep ServerAdmin /etc/httpd/conf/httpd.conf # ServerAdmin: Your address, where problems with the server should be ServerAdmin admin@example.com |
The varriables in defaults dir can be overwritten in other files:
1 2 3 4 5 6 7 8 |
[miro@controlnode roles]$ cat install.yml --- - hosts: managedhost1 become: yes roles: - apache vars: apache_server_admin: miro@example.com |
After we run install.yml playbook ffor the second time:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
[miro@controlnode roles]$ ansible-playbook install.yml PLAY [managedhost1] **************************************************************************************************************************************************************** TASK [Gathering Facts] ************************************************************************************************************************************************************* ok: [managedhost1] TASK [apache : install apache] ***************************************************************************************************************************************************** ok: [managedhost1] TASK [apache : copy httpd.conf template] ******************************************************************************************************************************************* changed: [managedhost1] TASK [apache : enable and start service] ******************************************************************************************************************************************* ok: [managedhost1] RUNNING HANDLER [apache : restart apache service] ********************************************************************************************************************************** changed: [managedhost1] PLAY RECAP ************************************************************************************************************************************************************************* managedhost1 : ok=5 changed=2 unreachable=0 failed=0 |
We can see that ServerAdmin
parameter has changed:
1 2 3 |
[root@managedhost1 conf]# grep ServerAdmin /etc/httpd/conf/httpd.conf # ServerAdmin: Your address, where problems with the server should be ServerAdmin miro@example.com |
It is also possible to use the never syntax:
1 2 3 4 5 6 7 |
[root@controlnode roles]# cat install.yml --- - hosts: managedhost1 become: yes tasks: - include_role: name: apache |
With this syntax we can use conditionals:
1 2 3 4 5 6 7 |
--- - hosts: managedhost1 become: yes tasks: - include_role: name: apache when: "ansible_facts['os_family'] == 'RedHat'" |
Dependencies
Roles may include other roles using the dependencies keyword. Dependent roles are applied prior to the role dependent on them. A role using the same parameters will not be applied more than one time. This can cause complication with role dependencies. Having 'allow_duplicates: true'
defined in meta/main.yml
within a role will allow the role to be applied more than once.
Let’s create php-webserver
role:
1 2 |
[root@controlnode ~]# cd /etc/ansible/roles [root@controlnode roles]# ansible-galaxy init php-webserver |
We create a tasks:
1 2 3 4 5 6 7 8 9 10 11 12 |
[miro@controlnode roles]$ cat /etc/ansible/roles/php-webserver/tasks/main.yml --- # tasks file for php-webserver - name: install php yum: name={{ item }} state=latest with_items: - php - php-gd - php-pear - php-mysql notify: restart httpd |
We modify depedencies
section in meta file:
1 2 3 4 5 |
[miro@controlnode roles]$ cat /etc/ansible/roles/php-webserver/meta/main.yml dependencies: # List your role dependencies here, one per line. Be sure to remove the '[]' above, # if you add dependencies to this list. - role: apache |
And write a playbook install2
to use php-webserver
role:
1 2 3 4 5 6 |
[miro@controlnode roles]$ cat install2.yml --- - hosts: managedhost1 become: yes roles: - php-webserver |
Let’s run install2 playbook:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
[miro@controlnode roles]$ ansible-playbook install2.yml PLAY [managedhost1] ********************************************************************************************************* TASK [Gathering Facts] ****************************************************************************************************** ok: [managedhost1] TASK [apache : install apache] ********************************************************************************************** ok: [managedhost1] TASK [apache : copy httpd.conf template] ************************************************************************************ ok: [managedhost1] TASK [apache : enable and start service] ************************************************************************************ ok: [managedhost1] TASK [php-webserver : install php] ****************************************************************************************** changed: [managedhost1] => (item=[u'php', u'php-gd', u'php-pear', u'php-mysql']) RUNNING HANDLER [apache : restart apache service] *************************************************************************** changed: [managedhost1] PLAY RECAP ****************************************************************************************************************** managedhost1 : ok=6 changed=2 unreachable=0 failed=0 |
As we see role php-webserver
has used depedency role apache
.
Controlling order of execution
Normally, the tasks of roles execute before the tasks of the playbooks that use them. Ansible provides a way of overriding this default behavior: the pre_tasks and post_tasks tasks. The pre_tasks tasks are performed before any roles are applied. The post_tasks tasks are performed after all the roles have completed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
--- - hosts: remote.example.com pre_tasks: - debug: msg: 'hello' roles: - role1 - role2 tasks: - debug: msg: 'still busy' post_tasks: - debug: msg: 'goodbye' |
Example 1.
1. Creating the role directory structure
Ansible looks for roles in a subdirectory called roles in the project directory. Roles can also be kept in the directories referenced by the roles_path variable in Ansible configuration files. This variable contains a colon-separated list of directories to search. Each role has its own directory with specially named subdirectories. The following directory structure contains the files that define the motd role.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
[miro@controlnode ~]$ cd /home/miro/ansible [miro@controlnode ansible]$ mkdir roles [miro@controlnode ansible]$ cd roles [miro@controlnode roles]$ ansible-galaxy init motd - motd was created successfully [miro@controlnode ansible]$ tree roles roles └── motd ├── defaults │ └── main.yml ├── files ├── handlers │ └── main.yml ├── meta │ └── main.yml ├── README.md ├── tasks │ └── main.yml ├── templates ├── tests │ ├── inventory │ └── test.yml └── vars └── main.yml 9 directories, 8 files |
2. Defining the role content
After the directory structure is created, the content of the Ansible role must be defined. A good place to start would be the ROLENAME/tasks/main.yml file. This file defines which modules to call on the managed hosts that this role is applied. The following tasks/main.yml file manages the /etc/motd file on managed hosts. It uses the template module to copy the template named motd.j2 to the managed host. The template is retrieved from the templates subdirectory of the role.
1 2 3 4 5 6 7 8 9 10 |
[miro@controlnode roles]$ cat motd/tasks/main.yml --- # tasks file for motd - name: deliver motd file template: src: templates/motd.j2 dest: /etc/motd owner: root group: root mode: 0444 |
The following command displays the contents of the templates/motd.j2 template of the motd role. It references Ansible facts and a system-owner variable.
1 2 3 4 5 |
[miro@controlnode roles]$ cat motd/templates/motd.j2 This is the system {{ ansible_hostname }}. Today's date is: {{ ansible_date_time.date }}. Only use this system with permission. You can ask {{ system_owner }} for access. |
The role can define a default value for the system_owner variable. The defaults/main.yml file in the role’s directory structure is where these values are set. The following defaults/main.yml file sets the system_owner variable to
user@host.example.com. This will be the email address that is written in the /etc/motd file of managed hosts that this role is applied to.
1 2 3 4 |
[miro@controlnode roles]$ cat motd/defaults/main.yml --- # defaults file for motd system_owner: user@host.example.com |
3. Using the role in a playbook
To access a role, reference it in the roles: section of a playbook. The following playbook refers to the motd role. Because no variables are specified, the role will be applied with its default variable values.
1 2 3 4 5 6 7 8 |
[miro@controlnode roles]$ cd .. [miro@controlnode ansible]$ cat use-motd-role.yml --- - name: use motd role playbook hosts: managedhost1 become: yes roles: - motd |
When the playbook is executed, tasks performed because of a role can be identified by the role name prefix. The following sample output illustrates this with the motd: deliver motd file message.
1 2 3 4 5 6 7 8 9 10 11 12 |
[miro@controlnode ansible]$ ansible-playbook -i inventory use-motd-role.yml PLAY [use motd role playbook] ***************************************************************************************** TASK [Gathering Facts] ************************************************************************************************ ok: [managedhost1] TASK [motd : deliver motd file] *************************************************************************************** changed: [managedhost1] PLAY RECAP ************************************************************************************************************ managedhost1 : ok=2 changed=1 unreachable=0 failed=0 |
1 2 3 4 5 6 |
[miro@controlnode ansible]$ ansible managedhost1 -i inventory -a 'cat /etc/motd' managedhost1 | SUCCESS | rc=0 >> This is the system managedhost1. Today's date is: 2020-02-08. Only use this system with permission. You can ask user@host.example.com for access. |
4. Changing a role’s behavior with variables
Variables can be used with roles, like parameters, to override previously defined, default values. When they are referenced, the variable: value pairs must be specified as well. The following example shows how to use the motd role with a different value for the system_owner role variable. The value specified, someone@host.example.com, will replace the variable reference when the role is applied to a managed host.
1 2 3 4 5 6 7 8 |
[miro@controlnode ansible]$ cat use-motd-role2.yml --- - name: use motd role playbook hosts: managedhost1 become: yes roles: - role: motd system_owner: someone@host.example.com |
Lets run the playbook use-motd-role2.yml
:
1 2 3 4 5 6 7 8 9 10 11 12 |
[miro@controlnode ansible]$ ansible-playbook -i inventory use-motd-role2.yml PLAY [use motd role playbook] ********************************************************************************************************* TASK [Gathering Facts] **************************************************************************************************************** ok: [managedhost1] TASK [motd : deliver motd file] ******************************************************************************************************* changed: [managedhost1] PLAY RECAP **************************************************************************************************************************** managedhost1 : ok=2 changed=1 unreachable=0 failed=0 |
And check if the motd file changed:
1 2 3 4 5 6 |
[miro@controlnode ansible]$ ansible managedhost1 -i inventory -a 'cat /etc/motd' managedhost1 | SUCCESS | rc=0 >> This is the system managedhost1. Today's date is: 2020-02-08. Only use this system with permission. You can ask someone@host.example.com for access. |
Example 2.
In this exercise, you will create two roles that use variables and parameters: myvhost and myfirewall. The myvhost role will install and configure the Apache service on a host. A template is provided that will be used for /etc/httpd/conf.d/vhost.conf: vhost.conf.j2
. The myfirewall role installs, enables, and starts the firewalld daemon. It opens the firewall service port specified by the firewall_service variable.
Create the directory structure for a role called myvhost.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
[miro@controlnode ansible]$ cd /home/miro/ansible/roles [miro@controlnode roles]$ ansible-galaxy init myvhost - myvhost was created successfully [miro@controlnode roles]$ tree myvhost myvhost ├── defaults │ └── main.yml ├── files ├── handlers │ └── main.yml ├── meta │ └── main.yml ├── README.md ├── tasks │ └── main.yml ├── templates ├── tests │ ├── inventory │ └── test.yml └── vars └── main.yml 8 directories, 8 files |
Create the main.yml
file in the tasks subdirectory of the role. The role should perform four tasks:
• Install the httpd package.
• Start and enable the httpd service.
• Download the HTML content into the virtual host DocumentRoot directory.
• Install the template configuration file that configures the webserver.
Use a text editor to create a file called roles/myvhost/tasks/main.yml
. Include
code to use the yum module to install the httpd package. Add additional code to use the service module to start and enable the httpd service. Add another stanza to copy the HTML content from the role to the virtual host DocumentRoot directory. Use the copy module and include a trailing slash after the source directory name. This will cause the module to copy the contents of the html directory immediately below the destination directory (similar to rsync usage). The ansible_hostname
variable will expand to the short host name of the managed host. Add another stanza to use the template module to create /etc/httpd/conf.d/vhost.conf
on the managed host. It should call a handler to restart the httpd daemon when this file is updated. The file contents should look
like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
[miro@controlnode ansible]$ cd roles/myvhost [miro@controlnode myvhost]$ vi tasks/main.yml [miro@controlnode myvhost]$ cat tasks/main.yml --- # tasks file for myvhost - name: install httpd yum: name: httpd state: latest - name: start and enable httpd service service: name: httpd state: started enabled: true - 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 |
Create the handler for restarting the httpd service. Use a text editor to create a file called roles/myvhost/handlers/main.yml
. Include code to use the service module. The file contents should look like the following:
1 2 3 4 5 6 7 |
[miro@controlnode myvhost]$ cat handlers/main.yml --- # handlers file for myvhost - name: restart httpd service: name: httpd state: restarted |
Create the HTML content that will be served by the webserver. The role task that called the copy module referred to an html directory as the src.
Create this directory below the files subdirectory of the role. Create an index.html file below that directory with the contents: “simple index”. Be
sure to use this string verbatim because the grading script looks for it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
[miro@controlnode myvhost]$ mkdir -p files/html [miro@controlnode myvhost]$ echo 'simple index' > files/html/index.html [miro@controlnode myvhost]$ cat templates/vhost.conf.j2 # modified on {{ansible_date_time.date}} {{ansible_date_time.time}} by student on {{ansible_fqdn}} <VirtualHost *:80> 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> |
Write a playbook that uses the role, called use-vhost-role.yml
. It should have the following content:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
[miro@controlnode myvhost]$ cd ../.. [miro@controlnode ansible]$ cat use-vhost-rule.yml --- - name: use vhost role playbook become: yes hosts: webservers pre_tasks: - debug: msg: 'Beginning web server configuration.' roles: - myvhost post_tasks: - debug: msg: 'Web server has been configured.' |
Run the playbook. Review the output to confirm that Ansible performed the actions on the web server,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
[miro@controlnode ansible]$ ansible-playbook use-vhost-role.yml PLAY [use vhost role playbook] *************************************************************************************************************************************** TASK [Gathering Facts] *********************************************************************************************************************************************** ok: [managedhost2] TASK [debug] ********************************************************************************************************************************************************* ok: [managedhost2] => { "msg": "Beginning web server configuration." } TASK [myvhost : install httpd] *************************************************************************************************************************************** ok: [managedhost2] TASK [myvhost : start and enable httpd service] ********************************************************************************************************************** ok: [managedhost2] TASK [myvhost : deliver html content] ******************************************************************************************************************************** ok: [managedhost2] TASK [myvhost : template vhost file] ********************************************************************************************************************************* changed: [managedhost2] RUNNING HANDLER [myvhost : restart httpd] **************************************************************************************************************************** changed: [managedhost2] TASK [debug] ********************************************************************************************************************************************************* ok: [managedhost2] => { "msg": "Web server has been configured." } PLAY RECAP *********************************************************************************************************************************************************** managedhost2 : ok=8 changed=2 unreachable=0 failed=0 |
Run ad hoc commands to confirm that the role worked. The httpd package should be installed and the httpd service should be running.
1 2 3 4 5 6 7 |
[miro@controlnode ansible]$ ansible managedhost2 -a 'yum list installed httpd' [WARNING]: Consider using yum module rather than running yum managedhost2 | SUCCESS | rc=0 >> Wczytane wtyczki: fastestmirror Zainstalowane pakiety httpd.x86_64 2.4.6-93.el7.centos @base |
Run ad hoc commands to confirm that the role worked. The httpd package should be installed and the httpd service should be running.
1 2 3 4 5 6 7 8 9 10 11 |
[miro@controlnode ansible]$ ansible managedhost2 -a 'systemctl is-active httpd' managedhost2 | SUCCESS | rc=0 >> active [miro@controlnode ansible]$ ansible managedhost2 -a 'systemctl is-enabled http' managedhost2 | FAILED | rc=1 >> Failed to get unit file state for http.service: No such file or directorynon-zero return code [miro@controlnode ansible]$ ansible managedhost2 -a 'systemctl is-enabled httpd' managedhost2 | SUCCESS | rc=0 >> enabled |
Use a web browser on managedhost2 to check if the web content is available locally. It should succeed and display content.
1 2 3 4 5 |
[miro@controlnode ansible]$ ansible managedhost2 -a 'curl -s http://localhost' [WARNING]: Consider using get_url or uri module rather than running curl managedhost2 | SUCCESS | rc=0 >> simple index |
Check if you can connect from controlnode to the webserver on managedhost2.example.com:
1 2 |
[miro@controlnode ansible]$ curl managedhost2.example.com curl: (7) Failed connect to managedhost2.example.com:80; No route to host |
The issue is due to firewall on managedhost2 don’t allow for http connection. So we need to configure firewall on managedhost2.
Create the role directory structure for a role called myfirewall:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
[miro@controlnode ansible]$ cd roles [miro@controlnode roles]$ ansible-galaxy init myfirewall - myfirewall was created successfully [miro@controlnode roles]$ tree myfirewall myfirewall ├── defaults │ └── main.yml ├── files ├── handlers │ └── main.yml ├── meta │ └── main.yml ├── README.md ├── tasks │ └── main.yml ├── templates ├── tests │ ├── inventory │ └── test.yml └── vars └── main.yml 8 directories, 8 files |
Create the main.yml
file in the tasks subdirectory of the role. The role should perform three tasks:
• Install the firewalld package.
• Start and enable the firewalld service.
• Open a firewall service port.
Include code to use the yum module to install the firewalld package. Add additional code to use the service module to start and enable the firewalld service. Add another stanza to use the firewalld module to immediately, and persistently, open the service port specified by the firewall_service variable. The file contents should look like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
[miro@controlnode roles]$ cat myfirewall/tasks/main.yml --- # tasks file for myfirewall - name: install firewalld yum: name: firewalld state: latest - name: start and enable firewalld service service: name: firewalld state: started enabled: true - name: firewall services config firewalld: state: enabled immediate: true permanent: true service: "{{ firewall_service }}" |
Create the handler for restarting the firewalld service. Use a text editor to create a file called roles/myfirewall/handlers/main.yml. Include code to use the service module. The file contents should look like the following:
1 2 3 4 5 6 7 |
[miro@controlnode roles]$ cat myfirewall/handlers/main.yml --- # handlers file for myfirewall - name: restart firewalld service: name: firewalld state: restarted |
Create the file that defines the default value for the firewall_service variable. It should have a default value of ssh initially. We will override the value to open the port for http when we use the role in a later step. The file myfirewall/defaults/main.yml
should contain the following content:
1 2 3 4 |
[miro@controlnode roles]$ cat myfirewall/defaults/main.yml --- # defaults file for myfirewall firewall_service: ssh |
Modify the myvhost role to include the myfirewall role as a dependency, then retest the modified role. Use a text editor to modify a file, called myvhost/meta/main.yml, that makes myvhost depend on the myfirewall role. The firewall_service variable should be set to http so the correct service port is opened. By using the role in this way, the default value of ssh for firewall_service will be ignored. The explicitly assigned value of http will be used instead. The dependencies section in the file should look like the following:
1 2 3 |
--- dependencies: - { role: myfirewall, firewall_service: http } |
Run the playbook again. Confirm the additional myfirewall tasks are successfully executed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
[miro@controlnode ansible]$ ansible-playbook use-vhost-role.yml PLAY [use vhost role playbook] ******************************************************************************************* TASK [Gathering Facts] *************************************************************************************************** ok: [managedhost2] TASK [debug] ************************************************************************************************************* ok: [managedhost2] => { "msg": "Beginning web server configuration." } TASK [myfirewall : install firewalld] ************************************************************************************ changed: [managedhost2] TASK [myfirewall : start and enable firewalld service] ******************************************************************* ok: [managedhost2] TASK [myfirewall : firewall services config] ***************************************************************************** ok: [managedhost2] TASK [myvhost : install httpd] ******************************************************************************************* ok: [managedhost2] TASK [myvhost : start and enable httpd service] ************************************************************************** ok: [managedhost2] TASK [myvhost : deliver html content] ************************************************************************************ ok: [managedhost2] TASK [myvhost : template vhost file] ************************************************************************************* ok: [managedhost2] TASK [debug] ************************************************************************************************************* ok: [managedhost2] => { "msg": "Web server has been configured." } PLAY RECAP *************************************************************************************************************** managedhost2 : ok=10 changed=1 unreachable=0 failed=0 |
Now we can connect from controlnode to webserver on managedhost2:
1 2 |
[miro@controlnode ansible]$ curl -s managedhost2.example.com simple index |
Example 3.
In this lab, you will create two roles that use variables and parameters: student.myenv and myapache. The myenv
role customizes a system with required packages and a helpful script for all users. It will customize a user account, specified by the myenv_user variable, with a profile picture and an extra command alias in their ~/.bashrc file. The myapache role will install and configure the Apache service on a host. Two templates are provided which will be used for the /etc/httpd/conf/httpd.conf and the /var/www/html/index.html files: apache_httpdconf.j2 and apache_indexhtml.j2 respectively.
Create directories to contain the Ansible roles. They should be contained in the myenv and myapache directories below ~/ansible/roles directory:
1 2 3 4 5 6 |
[miro@controlnode roles]$ pwd /home/miro/ansible/roles [miro@controlnode roles]$ ansible-galaxy init --offline myenv - myenv was created successfully miro@controlnode roles]$ ansible-galaxy init --offline myapache - myapache was created successfully |
Install the mkcd.sh.j2 file as a template for the myenv role.
The lab setup script copied the file to the lab-roles working directory. Move it to the roles/student.myenv/templates/ subdirectory.
1 |
[student@workstation lab-roles]$ mv mkcd.sh.j2 roles/myenv/templates/ |
Define student.myenv role tasks to perform the following steps:
• Install packages defined by the myenv_packages variable.
• Copy the standard profile picture to the user’s home directory as ~/profile.png. Use the myenv_user variable for the user name.
• Add the following line to the user’s ~/.bashrc file: alias tree=’tree -C’ . Use the
myenv_user variable for the user name. Hint: The lineinfile module might be well suited for this task.
• Install the mkcd.sh.j2 template as /etc/profile.d/mkcd.sh. It should have
user:group ownership of root:root and have -rw-r–r– permissions.
The role should fail with an error message when the myenv_user variable is an empty string.
Modify roles/myenv/tasks/main.yml so that it contains the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
[miro@controlnode roles]$ cat myenv/tasks/main.yml --- # tasks file for myenv - name: check myenv_user default fail: msg: You must specify the variable `myenv_user` to use this role! when: "myenv_user == ''" - name: install my packages yum: name: "{{ item }}" state: installed with_items: "{{ myenv_packages }}" - name: copy placeholder profile pic copy: src: profile.png dest: "~{{ myenv_user }}/profile.png" - name: set an alias in `.bashrc` lineinfile: line: "alias tree='tree -C'" dest: "~{{ myenv_user }}/.bashrc" - name: template out mkcd function template: src: mkcd.sh.j2 dest: /etc/profile.d/mkcd.sh owner: root group: root mode: 0644 |
Define the myenv_packages variable for the myenv role so it contains the following packages: git, tree, and vim-enhanced.
Create roles/myenv/vars/main.yml
with the following contents:
1 2 3 4 5 6 7 |
[miro@controlnode roles]$ cat myenv/vars/main.yml --- # vars file for myenv myenv_packages: - 'git' - 'tree' - 'vim-enhanced' |
Assign the empty string as the default value for the myenv_user variable.
Create roles/myenv/defaults/main.yml with the following contents
1 2 3 4 |
[miro@controlnode roles]$ cat myenv/defaults/main.yml --- # defaults file for myenv myenv_user: '' |
Create a playbook, called use-myenv.yml
, that runs on all hosts. It should use the myenv role, but do not set the myenv_user variable. Test the myenv role and confirm that it fails.
1 2 3 4 5 6 7 |
[miro@controlnode ansible]$ cat use-myenv.yml --- - name: setup my personal environment become: yes hosts: all roles: - myenv |
Run the myenv.yml playbook. Check the ansible-playbook output to make sure it fails.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[miro@controlnode ansible]$ ansible-playbook use-myenv.yml PLAY [setup my personal environment] ************************************************************************************************************* TASK [Gathering Facts] *************************************************************************************************************************** ok: [managedhost2] TASK [myenv : check myenv_user default] ********************************************************************************************************** fatal: [managedhost2]: FAILED! => {"changed": false, "msg": "You must specify the variable `myenv_user` to use this role!"} to retry, use: --limit @/home/miro/ansible/use-myenv.retry PLAY RECAP *************************************************************************************************************************************** managedhost2 : ok=1 changed=0 unreachable=0 failed=1 |
Update the myenv.yml playbook so that it uses the student.myenv role, setting the myenv_user variable to student. Test the myenv role and confirm that it works properly.
1 2 3 4 5 6 7 8 |
[miro@controlnode ansible]$ cat use-myenv.yml --- - name: setup my personal environment become: yes hosts: managedhost2 roles: - role: myenv myenv_user: miro |
Run the use-myenv.yml playbook. Check the ansible-playbook output to make sure the tasks ran properly.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
[miro@controlnode ansible]$ ansible-playbook use-myenv.yml PLAY [setup my personal environment] ************************************************************************************************************* TASK [Gathering Facts] *************************************************************************************************************************** ok: [managedhost2] TASK [myenv : check myenv_user default] ********************************************************************************************************** skipping: [managedhost2] TASK [myenv : install my packages] *************************************************************************************************************** ok: [managedhost2] => (item=[u'git', u'tree', u'vim-enhanced']) TASK [myenv : set an alias in `.bashrc`] ********************************************************************************************************* ok: [managedhost2] TASK [myenv : template out mkcd function] ******************************************************************************************************** changed: [managedhost2] PLAY RECAP *************************************************************************************************************************************** managedhost2 : ok=4 changed=1 unreachable=0 failed=0 |
Create a handler that will restart the httpd service. Modify roles/myapache/handlers/main.yml
so that it contains the following:
1 2 3 4 5 6 7 |
[miro@controlnode ansible]$ cat roles/myapache/handlers/main.yml --- # handlers file for myapache - name: restart apache service: name: httpd state: restarted |
Define myapache role tasks to perform the following steps:
• Install the httpd and firewalld packages.
• Copy the apache_httpdconf.j2 template to /etc/httpd/conf/httpd.conf. The
target file should be owned by root with -r–r–r– permissions. Restart Apache using the handler created previously.
• Copy the apache_indexhtml.j2 template to /var/www/html/index.html. The
target file should be owned by root with -r–r–r– permissions.
• Start and enable the httpd and firewalld services.
• Open port 80/tcp on the firewall.
Package installation should always occur when this role is used, but the other tasks should only occur when the apache_enable variable is set to true. The role should restart the Apache service when the configuration file is updated.
Modify roles/myapache/tasks/main.yml so that it contains the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
[miro@controlnode ansible]$ cat roles/myapache/tasks/main.yml --- # tasks file for myapache - name: install apache package yum: name: httpd state: latest - name: install firewalld package yum: name: firewalld state: latest - name: template out apache configuration file template: src: apache_httpdconf.j2 dest: /etc/httpd/conf/httpd.conf owner: root group: root mode: 0444 notify: - restart apache when: apache_enable - name: template out apache index.html template: src: apache_indexhtml.j2 dest: /var/www/html/index.html owner: root group: root mode: 0444 when: apache_enable - name: start and enable apache daemon service: name: httpd state: started enabled: true when: apache_enable - name: start and enable firewalld daemon service: name: firewalld state: started enabled: true when: apache_enable - name: open http firewall port firewalld: port: 80/tcp immediate: true permanent: true state: enabled when: apache_enable |
Create the default variable values for the myapache role. The apache_enable variable should have a default value of false.
Modify roles/myapache/defaults/main.yml so it contains the following:
1 2 3 4 |
[miro@controlnode ansible]$ cat roles/myapache/defaults/main.yml --- # defaults file for myapache apache_enable: false |
Create a playbook, called use-myapache.yml, that runs on serverb. It should use the myapache role, but use the default value of the apache_enable variable. Test the myapache role and confirm that it installs the packages, but does not deploy the web server.
1 2 3 4 5 6 7 |
[miro@controlnode ansible]$ cat use-myapache.yml --- - name: setup apache become: yes hosts: managedhost2 roles: - myapache |
Run the use-myapache.yml
playbook. Check the ansible-playbook output to make sure it installs the needed packages, but skips the remaining tasks.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
[miro@controlnode ansible]$ ansible-playbook use-myapache.yml PLAY [setup apache] **************************************************************************************************************************************** TASK [Gathering Facts] ************************************************************************************************************************************* ok: [managedhost2] TASK [myapache : install apache package] ******************************************************************************************************************* ok: [managedhost2] TASK [myapache : install firewalld package] **************************************************************************************************************** ok: [managedhost2] TASK [myapache : template out apache configuration file] *************************************************************************************************** skipping: [managedhost2] TASK [myapache : template out apache index.html] *********************************************************************************************************** skipping: [managedhost2] TASK [myapache : start and enable apache daemon] *********************************************************************************************************** skipping: [managedhost2] TASK [myapache : start and enable firewalld daemon] ******************************************************************************************************** skipping: [managedhost2] TASK [myapache : open http firewall port] ****************************************************************************************************************** skipping: [managedhost2] PLAY RECAP ************************************************************************************************************************************************* managedhost2 : ok=3 changed=0 unreachable=0 failed=0 |
Modify the apache.yml playbook so that it uses the myapache role, setting the
apache_enable variable to true. Test the myapache role and confirm that it works properly.
1 2 3 4 5 6 7 8 |
[miro@controlnode ansible]$ cat use-myapache.yml --- - name: setup apache become: yes hosts: managedhost1 roles: - role: myapache apache_enable: true |
Run the use-myapache.yml playbook. Check the ansible-playbook output to make sure the tasks ran properly.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
[miro@controlnode ansible]$ ansible-playbook use-myapache.yml PLAY [setup apache] **************************************************************************************************************************************** TASK [Gathering Facts] ************************************************************************************************************************************* ok: [managedhost2] TASK [myapache : install apache package] ******************************************************************************************************************* ok: [managedhost2] TASK [myapache : install firewalld package] **************************************************************************************************************** ok: [managedhost2] TASK [myapache : template out apache configuration file] *************************************************************************************************** changed: [managedhost2] TASK [myapache : template out apache index.html] *********************************************************************************************************** changed: [managedhost2] TASK [myapache : start and enable apache daemon] *********************************************************************************************************** ok: [managedhost2] TASK [myapache : start and enable firewalld daemon] ******************************************************************************************************** ok: [managedhost2] TASK [myapache : open http firewall port] ****************************************************************************************************************** changed: [managedhost2] RUNNING HANDLER [myapache : restart apache] **************************************************************************************************************** changed: [managedhost2] PLAY RECAP ************************************************************************************************************************************************* managedhost2 : ok=9 changed=4 unreachable=0 failed=0 |
Use a web browser to confirm that serverb is serving web content.
1 2 |
[miro@controlnode ansible]$ curl -S managedhost1 myapache is running |