{"id":3226,"date":"2020-01-22T11:46:43","date_gmt":"2020-01-22T10:46:43","guid":{"rendered":"http:\/\/miro.borodziuk.eu\/?p=3226"},"modified":"2021-05-25T16:21:00","modified_gmt":"2021-05-25T14:21:00","slug":"ansible-playbooks","status":"publish","type":"post","link":"http:\/\/miro.borodziuk.eu\/index.php\/2020\/01\/22\/ansible-playbooks\/","title":{"rendered":"Ansible Playbooks"},"content":{"rendered":"<p>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.<\/p>\n<p><!--more--><\/p>\n<p>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.<\/p>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">Blocks<\/span><br \/>\nComplex 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.<\/p>\n<pre class=\"lang:sh decode:true \">tasks:\r\n- name: first task\r\n  yum:\r\n    name: httpd\r\n    state: latest\r\n- name: second task\r\n  yum:\r\n    name: openssh-server\r\n    state: latest\r\n- name: third task\r\n  service:\r\n    name:httpd\r\n    enabled:true\r\n    state: started\r\n- name: fourth task\r\n  service:\r\n    name: sshd\r\n    enabled:true\r\n    state:started<\/pre>\n<p>&nbsp;<\/p>\n<pre class=\"lang:sh decode:true \">tasks:\r\n- block:\r\n  - name: first package task\r\n    yum:\r\n      name: httpd\r\n      state: latest\r\n  - name: second package task\r\n    yum:\r\n      name: openssh-server\r\n      state: latest\r\n- block:\r\n  - name: first service task\r\n    service:\r\n      name: httpd\r\n      enabled: true\r\n      state: started\r\n  - name: second service task\r\n    service:\r\n      name: sshd\r\n      enabled: true\r\n      state: started<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\"> Multiple plays<\/span><br \/>\nPlaybook 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.<\/p>\n<p>The following example shows the format for creating a simple playbook with multiple plays.<\/p>\n<pre class=\"lang:sh decode:true \">---\r\n# This is a simple playbook with two plays\r\n- name: first play\r\n  hosts: web.example.com\r\n  tasks:\r\n    - name: first task\r\n        service:\r\n        name: httpd\r\n        enabled: true\r\n- name: second play\r\n  hosts: database.example.com\r\n  tasks:\r\n    - name: first task\r\n      service:\r\n        name: mariadb\r\n        enabled: true\r\n...<\/pre>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>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.<\/p>\n<p>One playbook with one play, containing multiple tasks looks like this.<\/p>\n<pre class=\"lang:sh decode:true\">---\r\n\r\n- hosts: group1\r\n  tasks:\r\n  - name: Install lldpad package\r\n    yum:\r\n      name: lldpad\r\n      state: latest\r\n  - name: check lldpad service status\r\n    service:\r\n    name: lldpad\r\n      state: started<\/pre>\n<p>Explanation:<\/p>\n<ul>\n<li>hosts &#8211; Group of hosts on which the playbook will run<\/li>\n<li>Yum module is used in this task for lldpad installation<\/li>\n<li>The service module is used to check if the service is up and running after installation<\/li>\n<\/ul>\n<p>The ordering of the contents within a playbook is important, because Ansible executes plays and tasks in the order they are presented.<\/p>\n<p>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.<\/p>\n<p>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.<\/p>\n<pre class=\"lang:sh decode:true\">[group1]\r\nhost1 ansible_host=192.168.100.2 ansible_ssh_port=22\r\n[group2]\r\nhost2 ansible_host=192.168.100.3 ansible_ssh_port=22<\/pre>\n<p>&nbsp;<\/p>\n<p>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.<\/p>\n<p>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.<\/p>\n<p><code>Ansible_os_family<\/code> and <code>ansible_system_vendor<\/code> are variables gathered with gather_facts option and can be used like in this conditional example.<\/p>\n<pre class=\"lang:sh decode:true\">---\r\n\r\n- hosts: group1\r\n  tasks:\r\n  - name: Enable SELinux\r\n    selinux:\r\n      state: enabled\r\n      when: ansible_os_family == 'Debian'\r\n      register: enable_selinux\r\n\r\n  - debug:\r\n      Imsg: \"Selinux Enabled. Please restart the server to apply changes.\"\r\n    when: enable_selinux.changed == true\r\n\r\n- hosts: group2\r\n  tasks:\r\n  - name: Install apache\r\n    yum:\r\n      name: httpd\r\n      state: present\r\n    when: ansible_system_vendor == 'HP' and ansible_os_family == 'RedHat'<\/pre>\n<p>Explanation:<\/p>\n<p>Example of the when clause, In this case, when OS type is Debian. The <code>ansible_os_family<\/code> variable is gathered via<code> gather_facts<\/code> functionality.<br \/>\nThe task output is registered for future use, with its name enable_selinux<br \/>\nAnother 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.<\/p>\n<p>&nbsp;<\/p>\n<p>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.<\/p>\n<p>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.<\/p>\n<p>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.<\/p>\n<pre class=\"lang:sh decode:true \">---\r\n\r\n- hosts: group2\r\n  tasks:\r\n  - name: sshd config file modify port\r\n    lineinfile:\r\n      path: \/etc\/ssh\/sshd_config\r\n      regexp: 'Port 28675'\r\n      line: '#Port 22'\r\n    notify:\r\n    - restart sshd\r\n  handlers\r\n  - name: restart sshd\r\n    service: sshd\r\n      name: sshd\r\n      state: restarted<\/pre>\n<p>In this case, if the first task, <code>\"sshd config file modify port\"<\/code> 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.<\/p>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">Running playbooks Basic Playbook Syntax<br \/>\n<\/span><\/p>\n<pre class=\"lang:sh decode:true\">[miro@controlnode playbooks]$ cat \/etc\/ansible\/hosts\r\nmanagedhost1 ansible_host=managedhost1.example.com\r\n\r\n[labservers]\r\nmanagedhost1.example.com\r\nmanagedhost2.example.com\r\n\r\n\r\n[miro@controlnode playbooks]$ cat \/home\/miro\/ansible\/playbooks\/playbook0.yml\r\n---\r\n- hosts: labservers\r\n  become: yes\r\n  tasks:\r\n  - name: ensure apache is at the latest version\r\n    yum:\r\n      name=httpd\r\n      state=latest<\/pre>\n<p>The playbook will install apache in the newest version.<\/p>\n<pre class=\"lang:sh decode:true\">[miro@controlnode playbooks]$ ansible-playbook playbook0.yml\r\n\r\nPLAY [labservers] ****************************************************************************************************************\r\n\r\nTASK [Gathering Facts] ***********************************************************************************************************\r\nok: [managedhost2.example.com]\r\nok: [managedhost1.example.com]\r\n\r\nTASK [ensure apache is at the latest version] ************************************************************************************\r\nok: [managedhost2.example.com]\r\nchanged: [managedhost1.example.com]\r\n\r\nPLAY RECAP ***********************************************************************************************************************\r\nmanagedhost1.example.com : ok=2 changed=1 unreachable=0 failed=0\r\nmanagedhost2.example.com : ok=2 changed=0 unreachable=0 failed=0<\/pre>\n<p>Modification playbook to start and enable <code>httpd<\/code> service:<\/p>\n<pre class=\"lang:sh decode:true\">[miro@controlnode playbooks]$ cat \/home\/miro\/ansible\/playbooks\/playbook0.yml\r\n---\r\n- hosts: labservers\r\n  become: yes\r\n\r\n  tasks:\r\n  - name: ensure apache is at the latest version\r\n    yum:\r\n      name: httpd\r\n      state: latest\r\n  - name: start enable httpd\r\n    service:\r\n      name: httpd\r\n      state: started\r\n      enabled: yes\r\n\r\n[miro@controlnode playbooks]$ ansible-playbook playbook0.yml\r\n\r\nPLAY [labservers] ****************************************************************************************************************\r\n\r\nTASK [Gathering Facts] ***********************************************************************************************************\r\nok: [managedhost2.example.com]\r\nok: [managedhost1.example.com]\r\n\r\nTASK [ensure apache is at the latest version] ************************************************************************************\r\nok: [managedhost2.example.com]\r\nok: [managedhost1.example.com]\r\n\r\nTASK [start enable httpd] ********************************************************************************************************\r\nchanged: [managedhost1.example.com]\r\nchanged: [managedhost2.example.com]\r\n\r\nPLAY RECAP ***********************************************************************************************************************\r\nmanagedhost1.example.com : ok=3 changed=1 unreachable=0 failed=0\r\nmanagedhost2.example.com : ok=3 changed=1 unreachable=0 failed=0<\/pre>\n<p>Adding<code> index.html<\/code> file:<\/p>\n<pre class=\"lang:sh decode:true\">[miro@controlnode playbooks]$ cat \/home\/miro\/ansible\/playbooks\/playbook0.yml\r\n---\r\n- hosts: labservers\r\n  become: yes\r\n  tasks:\r\n   - name: ensure apache is at the latest version\r\n     yum:\r\n       name: httpd\r\n       state: latest\r\n   - name: start enable httpd\r\n     service:\r\n       name: httpd\r\n       state: started\r\n       enabled: yes\r\n   - name: create index.html\r\n     file:\r\n       path: \/var\/www\/html\/index.html\r\n       state: touch\r\n   - name: add line to index.html\r\n     lineinfile:\r\n       path: \/var\/www\/html\/index.html\r\n       line: \"Hello World !\"\r\n\r\n\r\n[miro@controlnode playbooks]$ ansible-playbook playbook0.yml\r\n\r\nPLAY [labservers] ****************************************************************************************************************\r\n\r\nTASK [Gathering Facts] ***********************************************************************************************************\r\nok: [managedhost1.example.com]\r\nok: [managedhost2.example.com]\r\n\r\nTASK [ensure apache is at the latest version] ************************************************************************************\r\nok: [managedhost2.example.com]\r\nok: [managedhost1.example.com]\r\n\r\nTASK [start enable httpd] ********************************************************************************************************\r\nok: [managedhost2.example.com]\r\nok: [managedhost1.example.com]\r\n\r\nTASK [create index.html] *********************************************************************************************************\r\nchanged: [managedhost2.example.com]\r\nchanged: [managedhost1.example.com]\r\n\r\nTASK [add line to index.html] ****************************************************************************************************\r\nchanged: [managedhost2.example.com]\r\nchanged: [managedhost1.example.com]\r\n\r\nPLAY RECAP ***********************************************************************************************************************\r\nmanagedhost1.example.com : ok=5 changed=2 unreachable=0 failed=0\r\nmanagedhost2.example.com : ok=5 changed=2 unreachable=0 failed=0<\/pre>\n<p>We can limit running playbook only on one host. It is usefull when you engineering playbooks.<\/p>\n<pre class=\"lang:sh decode:true\">[miro@controlnode playbooks]$ ansible-playbook playbook0.yml --limit managedhost1.example.com\r\n\r\nPLAY [labservers] ****************************************************************************************************************\r\n\r\nTASK [Gathering Facts] ***********************************************************************************************************\r\nok: [managedhost1.example.com]\r\n\r\nTASK [ensure apache is at the latest version] ************************************************************************************\r\nok: [managedhost1.example.com]\r\n\r\nTASK [start enable httpd] ********************************************************************************************************\r\nok: [managedhost1.example.com]\r\n\r\nTASK [create index.html] *********************************************************************************************************\r\nchanged: [managedhost1.example.com]\r\n\r\nTASK [add line to index.html] ****************************************************************************************************\r\nok: [managedhost1.example.com]\r\n\r\nPLAY RECAP ***********************************************************************************************************************\r\nmanagedhost1.example.com : ok=5 changed=1 unreachable=0 failed=0\r\n<\/pre>\n<p>Let&#8217;s check if our playbook works:<\/p>\n<pre class=\"lang:sh decode:true \">[miro@controlnode playbooks]$ curl managedhost1\/index.html\r\nHello World !<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">Use Variables to Retrieve the Results of Running Commands<\/span><\/p>\n<ul>\n<li>The <code>register<\/code> keyword<\/li>\n<li>May be referenced within the play<\/li>\n<li>Many attributes returned<\/li>\n<\/ul>\n<pre class=\"lang:sh decode:true\">---\r\n\r\n- hosts: scoldham2.mylabserver.com \r\n  tasks: \r\n  - name: copy a file \r\n    copy: \r\n      src: testfile \r\n      dest: \/home\/user\/testregister \r\n      mode: 400 \r\n    register: var \r\n  - name: output debug info \r\n    debug: msg=\"debug info is {{ var }}\"<\/pre>\n<p>Example<\/p>\n<pre class=\"lang:sh decode:true\">[miro@controlnode playbooks]$ cat playbook1.yml\r\n---\r\n\r\n- hosts: localhost\r\n  tasks:\r\n  - name: create file\r\n    file:\r\n      path: \/tmp\/newfile\r\n      state: touch\r\n    register: output\r\n  - debug: msg=\"Register output is {{output}}\"<\/pre>\n<pre class=\"lang:sh decode:true \">[miro@controlnode playbooks]$ ansible-playbook playbook1.yml\r\n\r\nPLAY [localhost] *********************************************************************************************************************************************************************************************\r\n\r\nTASK [Gathering Facts] ***************************************************************************************************************************************************************************************\r\nok: [localhost]\r\n\r\nTASK [create file] *******************************************************************************************************************************************************************************************\r\nchanged: [localhost]\r\n\r\nTASK [debug] *************************************************************************************************************************************************************************************************\r\nok: [localhost] =&gt; {\r\n\"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}\"\r\n}\r\n\r\nPLAY RECAP ***************************************************************************************************************************************************************************************************\r\nlocalhost : ok=3 changed=1 unreachable=0 failed=0<\/pre>\n<p>&nbsp;<\/p>\n<p>Let&#8217;s write uid of output to the file:<\/p>\n<pre class=\"lang:sh decode:true\">[miro@controlnode playbooks]$ cat playbook1.yml\r\n---\r\n   \r\n- hosts: localhost\r\n  tasks:\r\n  - name: create file\r\n    file:\r\n    path: \/tmp\/newfile\r\n    state: touch\r\n    register: output\r\n    - debug: msg=\"Register output is {{output}}\"\r\n\r\n  - name: edit file\r\n    lineinfile:\r\n      path: \/tmp\/newfile\r\n      line: \"{{output.uid}}\"\r\n\r\n\r\n[miro@controlnode playbooks]$ ansible-playbook playbook1.yml\r\n\r\nPLAY [localhost] ***********************************************************************************************\r\n\r\nTASK [Gathering Facts] *****************************************************************************************\r\nok: [localhost]\r\n\r\nTASK [create file] *********************************************************************************************\r\nchanged: [localhost]\r\n\r\nTASK [debug] ***************************************************************************************************\r\nok: [localhost] =&gt; {\r\n\"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}\"\r\n}\r\n\r\nTASK [edit file] ***********************************************************************************************\r\nchanged: [localhost]\r\n\r\nPLAY RECAP *****************************************************************************************************\r\nlocalhost : ok=4 changed=2 unreachable=0 failed=0\r\n\r\n[miro@controlnode playbooks]$ cat \/tmp\/newfile\r\n1000\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\"> Executing a dry run<\/span><br \/>\nAnother helpful option is the <code>-C<\/code> 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.<\/p>\n<pre class=\"lang:sh decode:true\">[student@controlnode ~]$ ansible-playbook -C webserver.yml\r\nPLAY [play to setup web server] ************************************************\r\nTASK [setup] *******************************************************************\r\nok: [servera.lab.example.com]\r\nDemonstration: Writing and executing playbooks\r\nDO407-A2.0-en-1-20160804 89\r\nTASK [latest httpd version installed] ******************************************\r\nchanged: [servera.lab.example.com]\r\nPLAY RECAP *********************************************************************\r\nservera.lab.example.com : ok=2 changed=1 unreachable=0 failed=0<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">Step-by-step execution<\/span><br \/>\nWhen developing new playbooks, it may be helpful to execute the playbook interactively. The ansible-playbook command offers the <code>--step<\/code> 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 &#8216;y&#8217; to execute the task,<code> 'n'<\/code> to skip the task, or<code> 'c'<\/code> to exit step-by-step execution and execute the remaining tasks noninteractively.<br \/>\nThe 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.<\/p>\n<pre class=\"lang:sh decode:true\">[student@controlnode ~]$ ansible-playbook --step webserver.yml\r\nPLAY [play to setup web server] ************************************************\r\nPerform task: TASK: setup (y\/n\/c): y\r\nPerform task: TASK: setup (y\/n\/c): *********************************************\r\nTASK [setup] *******************************************************************\r\nok: [servera.lab.example.com]\r\nPerform task: TASK: latest httpd version installed (y\/n\/c): y\r\nPerform task: TASK: latest httpd version installed (y\/n\/c): ********************\r\nTASK [latest httpd version installed] ******************************************\r\nok: [servera.lab.example.com]\r\nPLAY RECAP *********************************************************************\r\nservera.lab.example.com : ok=2 changed=0 unreachable=0 failed=<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">Idempotency<\/span><\/p>\n<p>When possible, try to avoid the <code>command<\/code>, <code>shell<\/code>, and <code>raw<\/code> 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 <code>\/etc\/resolv.conf<\/code> even if it already consists of the line <code>\"nameserver 192.0.2.1\"<\/code>.<\/p>\n<pre class=\"lang:sh decode:true \">- name: Non-idempotent approach with shell module\r\n  shell: echo \"nameserver 192.0.2.1\" &gt; \/etc\/resolv.conf<\/pre>\n<p>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 <code>copy<\/code> module and use that to get the desired effect. The following example will not rewrite the file<code> \/etc\/resolv.conf<\/code> if it already consists of the right content:<\/p>\n<pre class=\"lang:sh decode:true\">- name: Idempotent approach with copy module\r\n  copy:\r\n    dest: \/etc\/resolv.conf\r\n    content: \"nameserver 192.0.2.1\\n\"<\/pre>\n<p>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.<\/p>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">Example 1.<\/span><\/p>\n<p>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<br \/>\nalso 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.<br \/>\nThe 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.<\/p>\n<p>1. Log in as the student user on workstation. Change directory to the working directory and review the contents of the directory.<\/p>\n<pre class=\"lang:sh decode:true\">[student@workstation ~]$ cd \/home\/student\/imp-playdemo\r\n[student@workstation imp-playdemo]$ ls -la\r\n\r\ntotal 12\r\ndrwxrwxr-x. 3 student student 50 May 18 21:03 .\r\ndrwx------. 7 student student 4096 May 18 21:03 ..\r\n-rw-rw-r--. 1 student student 138 May 18 21:03 ansible.cfg\r\n-rw-rw-r--. 1 student student 34 May 18 21:03 inventory\r\ndrwxrwxr-x. 2 student student 6 May 18 21:03 log\r\n\r\n\r\n[student@workstation imp-playdemo]$ cat ansible.cfg\r\n\r\n[defaults]\r\nremote_user=devops\r\ninventory=inventory\r\nlog_path=\/home\/student\/imp-playdemo\/log\/ansible.log\r\nno_log=False\r\nno_target_syslog=False\r\n\r\n\r\n[student@workstation imp-playdemo]$ cat inventory\r\nlocalhost\r\nservera.lab.example.com<\/pre>\n<p>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.<\/p>\n<pre class=\"lang:sh decode:true\">---\r\n- name: ftp client installed\r\n  hosts: localhost\r\n  remote_user: devops\r\n  become: yes\r\n  become_method: sudo\r\n  become_user: root\r\n  tasks:\r\n- name: latest lftp version installed\r\n  yum:\r\n    name: lftp\r\n    state: latest<\/pre>\n<p>&#8230;<br \/>\n3. 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<br \/>\nalso ensures that the vsftpd and firewalld services are enabled and running.<br \/>\nRelated tasks are grouped together using blocks.<\/p>\n<pre class=\"lang:sh decode:true\">---\r\n- name: ftp server installed\r\n  hosts: servera.lab.example.com\r\n  remote_user: devops\r\n  become: yes\r\n  become_method: sudo\r\n  become_user: root\r\n  tasks:\r\n  - block:\r\n  - name: latest vsftpd version installed\r\n  yum:\r\n    name: vsftpd\r\n    state: latest\r\n- name: latest firewalld version installed\r\n  yum:\r\n    name: firewalld\r\n    state: latest\r\n- block:\r\n  - name: firewalld permits ftp service\r\n    firewalld:\r\n      service: ftp\r\n      permanent: true\r\n      state: enabled\r\n      immediate: yes\r\n- block:\r\n  - name: vsftpd enabled and running\r\n    service:\r\n      name: vsftpd\r\n      enabled: true\r\n      state: started\r\n  - name: firewalld enabled and running\r\n    service:\r\n      name: firewalld\r\n      enabled: true\r\n      state: started\r\n...<\/pre>\n<p>4. Execute the \/home\/student\/imp-playdemo\/ftpclient.yml playbook.<\/p>\n<pre class=\"lang:sh decode:true \">[student@workstation imp-playdemo]$ ansible-playbook ftpclient.yml\r\nPLAY [ftp client installed] ****************************************************\r\nTASK [setup] *******************************************************************\r\nok: [localhost]\r\nTASK [latest lftp version installed] *******************************************\r\nchanged: [localhost]\r\nPLAY RECAP *********************************************************************\r\nlocalhost : ok=2 changed=1 unreachable=0 failed=0\r\n5. Execute the \/home\/student\/imp-playdemo\/ftpserver.yml playbook.\r\n[student@workstation imp-playdemo]$ ansible-playbook ftpserver.yml\r\nPLAY [ftp server installed] ****************************************************\r\nTASK [setup] *******************************************************************\r\nok: [servera.lab.example.com]\r\nTASK [latest vsftpd version installed] *****************************************\r\nchanged: [servera.lab.example.com]\r\nTASK [latest firewalld version installed] **************************************\r\nok: [servera.lab.example.com]\r\nTASK [firewalld permits ftp service] *******************************************\r\nchanged: [servera.lab.example.com]\r\nTASK [vsftpd enabled and running] **********************************************\r\nchanged: [servera.lab.example.com]<\/pre>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">Example 2.<\/span><\/p>\n<p>A developer has asked you to configure Ansible to automate the setup of web servers for your company&#8217;s intranet web site. The developer is using the host <code>servera<\/code> to develop the web site and test your Ansible playbook.<br \/>\nA 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 <code>ansible.cfg<\/code> configuration file and an <code>inventory<\/code> 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 <code>httpd<\/code> and <code>firewalld<\/code> packages installed. Also, the <code>httpd<\/code> and <code>firewalld<\/code> services need to be enabled and running. Lastly, firewalld should allow remote systems access to the HTTP service.<\/p>\n<p>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 <code>\/var\/www\/html\/index.html<\/code> file to test the installation. Populate this file with the message<em> &#8216;Welcome to the example.com intranet! &#8216;<\/em>.<\/p>\n<p>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 <code>http:\/\/servera.lab.example.com\/index.html<\/code> and verifies that the HTTP status return code is 200.<\/p>\n<p>Final playbook <code>intranet.yml<\/code> looks like this:<\/p>\n<pre class=\"lang:sh decode:true\">---\r\n- name: intranet services\r\n  hosts: servera.lab.example.com\r\n  become: yes\r\n  tasks:\r\n  - block:\r\n    - name: latest httpd version installed\r\n      yum:\r\n        name: httpd\r\n        state: latest\r\n    - name: latest firewalld version installed\r\n      yum:\r\n        name: firewalld\r\n        state: latest\r\n  - block:\r\n    - name: firewalld permits http service\r\n      firewalld:\r\n        service: http\r\n        permanent: true\r\n        state: enabled\r\n        immediate: yes\r\n  - block:\r\n    - name: httpd enabled and running\r\n      service:\r\n        name: httpd\r\n        enabled: true\r\n        state: started\r\n    - name: firewalld enabled and running\r\n      service:\r\n        name: firewalld\r\n        enabled: true\r\n        state: started\r\n  - block:\r\n    - name: test html page\r\n      copy:\r\n        content: \"Welcome to the example.com intranet!\\n\"\r\n        dest: \/var\/www\/html\/index.html\r\n    - name: test\r\n      hosts: localhost\r\n        tasks:\r\n        - name: connect to intranet\r\n          uri:\r\n            url: http:\/\/servera.lab.example.com\r\n            status_code: 200<\/pre>\n<p>&nbsp;<\/p>\n<p>Example.<\/p>\n<p>A developer responsible for the company&#8217;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 <em>firewalld<\/em>, <em>httpd<\/em>, <em>php<\/em>, <em>php-mysql<\/em>, and <em>mariadb-server<\/em> packages. The <em>firewalld<\/em>, <em>httpd<\/em>, and <em>mariadb<\/em> services also need to be enabled and running. The <em>firewalld<\/em> service must also provide access for remote systems to the web site provided by the <em>httpd<\/em> service.<\/p>\n<p>Construct a playbook on the control node, workstation, called <code>internet.yml<\/code>. 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<code> get_url<\/code> module to fetch and populate the content for the <code>\/var\/www\/html\/index.php<\/code> file on the managed host to test the installation. This content is available for download at <code>http:\/\/materials.example.com\/<\/code> <code>grading\/var\/www\/html\/index.php<\/code>.<\/p>\n<p>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 <code>uri<\/code> module to make an HTTP request to<code> http:\/\/ serverb.lab.example.com\/index.php<\/code> and verifies that the HTTP status return code is 200.<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"lang:sh decode:true \">---\r\n- name: internet services\r\n  hosts: serverb.lab.example.com\r\n  become: yes\r\n  tasks:\r\n  - block:\r\n    - name: latest httpd version installed\r\n      yum:\r\n        name: httpd\r\n        state: latest\r\n    - name: latest firewalld version installed\r\n      yum:\r\n        name: firewalld\r\n        state: latest\r\n    - name: latest mariadb-server version installed\r\n      yum:\r\n        name: mariadb-server\r\n        state: latest\r\n    - name: latest php version installed\r\n      yum:\r\n        name: php\r\n        state: latest\r\n    - name: latest php-mysql version installed\r\n      yum:\r\n        name: php-mysql\r\n        state: latest\r\n  - block:\r\n    - name: firewalld permits http service\r\n      firewalld:\r\n        service: http\r\n        permanent: true\r\n        state: enabled\r\n        immediate: yes\r\n  - block:\r\n    - name: httpd enabled and running\r\n      service:\r\n        name: httpd\r\n        enabled: true\r\n        state: started\r\n    - name: mariadb enabled and running\r\n      service:\r\n        name: mariadb\r\n          enabled: true\r\n          state: started\r\n    - name: firewalld enabled and running\r\n      service:\r\n        name: firewalld\r\n          enabled: true\r\n          state: started\r\n  - block:\r\n    - name: get test php page\r\n      get_url:\r\n        url: \"http:\/\/materials.example.com\/grading\/var\/www\/html\/index.php\"\r\n        dest: \/var\/www\/html\/index.php\r\n        mode: 0644\r\n    - name: test\r\n      hosts: localhost\r\n      tasks:\r\n      - name: connect to internet web server\r\n        uri:\r\n          url: http:\/\/serverb.lab.example.com\r\n          status_code: 200<\/pre>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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.<\/p>\n","protected":false},"author":1,"featured_media":3227,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[86],"tags":[],"_links":{"self":[{"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/posts\/3226"}],"collection":[{"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/comments?post=3226"}],"version-history":[{"count":70,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/posts\/3226\/revisions"}],"predecessor-version":[{"id":3411,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/posts\/3226\/revisions\/3411"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/media\/3227"}],"wp:attachment":[{"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/media?parent=3226"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/categories?post=3226"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/tags?post=3226"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}