In comparison with ad-hoc commands, playbooks are used in complex scenarios, and they offer increased flexibility. Playbooks use YAML format, so there is not much syntax needed, but indentation must be respected. Ansible playbooks tend to be more of a configuration language than a programming language.
Like the name is saying, a playbook is a collection of plays. Through a playbook, you can designate specific roles to some of the hosts and other roles to other hosts. By doing so, you can orchestrate multiple servers in very diverse scenarios, all in one playbook.
Blocks
Complex playbooks may contain a long list of tasks. Some tasks in the list may be related in their function. With Ansible version 2.0, blocks offer another alternative to task organization. Blocks can be used to group related tasks together. This not only improves readability but also allows task parameters to be performed on a block level when writing more complex playbooks. The following examples show how a list of tasks can be organized into distinct groups with the use of blocks.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
tasks: - name: first task yum: name: httpd state: latest - name: second task yum: name: openssh-server state: latest - name: third task service: name:httpd enabled:true state: started - name: fourth task service: name: sshd enabled:true state:started |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
tasks: - block: - name: first package task yum: name: httpd state: latest - name: second package task yum: name: openssh-server state: latest - block: - name: first service task service: name: httpd enabled: true state: started - name: second service task service: name: sshd enabled: true state: started |
Multiple plays
Playbook can contain one or more plays. Because plays map managed hosts to tasks, scenarios that require different tasks to be performed on different hosts, such as orchestration, necessitate the use of different plays. Rather than having plays in separate playbook files, multiple plays can be placed in the same playbook file. Plays are expressed in a list context, so the start of each play is indicated by a preceding dash and space.
The following example shows the format for creating a simple playbook with multiple plays.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
--- # This is a simple playbook with two plays - name: first play hosts: web.example.com tasks: - name: first task service: name: httpd enabled: true - name: second play hosts: database.example.com tasks: - name: first task service: name: mariadb enabled: true ... |
To have all the details precise before continuing with examples, we must first define a task. These are the interface to ansible modules for roles and playbooks.
One playbook with one play, containing multiple tasks looks like this.
1 2 3 4 5 6 7 8 9 10 11 12 |
--- - hosts: group1 tasks: - name: Install lldpad package yum: name: lldpad state: latest - name: check lldpad service status service: name: lldpad state: started |
Explanation:
- hosts – Group of hosts on which the playbook will run
- Yum module is used in this task for lldpad installation
- The service module is used to check if the service is up and running after installation
The ordering of the contents within a playbook is important, because Ansible executes plays and tasks in the order they are presented.
Each ansible playbook works with an inventory file. The inventory file contains a list of servers divided into groups for better control for details like IP address and SSH port for each host.
The inventory file you can use for this example looks like below. There are two groups, named group1 and group2 each containing host1 and host2 respectively.
1 2 3 4 |
[group1] host1 ansible_host=192.168.100.2 ansible_ssh_port=22 [group2] host2 ansible_host=192.168.100.3 ansible_ssh_port=22 |
Another useful example of an Ansible playbook containing this time two plays for two hosts is the next one. For the first group of hosts, group1, selinux will be enabled. If it is enabled, then a message will appear on the screen of the host.
For the second group of hosts, httpd package will be installed only if the ansible_os_family is RedHat and ansible_system_vendor is HP.
Ansible_os_family
and ansible_system_vendor
are variables gathered with gather_facts option and can be used like in this conditional example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
--- - hosts: group1 tasks: - name: Enable SELinux selinux: state: enabled when: ansible_os_family == 'Debian' register: enable_selinux - debug: Imsg: "Selinux Enabled. Please restart the server to apply changes." when: enable_selinux.changed == true - hosts: group2 tasks: - name: Install apache yum: name: httpd state: present when: ansible_system_vendor == 'HP' and ansible_os_family == 'RedHat' |
Explanation:
Example of the when clause, In this case, when OS type is Debian. The ansible_os_family
variable is gathered via gather_facts
functionality.
The task output is registered for future use, with its name enable_selinux
Another example of the when clause. In this case, a message will be displayed for the host user if the SELinux was indeed enabled before. Another example of the when clause consisting of two rules.
Besides tasks, there are also some particular tasks called handlers. Handlers must have a unique name throughout the playbook. These work in the same way as a regular task but a handler can be notified via a notifier.
If a handler is not notified during the run of the playbook, it will not run. However, if more than one task notifies a handler, this will run only once after all the tasks are finished.
In the example shown below, you can see how a specific task has a notify section which calls upon another task. If the output of the first task is changed, then a handler task will be called. The best example is to have a configuration file changed and afterward restart that specific service.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
--- - hosts: group2 tasks: - name: sshd config file modify port lineinfile: path: /etc/ssh/sshd_config regexp: 'Port 28675' line: '#Port 22' notify: - restart sshd handlers - name: restart sshd service: sshd name: sshd state: restarted |
In this case, if the first task, "sshd config file modify port"
is changed, meaning that if the port is not 28675 in the first place, then it will be modified and the task will notify the handler with the same name to run, and it will restart the sshd service.
Running playbooks Basic Playbook Syntax
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
[miro@controlnode playbooks]$ cat /etc/ansible/hosts managedhost1 ansible_host=managedhost1.example.com [labservers] managedhost1.example.com managedhost2.example.com [miro@controlnode playbooks]$ cat /home/miro/ansible/playbooks/playbook0.yml --- - hosts: labservers become: yes tasks: - name: ensure apache is at the latest version yum: name=httpd state=latest |
The playbook will install apache in the newest version.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[miro@controlnode playbooks]$ ansible-playbook playbook0.yml PLAY [labservers] **************************************************************************************************************** TASK [Gathering Facts] *********************************************************************************************************** ok: [managedhost2.example.com] ok: [managedhost1.example.com] TASK [ensure apache is at the latest version] ************************************************************************************ ok: [managedhost2.example.com] changed: [managedhost1.example.com] PLAY RECAP *********************************************************************************************************************** managedhost1.example.com : ok=2 changed=1 unreachable=0 failed=0 managedhost2.example.com : ok=2 changed=0 unreachable=0 failed=0 |
Modification playbook to start and enable httpd
service:
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 |
[miro@controlnode playbooks]$ cat /home/miro/ansible/playbooks/playbook0.yml --- - hosts: labservers become: yes tasks: - name: ensure apache is at the latest version yum: name: httpd state: latest - name: start enable httpd service: name: httpd state: started enabled: yes [miro@controlnode playbooks]$ ansible-playbook playbook0.yml PLAY [labservers] **************************************************************************************************************** TASK [Gathering Facts] *********************************************************************************************************** ok: [managedhost2.example.com] ok: [managedhost1.example.com] TASK [ensure apache is at the latest version] ************************************************************************************ ok: [managedhost2.example.com] ok: [managedhost1.example.com] TASK [start enable httpd] ******************************************************************************************************** changed: [managedhost1.example.com] changed: [managedhost2.example.com] PLAY RECAP *********************************************************************************************************************** managedhost1.example.com : ok=3 changed=1 unreachable=0 failed=0 managedhost2.example.com : ok=3 changed=1 unreachable=0 failed=0 |
Adding index.html
file:
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 |
[miro@controlnode playbooks]$ cat /home/miro/ansible/playbooks/playbook0.yml --- - hosts: labservers become: yes tasks: - name: ensure apache is at the latest version yum: name: httpd state: latest - name: start enable httpd service: name: httpd state: started enabled: yes - name: create index.html file: path: /var/www/html/index.html state: touch - name: add line to index.html lineinfile: path: /var/www/html/index.html line: "Hello World !" [miro@controlnode playbooks]$ ansible-playbook playbook0.yml PLAY [labservers] **************************************************************************************************************** TASK [Gathering Facts] *********************************************************************************************************** ok: [managedhost1.example.com] ok: [managedhost2.example.com] TASK [ensure apache is at the latest version] ************************************************************************************ ok: [managedhost2.example.com] ok: [managedhost1.example.com] TASK [start enable httpd] ******************************************************************************************************** ok: [managedhost2.example.com] ok: [managedhost1.example.com] TASK [create index.html] ********************************************************************************************************* changed: [managedhost2.example.com] changed: [managedhost1.example.com] TASK [add line to index.html] **************************************************************************************************** changed: [managedhost2.example.com] changed: [managedhost1.example.com] PLAY RECAP *********************************************************************************************************************** managedhost1.example.com : ok=5 changed=2 unreachable=0 failed=0 managedhost2.example.com : ok=5 changed=2 unreachable=0 failed=0 |
We can limit running playbook only on one host. It is usefull when you engineering playbooks.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
[miro@controlnode playbooks]$ ansible-playbook playbook0.yml --limit managedhost1.example.com PLAY [labservers] **************************************************************************************************************** TASK [Gathering Facts] *********************************************************************************************************** ok: [managedhost1.example.com] TASK [ensure apache is at the latest version] ************************************************************************************ ok: [managedhost1.example.com] TASK [start enable httpd] ******************************************************************************************************** ok: [managedhost1.example.com] TASK [create index.html] ********************************************************************************************************* changed: [managedhost1.example.com] TASK [add line to index.html] **************************************************************************************************** ok: [managedhost1.example.com] PLAY RECAP *********************************************************************************************************************** managedhost1.example.com : ok=5 changed=1 unreachable=0 failed=0 |
Let’s check if our playbook works:
1 2 |
[miro@controlnode playbooks]$ curl managedhost1/index.html Hello World ! |
Use Variables to Retrieve the Results of Running Commands
- The
register
keyword - May be referenced within the play
- Many attributes returned
1 2 3 4 5 6 7 8 9 10 11 12 |
--- - hosts: scoldham2.mylabserver.com tasks: - name: copy a file copy: src: testfile dest: /home/user/testregister mode: 400 register: var - name: output debug info debug: msg="debug info is {{ var }}" |
Example
1 2 3 4 5 6 7 8 9 10 11 |
[miro@controlnode playbooks]$ cat playbook1.yml --- - hosts: localhost tasks: - name: create file file: path: /tmp/newfile state: touch register: output - debug: msg="Register output is {{output}}" |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
[miro@controlnode playbooks]$ ansible-playbook playbook1.yml PLAY [localhost] ********************************************************************************************************************************************************************************************* TASK [Gathering Facts] *************************************************************************************************************************************************************************************** ok: [localhost] TASK [create file] ******************************************************************************************************************************************************************************************* changed: [localhost] TASK [debug] ************************************************************************************************************************************************************************************************* ok: [localhost] => { "msg": "Register output is {u'group': u'miro', u'uid': 1000, u'dest': u'/tmp/newfile', u'changed': True, 'failed': False, u'state': u'file', u'gid': 1000, u'secontext': u'unconfined_u:object_r:user_tmp_t:s0', u'mode': u'0664', u'owner': u'miro', u'diff': {u'after': {u'path': u'/tmp/newfile', u'state': u'touch'}, u'before': {u'path': u'/tmp/newfile', u'state': u'file'}}, u'size': 0}" } PLAY RECAP *************************************************************************************************************************************************************************************************** localhost : ok=3 changed=1 unreachable=0 failed=0 |
Let’s write uid of output to the file:
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 |
[miro@controlnode playbooks]$ cat playbook1.yml --- - hosts: localhost tasks: - name: create file file: path: /tmp/newfile state: touch register: output - debug: msg="Register output is {{output}}" - name: edit file lineinfile: path: /tmp/newfile line: "{{output.uid}}" [miro@controlnode playbooks]$ ansible-playbook playbook1.yml PLAY [localhost] *********************************************************************************************** TASK [Gathering Facts] ***************************************************************************************** ok: [localhost] TASK [create file] ********************************************************************************************* changed: [localhost] TASK [debug] *************************************************************************************************** ok: [localhost] => { "msg": "Register output is {u'group': u'miro', u'uid': 1000, u'dest': u'/tmp/newfile', u'changed': True, 'failed': False, u'state': u'file', u'gid': 1000, u'secontext': u'unconfined_u:object_r:user_tmp_t:s0', u'mode': u'0664', u'owner': u'miro', u'diff': {u'after': {u'path': u'/tmp/newfile', u'state': u'touch'}, u'before': {u'path': u'/tmp/newfile', u'state': u'file'}}, u'size': 0}" } TASK [edit file] *********************************************************************************************** changed: [localhost] PLAY RECAP ***************************************************************************************************** localhost : ok=4 changed=2 unreachable=0 failed=0 [miro@controlnode playbooks]$ cat /tmp/newfile 1000 |
Executing a dry run
Another helpful option is the -C
option. This causes Ansible to report what changes would have occurred if the playbook were executed, but does not make any actual changes to managed hosts. The following example shows the dry run of a playbook containing a single task for ensuring that the latest version of httpd package is installed on a managed host. Note that the dry run reports that the task would effect a change on the managed host.
1 2 3 4 5 6 7 8 9 10 |
[student@controlnode ~]$ ansible-playbook -C webserver.yml PLAY [play to setup web server] ************************************************ TASK [setup] ******************************************************************* ok: [servera.lab.example.com] Demonstration: Writing and executing playbooks DO407-A2.0-en-1-20160804 89 TASK [latest httpd version installed] ****************************************** changed: [servera.lab.example.com] PLAY RECAP ********************************************************************* servera.lab.example.com : ok=2 changed=1 unreachable=0 failed=0 |
Step-by-step execution
When developing new playbooks, it may be helpful to execute the playbook interactively. The ansible-playbook command offers the --step
option for this purpose. When executed with this option, Ansible steps through each task in the playbook. Prior to executing each task, it prompts the user for input. User can choose ‘y’ to execute the task, 'n'
to skip the task, or 'c'
to exit step-by-step execution and execute the remaining tasks noninteractively.
The following example shows the step-by-step run of a playbook containing a task for ensuring that the latest version of the httpd package is installed on a managed host.
1 2 3 4 5 6 7 8 9 10 11 12 |
[student@controlnode ~]$ ansible-playbook --step webserver.yml PLAY [play to setup web server] ************************************************ Perform task: TASK: setup (y/n/c): y Perform task: TASK: setup (y/n/c): ********************************************* TASK [setup] ******************************************************************* ok: [servera.lab.example.com] Perform task: TASK: latest httpd version installed (y/n/c): y Perform task: TASK: latest httpd version installed (y/n/c): ******************** TASK [latest httpd version installed] ****************************************** ok: [servera.lab.example.com] PLAY RECAP ********************************************************************* servera.lab.example.com : ok=2 changed=0 unreachable=0 failed= |
Idempotency
When possible, try to avoid the command
, shell
, and raw
modules in playbooks. Because these take arbitrary commands, it is very easy to write non-idempotent playbooks with these modules. For example, this task using the shell module is not idempotent. Every time the play is run, it will rewrite /etc/resolv.conf
even if it already consists of the line "nameserver 192.0.2.1"
.
1 2 |
- name: Non-idempotent approach with shell module shell: echo "nameserver 192.0.2.1" > /etc/resolv.conf |
A number of things could be done to use the shell module in an idempotent way, and sometimes making those changes and using shell is the best approach. But a quicker solution may be to use copy
module and use that to get the desired effect. The following example will not rewrite the file /etc/resolv.conf
if it already consists of the right content:
1 2 3 4 |
- name: Idempotent approach with copy module copy: dest: /etc/resolv.conf content: "nameserver 192.0.2.1\n" |
The copy module is special-purpose and can easily test to see if the state has already been met, and if it has will make no changes. The shell module allows a lot of flexibility, but also requires more attention to ensure that it runs in an idempotent way. Idempotent playbooks can be run repeatedly to ensure systems are in a particular state without disrupting those systems if they already are.
Example 1.
This demonstration illustrates the creation and use of Ansible playbooks. The demonstration will be conducted out of the /home/student/imp-playdemo directory on workstation using the supplied configuration file, inventory, and playbooks. It is assumed that workstation is the control node, and that servera.lab.example.com is a managed host. The control node
also manages itself as a managed host. The managed hosts have a devops user that is able to get root access through sudo without a password. SSH authentication keys are set up to give student login access to the devops user.
The supplied configuration file specifies the remote user and the location of the inventory. It also configures Ansible logging to be enabled on the control node and managed hosts.
1. Log in as the student user on workstation. Change directory to the working directory and review the contents of the directory.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
[student@workstation ~]$ cd /home/student/imp-playdemo [student@workstation imp-playdemo]$ ls -la total 12 drwxrwxr-x. 3 student student 50 May 18 21:03 . drwx------. 7 student student 4096 May 18 21:03 .. -rw-rw-r--. 1 student student 138 May 18 21:03 ansible.cfg -rw-rw-r--. 1 student student 34 May 18 21:03 inventory drwxrwxr-x. 2 student student 6 May 18 21:03 log [student@workstation imp-playdemo]$ cat ansible.cfg [defaults] remote_user=devops inventory=inventory log_path=/home/student/imp-playdemo/log/ansible.log no_log=False no_target_syslog=False [student@workstation imp-playdemo]$ cat inventory localhost servera.lab.example.com |
2. Review the /home/student/imp-playdemo/ftpclient.yml playbook. It ensures that the latest version of the lftp package is installed on the local managed host. This operation will require escalated privileges, so the playbook enables privilege escalation using sudo and the root user. The connection to the managed host is made with the devops user.
1 2 3 4 5 6 7 8 9 10 11 12 |
--- - name: ftp client installed hosts: localhost remote_user: devops become: yes become_method: sudo become_user: root tasks: - name: latest lftp version installed yum: name: lftp state: latest |
…
3. Review the/home/student/imp-playdemo/ftpserver.yml playbook. It ensures that the latest version of the vsftpd and firewalld packages are installed on servera. This operation will require escalated privileges, so the playbook enables privilege escalation using sudo and the root user. The connection to the managed host is made with the devops user. The playbook ensures that firewalld allows incoming connections for the FTP service. It
also ensures that the vsftpd and firewalld services are enabled and running.
Related tasks are grouped together using blocks.
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 |
--- - name: ftp server installed hosts: servera.lab.example.com remote_user: devops become: yes become_method: sudo become_user: root tasks: - block: - name: latest vsftpd version installed yum: name: vsftpd state: latest - name: latest firewalld version installed yum: name: firewalld state: latest - block: - name: firewalld permits ftp service firewalld: service: ftp permanent: true state: enabled immediate: yes - block: - name: vsftpd enabled and running service: name: vsftpd enabled: true state: started - name: firewalld enabled and running service: name: firewalld enabled: true state: started ... |
4. Execute the /home/student/imp-playdemo/ftpclient.yml playbook.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
[student@workstation imp-playdemo]$ ansible-playbook ftpclient.yml PLAY [ftp client installed] **************************************************** TASK [setup] ******************************************************************* ok: [localhost] TASK [latest lftp version installed] ******************************************* changed: [localhost] PLAY RECAP ********************************************************************* localhost : ok=2 changed=1 unreachable=0 failed=0 5. Execute the /home/student/imp-playdemo/ftpserver.yml playbook. [student@workstation imp-playdemo]$ ansible-playbook ftpserver.yml PLAY [ftp server installed] **************************************************** TASK [setup] ******************************************************************* ok: [servera.lab.example.com] TASK [latest vsftpd version installed] ***************************************** changed: [servera.lab.example.com] TASK [latest firewalld version installed] ************************************** ok: [servera.lab.example.com] TASK [firewalld permits ftp service] ******************************************* changed: [servera.lab.example.com] TASK [vsftpd enabled and running] ********************************************** changed: [servera.lab.example.com] |
Example 2.
A developer has asked you to configure Ansible to automate the setup of web servers for your company’s intranet web site. The developer is using the host servera
to develop the web site and test your Ansible playbook.
A working directory, /home/student/imp-playbook, has been created on workstation for the purpose of managing the managed node, servera. The directory has already been populated with an ansible.cfg
configuration file and an inventory
inventory file. The managed host, servera, is already defined in this inventory file. The developer needs the managed host to have the latest versions of the httpd
and firewalld
packages installed. Also, the httpd
and firewalld
services need to be enabled and running. Lastly, firewalld should allow remote systems access to the HTTP service.
Construct a playbook on the control node, workstation, called /home/student/impplaybook/intranet.yml. Create a play in this playbook that configures the managed host as requested by the developer. Also include a task to create the /var/www/html/index.html
file to test the installation. Populate this file with the message ‘Welcome to the example.com intranet! ‘.
The playbook should also contain another play which performs a test from the control node to ensure that the web server is accessible across the network. This play should be comprised of a task which makes an HTTP request to http://servera.lab.example.com/index.html
and verifies that the HTTP status return code is 200.
Final playbook intranet.yml
looks like this:
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 |
--- - name: intranet services hosts: servera.lab.example.com become: yes tasks: - block: - name: latest httpd version installed yum: name: httpd state: latest - name: latest firewalld version installed yum: name: firewalld state: latest - block: - name: firewalld permits http service firewalld: service: http permanent: true state: enabled immediate: yes - block: - name: httpd enabled and running service: name: httpd enabled: true state: started - name: firewalld enabled and running service: name: firewalld enabled: true state: started - block: - name: test html page copy: content: "Welcome to the example.com intranet!\n" dest: /var/www/html/index.html - name: test hosts: localhost tasks: - name: connect to intranet uri: url: http://servera.lab.example.com status_code: 200 |
Example.
A developer responsible for the company’s Internet website has asked you to write an Ansible playbook to automate the setup of his server environment on serverb.lab.example.com. A working directory, /home/student/imp-lab, has been created on workstation for the purpose of managing the managed node, serverb. The directory has already been populated with an ansible.cfg configuration file and an inventory inventory file. The managed host, serverb, is already defined in this inventory file. The application being developed on serverb will require the installation of the latest versions of the firewalld, httpd, php, php-mysql, and mariadb-server packages. The firewalld, httpd, and mariadb services also need to be enabled and running. The firewalld service must also provide access for remote systems to the web site provided by the httpd service.
Construct a playbook on the control node, workstation, called internet.yml
. Create a play in this playbook that configures the managed host as requested by the developer. Place the package installation tasks within a block. Place the firewall configuration task within a separate block. Place the service management tasks within another separate block. In a final block, include a task that uses the get_url
module to fetch and populate the content for the /var/www/html/index.php
file on the managed host to test the installation. This content is available for download at http://materials.example.com/
grading/var/www/html/index.php
.
The playbook should also contain another play which performs a simple test from the control node to ensure that the web server is accessible across the network as expected. This play should be comprised of a task which uses the uri
module to make an HTTP request to http:// serverb.lab.example.com/index.php
and verifies that the HTTP status return code is 200.
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 55 56 57 58 59 60 61 62 |
--- - name: internet services hosts: serverb.lab.example.com become: yes tasks: - block: - name: latest httpd version installed yum: name: httpd state: latest - name: latest firewalld version installed yum: name: firewalld state: latest - name: latest mariadb-server version installed yum: name: mariadb-server state: latest - name: latest php version installed yum: name: php state: latest - name: latest php-mysql version installed yum: name: php-mysql state: latest - block: - name: firewalld permits http service firewalld: service: http permanent: true state: enabled immediate: yes - block: - name: httpd enabled and running service: name: httpd enabled: true state: started - name: mariadb enabled and running service: name: mariadb enabled: true state: started - name: firewalld enabled and running service: name: firewalld enabled: true state: started - block: - name: get test php page get_url: url: "http://materials.example.com/grading/var/www/html/index.php" dest: /var/www/html/index.php mode: 0644 - name: test hosts: localhost tasks: - name: connect to internet web server uri: url: http://serverb.lab.example.com status_code: 200 |