Implement an Ansible Playbook on RHEL

AnsibleBeginner
Practice Now

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.

  1. Open your terminal and install the ansible-core package, which provides the fundamental Ansible command-line tools.

    sudo dnf install -y ansible-core

    You should see output indicating that the package is being installed and verified.

    ...
    Installed:
      ansible-core-2.16.x-x.el9.x86_64
    ...
    Complete!
  2. For better organization, create a dedicated directory for this project within your home directory. Let's name it ansible-lab.

    mkdir -p ~/project/ansible-lab
  3. Navigate into your newly created project directory. All subsequent work in this lab will be done from this location.

    cd ~/project/ansible-lab
  4. Now, you will create your first inventory file. An inventory file is typically written in an INI-like format. You will use the nano text editor to create a file named inventory.

    nano inventory
  5. Inside the nano editor, 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.
    • localhost is the hostname of the machine you want to manage. In this case, it's the LabEx VM itself.
    • ansible_connection=local is 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=local

    To save the file in nano, press Ctrl+O, then Enter to confirm the filename, and Ctrl+X to exit the editor.

  6. With your inventory file created, you can use the ansible-inventory command to parse the file and display a list of the hosts it contains. The -i flag specifies the path to your inventory file.

    ansible-inventory --list -i inventory

    The 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.

  1. Use the nano text editor to create a new file named ansible.cfg in your current directory (~/project/ansible-lab).

    nano ansible.cfg
  2. Inside the nano editor, add the following content. This configuration tells Ansible where to find your default inventory file.

    • The [defaults] section is a standard part of the ansible.cfg file where you define most of the default settings.
    • The inventory = ./inventory line sets the default inventory to the inventory file located in the current directory (.).
    [defaults]
    inventory = ./inventory

    Save the file by pressing Ctrl+O, then Enter, and exit with Ctrl+X.

  3. Now that you have configured the default inventory path, you no longer need to use the -i flag with your Ansible commands (as long as you are in the ~/project/ansible-lab directory).

    To test this, run the ansible-inventory --list command again, but this time, omit the -i inventory part.

    ansible-inventory --list

    You should see the exact same JSON output as in the previous step, which confirms that Ansible is automatically finding and using your inventory file thanks to the new ansible.cfg configuration.

    {
      "_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.

  1. First, use the nano text editor to create a new file named apache.yml. This file will contain your playbook.

    nano apache.yml
  2. Inside the nano editor, 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 to apache.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 the webservers group from your inventory file.
    • become: true: This instructs Ansible to use privilege escalation (like sudo) 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:
  3. 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.dnf module to ensure the httpd package is installed. The state: present parameter 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.service module. state: started ensures the service is running, and enabled: true ensures it will start automatically on system boot.

    Add the following tasks to your apache.yml file, directly below the tasks: 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
  4. Your complete apache.yml playbook 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: true

    Save the file and exit nano (Ctrl+O, Enter, Ctrl+X).

  5. Before running your playbook, it's a good practice to check it for syntax errors using the ansible-playbook command with the --syntax-check flag.

    ansible-playbook --syntax-check apache.yml

    If the syntax is correct, the command will print the playbook's filename without any errors.

    playbook: apache.yml
  6. Now, execute the playbook.

    ansible-playbook apache.yml

    Ansible will run through the tasks. Since this is the first run, you will see changed status 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=0
  7. Finally, verify that the Apache web server is running by using curl to request the default web page from localhost.

    curl http://localhost

    You 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.

  1. First, create a simple HTML file that your playbook will deploy. Use nano to create a file named index.html in your current directory.

    nano index.html
  2. Add 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).

  3. Now, you will update your apache.yml playbook 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.yml file and create a new one with the complete content shown below.

    rm apache.yml
    nano apache.yml
  4. You will add a new task to the playbook. This task will copy the index.html file to the web server's document root (/var/www/html/).

    • Task: Deploy index.html. This task uses the ansible.builtin.copy module. src specifies the source file on the control node (index.html), and dest specifies the destination path on the managed host.
  5. Copy and paste the complete apache.yml playbook 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: true

    Save and exit nano.

  6. Save the file and exit nano (Ctrl+O, Enter, Ctrl+X), then run the updated playbook.

    ansible-playbook apache.yml

    This time, you should see the new task being executed. The "Install httpd" and "Start httpd" tasks should report ok because their desired state has already been met. The "Deploy custom index.html" task will report changed.

    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=0
  7. Finally, use curl again to verify that your custom web page is now being served.

    curl http://localhost

    The output should now be the content of your index.html file.

    <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.

  1. 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.yml file and create a new one with the complete two-play content shown below.

    rm apache.yml
    nano apache.yml
  2. You 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 on localhost, 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.uri module 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 the curl and grep check you've been doing manually.
  3. Copy and paste the complete apache.yml playbook 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).

  4. 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.yml

    The output will show both plays being executed. All tasks should complete successfully with an ok status.

    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.