Introduction
In this lab, you will learn how to implement a complete Ansible playbook to deploy an Apache web server on a Red Hat Enterprise Linux (RHEL) system. You will begin by setting up the foundational components of an Ansible project, which includes creating a static inventory file to define your managed nodes and configuring the local Ansible environment using an ansible.cfg file.
Following the initial setup, you will write a multi-task playbook to automate the core deployment process. This involves installing and starting the Apache service, deploying a custom web page, and configuring the system firewall to allow HTTP traffic. To complete the lab, you will add a second play to your playbook that tests the web server from the command line, verifying that the entire deployment was successful.
Create a Static Inventory File for Web Servers
In this step, you will learn the fundamentals of an Ansible inventory. An inventory is a text file that lists the servers (or "managed nodes") that Ansible will manage. You will create a simple, static inventory file for a group of web servers and learn how to verify its contents.
First, you need to ensure that Ansible is installed on your system. Since it's not installed by default, you will use the dnf package manager to install it.
Open your terminal and install the
ansible-corepackage, which provides the fundamental Ansible command-line tools.sudo dnf install -y ansible-coreYou should see output indicating that the package is being installed and verified.
... Installed: ansible-core-2.16.x-x.el9.x86_64 ... Complete!For better organization, create a dedicated directory for this project within your home directory. Let's name it
ansible-lab.mkdir -p ~/project/ansible-labNavigate into your newly created project directory. All subsequent work in this lab will be done from this location.
cd ~/project/ansible-labNow, you will create your first inventory file. An inventory file is typically written in an INI-like format. You will use the
nanotext editor to create a file namedinventory.nano inventoryInside the
nanoeditor, add the following content. This configuration defines a group called[webservers]and adds your local machine,localhost, to this group.[webservers]is a group name. Groups are used to target multiple hosts with a single command.localhostis the hostname of the machine you want to manage. In this case, it's the LabEx VM itself.ansible_connection=localis a special variable that tells Ansible to execute commands directly on the control node (your VM) instead of trying to connect to it via SSH.
[webservers] localhost ansible_connection=localTo save the file in
nano, pressCtrl+O, thenEnterto confirm the filename, andCtrl+Xto exit the editor.With your inventory file created, you can use the
ansible-inventorycommand to parse the file and display a list of the hosts it contains. The-iflag specifies the path to your inventory file.ansible-inventory --list -i inventoryThe command will output a JSON-formatted representation of your inventory, which confirms that Ansible can read and understand your file correctly.
{ "_meta": { "hostvars": { "localhost": { "ansible_connection": "local" } } }, "all": { "children": ["ungrouped", "webservers"] }, "webservers": { "hosts": ["localhost"] } }
You have successfully created a basic static inventory file and verified that Ansible can correctly interpret it. This inventory file will be the foundation for the playbooks you write in the following steps.
Configure the Ansible Environment with ansible.cfg
In this step, you will create an Ansible configuration file, ansible.cfg. This file allows you to set default behaviors for Ansible, saving you from typing common options on the command line repeatedly. By placing an ansible.cfg file in your project directory, you can define settings like the default inventory file path, which Ansible will automatically use when run from that directory.
You should still be in the ~/project/ansible-lab directory from the previous step.
Use the
nanotext editor to create a new file namedansible.cfgin your current directory (~/project/ansible-lab).nano ansible.cfgInside the
nanoeditor, add the following content. This configuration tells Ansible where to find your default inventory file.- The
[defaults]section is a standard part of theansible.cfgfile where you define most of the default settings. - The
inventory = ./inventoryline sets the default inventory to theinventoryfile located in the current directory (.).
[defaults] inventory = ./inventorySave the file by pressing
Ctrl+O, thenEnter, and exit withCtrl+X.- The
Now that you have configured the default inventory path, you no longer need to use the
-iflag with your Ansible commands (as long as you are in the~/project/ansible-labdirectory).To test this, run the
ansible-inventory --listcommand again, but this time, omit the-i inventorypart.ansible-inventory --listYou should see the exact same JSON output as in the previous step, which confirms that Ansible is automatically finding and using your
inventoryfile thanks to the newansible.cfgconfiguration.{ "_meta": { "hostvars": { "localhost": { "ansible_connection": "local" } } }, "all": { "children": ["ungrouped", "webservers"] }, "webservers": { "hosts": ["localhost"] } }
By creating a project-specific ansible.cfg, you have made your workflow more efficient. This is a common practice in Ansible projects to ensure consistent behavior and reduce command-line complexity.
Write a Playbook to Install and Start the Apache Service
In this step, you will write your first Ansible Playbook. A playbook is a file written in YAML format that describes a set of tasks to be executed on your managed hosts. You will create a playbook that installs the Apache web server (httpd) and starts its service on the localhost machine defined in your inventory.
You should still be in the ~/project/ansible-lab directory.
First, use the
nanotext editor to create a new file namedapache.yml. This file will contain your playbook.nano apache.ymlInside the
nanoeditor, you will define a "play." A play is the core unit of a playbook and maps a group of hosts to a set of tasks. Add the following content toapache.yml.---: This is a standard YAML marker indicating the start of a document.- name: ...: This is the beginning of your play. Giving it a descriptive name is a best practice.hosts: webservers: This tells Ansible to run this play on all hosts in thewebserversgroup from your inventory file.become: true: This instructs Ansible to use privilege escalation (likesudo) to execute the tasks. This is necessary for actions like installing software or managing services.tasks:: This keyword begins the list of tasks to be performed.
--- - name: Install and start Apache web server hosts: webservers become: true tasks:Now, add the tasks to your playbook. Each task is a single action that calls an Ansible module. Indentation is critical in YAML, so ensure the tasks are indented correctly under the
tasks:section.- Task 1: Install httpd. This task uses the
ansible.builtin.dnfmodule to ensure thehttpdpackage is installed. Thestate: presentparameter means Ansible will install the package if it's missing, and do nothing if it's already installed. - Task 2: Start httpd service. This task uses the
ansible.builtin.servicemodule.state: startedensures the service is running, andenabled: trueensures it will start automatically on system boot.
Add the following tasks to your
apache.ymlfile, directly below thetasks:line:- name: Install httpd package ansible.builtin.dnf: name: httpd state: present - name: Start and enable httpd service ansible.builtin.service: name: httpd state: started enabled: true- Task 1: Install httpd. This task uses the
Your complete
apache.ymlplaybook should now look like this. Double-check the indentation carefully.--- - name: Install and start Apache web server hosts: webservers become: true tasks: - name: Install httpd package ansible.builtin.dnf: name: httpd state: present - name: Start and enable httpd service ansible.builtin.service: name: httpd state: started enabled: trueSave the file and exit
nano(Ctrl+O,Enter,Ctrl+X).Before running your playbook, it's a good practice to check it for syntax errors using the
ansible-playbookcommand with the--syntax-checkflag.ansible-playbook --syntax-check apache.ymlIf the syntax is correct, the command will print the playbook's filename without any errors.
playbook: apache.ymlNow, execute the playbook.
ansible-playbook apache.ymlAnsible will run through the tasks. Since this is the first run, you will see
changedstatus for both tasks, indicating that the system state was modified.PLAY [Install and start Apache web server] ************************************* TASK [Gathering Facts] ********************************************************* ok: [localhost] TASK [Install httpd package] *************************************************** changed: [localhost] TASK [Start and enable httpd service] ****************************************** changed: [localhost] PLAY RECAP ********************************************************************* localhost : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0Finally, verify that the Apache web server is running by using
curlto request the default web page fromlocalhost.curl http://localhostYou should see the default Apache Test Page, which confirms your playbook worked successfully.
<html> <head> <title>Test Page</title> </head> <body> <h1>Test Page</h1> <p>This is the default test page for the Apache HTTP server.</p> </body> </html>
Add Tasks to Deploy a Web Page
In this step, you will expand your playbook to perform more realistic web server configuration. You will add a task to deploy a custom index.html page. This demonstrates how to manage files using Ansible's file management modules.
You should still be in the ~/project/ansible-lab directory.
First, create a simple HTML file that your playbook will deploy. Use
nanoto create a file namedindex.htmlin your current directory.nano index.htmlAdd the following HTML content to the file. This will be the content of your custom web page.
<h1>Welcome to the Ansible-managed Web Server!</h1> <p>This page was deployed using an Ansible Playbook.</p>Save and exit
nano(Ctrl+O,Enter,Ctrl+X).Now, you will update your
apache.ymlplaybook to add the new task. To avoid YAML formatting errors, it's recommended to recreate the file with the complete content.Important: To ensure proper YAML formatting and avoid indentation errors, remove the existing
apache.ymlfile and create a new one with the complete content shown below.rm apache.yml nano apache.ymlYou will add a new task to the playbook. This task will copy the
index.htmlfile to the web server's document root (/var/www/html/).- Task: Deploy index.html. This task uses the
ansible.builtin.copymodule.srcspecifies the source file on the control node (index.html), anddestspecifies the destination path on the managed host.
- Task: Deploy index.html. This task uses the
Copy and paste the complete
apache.ymlplaybook content below. This ensures proper YAML formatting and indentation.--- - name: Install and start Apache web server hosts: webservers become: true tasks: - name: Install httpd package ansible.builtin.dnf: name: httpd state: present - name: Deploy custom index.html ansible.builtin.copy: src: index.html dest: /var/www/html/index.html - name: Start and enable httpd service ansible.builtin.service: name: httpd state: started enabled: trueSave and exit
nano.Save the file and exit
nano(Ctrl+O,Enter,Ctrl+X), then run the updated playbook.ansible-playbook apache.ymlThis time, you should see the new task being executed. The "Install httpd" and "Start httpd" tasks should report
okbecause their desired state has already been met. The "Deploy custom index.html" task will reportchanged.PLAY [Install and start Apache web server] ************************************* TASK [Gathering Facts] ********************************************************* ok: [localhost] TASK [Install httpd package] *************************************************** ok: [localhost] TASK [Deploy custom index.html] ************************************************ changed: [localhost] TASK [Start and enable httpd service] ****************************************** ok: [localhost] PLAY RECAP ********************************************************************* localhost : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0Finally, use
curlagain to verify that your custom web page is now being served.curl http://localhostThe output should now be the content of your
index.htmlfile.<h1>Welcome to the Ansible-managed Web Server!</h1> <p>This page was deployed using an Ansible Playbook.</p>
Implement a Second Play to Test the Web Server Deployment
In this final step, you will add a second play to your playbook. A single playbook file can contain multiple plays, which are executed sequentially. This is useful for organizing tasks that target different hosts or have different purposes. You will add a new play that runs only on the control node (localhost) to test the web server that was configured in the first play.
You should still be in the ~/project/ansible-lab directory.
You will now add a second play to your playbook. To ensure proper YAML formatting when adding the second play, it's recommended to recreate the file with the complete content.
Important: To avoid YAML indentation errors when adding the second play, remove the existing
apache.ymlfile and create a new one with the complete two-play content shown below.rm apache.yml nano apache.ymlYou will add a second play to the playbook. A second play allows you to organize tasks that target different hosts or have different purposes.
name: Test web server: A descriptive name for the new play.hosts: localhost: This play will run onlocalhost, the control node itself.become: false: This test does not require root privileges, so we explicitly disable privilege escalation.- Task: Verify web content. This task uses the
ansible.builtin.urimodule to make an HTTP request to the web server. It checks that the server returns a status code of 200 (OK) and that the returned content contains the string "Ansible-managed". This automates thecurlandgrepcheck you've been doing manually.
Copy and paste the complete
apache.ymlplaybook content below, which now includes both plays:--- - name: Install and start Apache web server hosts: webservers become: true tasks: - name: Install httpd package ansible.builtin.dnf: name: httpd state: present - name: Deploy custom index.html ansible.builtin.copy: src: index.html dest: /var/www/html/index.html - name: Start and enable httpd service ansible.builtin.service: name: httpd state: started enabled: true - name: Test web server from localhost hosts: localhost become: false tasks: - name: Verify web server is serving correct content ansible.builtin.uri: url: http://localhost return_content: yes status_code: 200 register: result failed_when: "'Ansible-managed' not in result.content"Save the file and exit
nano(Ctrl+O,Enter,Ctrl+X).Run the full playbook. Ansible will execute the first play, find that all tasks are already in their desired state (
ok), and then proceed to the second play to run the test.ansible-playbook apache.ymlThe output will show both plays being executed. All tasks should complete successfully with an
okstatus.PLAY [Install and start Apache web server] ************************************* TASK [Gathering Facts] ********************************************************* ok: [localhost] TASK [Install httpd package] *************************************************** ok: [localhost] TASK [Deploy custom index.html] ************************************************ ok: [localhost] TASK [Start and enable httpd service] ****************************************** ok: [localhost] PLAY [Test web server from localhost] ****************************************** TASK [Gathering Facts] ********************************************************* ok: [localhost] TASK [Verify web server is serving correct content] **************************** ok: [localhost] PLAY RECAP ********************************************************************* localhost : ok=6 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
By adding a second play, you have created a more robust automation workflow that not only configures a service but also includes a built-in test to verify that the deployment was successful.
Summary
In this lab, you learned how to prepare a RHEL environment for Ansible automation by installing the ansible-core package and structuring a project directory. You created a fundamental static inventory file to define a group of managed nodes, specifying localhost with a local connection. You also configured the Ansible environment using an ansible.cfg file to point to your custom inventory, establishing a clean and organized workspace for running playbooks.
You then authored a comprehensive Ansible playbook to automate the deployment of an Apache web server. This involved writing tasks to install the httpd package using the ansible.builtin.dnf module and to ensure the service was started and enabled with the ansible.builtin.service module. The playbook was enhanced to deploy a custom index.html web page using the ansible.builtin.copy module. Finally, you implemented a second play within the same playbook to validate the deployment, using the ansible.builtin.uri module to test connectivity to the newly deployed web server, demonstrating a complete setup and verification workflow.


