diff --git a/exercises/ansible_rhel/1.3-playbook/README.md b/exercises/ansible_rhel/1.3-playbook/README.md index cf3479a50..add63ded4 100644 --- a/exercises/ansible_rhel/1.3-playbook/README.md +++ b/exercises/ansible_rhel/1.3-playbook/README.md @@ -6,488 +6,112 @@ ## Table of Contents - [Workshop Exercise - Writing Your First Playbook](#workshop-exercise---writing-your-first-playbook) - - [Table of Contents](#table-of-contents) - [Objective](#objective) - [Guide](#guide) - [Step 1 - Playbook Basics](#step-1---playbook-basics) - - [Step 2 - Creating a Directory Structure and File for your Playbook](#step-2---creating-a-directory-structure-and-file-for-your-playbook) + - [Step 2 - Creating Your Playbook](#step-2---creating-your-playbook) - [Step 3 - Running the Playbook](#step-3---running-the-playbook) - - [Step 4 - Extend your Playbook: Start \& Enable Apache](#step-4---extend-your-playbook-start--enable-apache) - - [Step 5 - Extend your Playbook: Create an web.html](#step-5---extend-your-playbook-create-an-webhtml) - - [Step 6 - Practice: Apply to Multiple Host](#step-6---practice-apply-to-multiple-host) + - [Step 4 - Checking the Playbook](#step-4---checking-the-playbook) -## Objective -This exercise covers using Ansible to build two Apache web servers on Red Hat Enterprise Linux. This exercise covers the following Ansible fundamentals: +## Objective -* Understanding Ansible module parameters -* Understanding and using the following modules - * [dnf module](https://docs.ansible.com/ansible/latest/modules/dnf_module.html) - * [service module](https://docs.ansible.com/ansible/latest/modules/service_module.html) - * [copy module](https://docs.ansible.com/ansible/latest/modules/copy_module.html) -* Understanding [Idempotence](https://en.wikipedia.org/wiki/Idempotence) and how Ansible modules can be idempotent +In this exercise, you'll use Ansible to conduct basic system setup tasks on a +Red Hat Enterprise Linux server. You will become familiar with fundamental +Ansible modules like `dnf` and `user`, and learn how to create and run +playbooks. ## Guide -Playbooks are files which describe the desired configurations or steps to implement on managed hosts. Playbooks can change lengthy, complex administrative tasks into easily repeatable routines with predictable and successful outcomes. - -A playbook can have multiple plays and a play can have one or multiple tasks. In a task a *module* is called, like the modules in the previous chapter. The goal of a *play* is to map a group of hosts. The goal of a *task* is to implement modules against those hosts. - -> **Tip** -> -> Here is a nice analogy: When Ansible modules are the tools in your workshop, the inventory is the materials and the Playbooks are the instructions. +Playbooks in Ansible are essentially scripts written in YAML format. They are +used to define the tasks and configurations that Ansible will apply to your +servers. ### Step 1 - Playbook Basics +First, create a text file in YAML format for your playbook. Remember: +- Start with three dashes (`---`). +- Use spaces, not tabs, for indentation. -Playbooks are text files written in YAML format and therefore need: - -* to start with three dashes (`---`) - -* proper indentation using spaces and **not** tabs\! - -There are some important concepts: - -* **hosts**: the managed hosts to perform the tasks on - -* **tasks**: the operations to be performed by invoking Ansible modules and passing them the necessary options - -* **become**: privilege escalation in playbooks - -> **Warning** -> -> The ordering of the contents within a Playbook is important, because Ansible executes plays and tasks in the order they are presented. - -A Playbook should be **idempotent**, so if a Playbook is run once to put the hosts in the correct state, it should be safe to run it a second time and it should make no further changes to the hosts. +Key Concepts: +- `hosts`: Specifies the targets for your playbook. +- `tasks`: The actions Ansible will perform. +- `become`: Allows privilege escalation (running tasks with elevated privileges). -> **Tip** -> -> Most Ansible modules are idempotent, so it is relatively easy to ensure this is true. +> NOTE: An Ansible playbook is designed to be idempotent, meaning if you run it multiple times on the same hosts, it ensures the desired state without making redundant changes. -### Step 2 - Creating a Directory Structure and File for your Playbook - -Enough theory, it’s time to create your first Ansible playbook. In this lab you create a playbook to set up an Apache web server in three steps: - -1. Install httpd package -2. Enable/start httpd service -3. Copy over an web.html file to each web host - -This Playbook makes sure the package containing the Apache web server is installed on `node1`. - -There is a [best practice](https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html) on the preferred directory structures for playbooks. We strongly encourage you to read and understand these practices as you develop your Ansible ninja skills. That said, our playbook today is very basic and creating a complex structure will just confuse things. - -Instead, we are going to create a very simple directory structure for our playbook, and add just a couple of files to it. - -On your control host **ansible**, create a directory called `ansible-files` in your home directory and change directories into it: +### Step 2 - Creating Your Playbook +Before creating your first playbook, ensure you are in the correct directory by changing to `~/lab_inventory`: ```bash -[student@ansible-1 ~]$ mkdir ansible-files -[student@ansible-1 ~]$ cd ansible-files/ +cd ~/lab_inventory ``` -Add a file called `apache.yml` with the following content. As discussed in the previous exercises, use `vi`/`vim` or, if you are new to editors on the command line, check out the [editor intro](../0.0-support-docs/editor_intro.md) again. - -```yaml ---- -- name: Apache server installed - hosts: node1 - become: yes -``` +Now create a playbook named `system_setup.yml` to perform basic system setup: +- Update all security related packages. +- Create a new user named ‘myuser’. -This shows one of Ansible’s strengths: The Playbook syntax is easy to read and understand. In this Playbook: - -* A name is given for the play via `name:`. -* The host to run the playbook against is defined via `hosts:`. -* We enable user privilege escalation with `become:`. - -> **Tip** -> -> You obviously need to use privilege escalation to install a package or run any other task that requires root permissions. This is done in the Playbook by `become: yes`. - -Now that we've defined the play, let's add a task to get something done. We will add a task in which dnf will ensure that the Apache package is installed in the latest version. Modify the file so that it looks like the following listing: +The basic structure looks as follows: ```yaml --- -- name: Apache server installed +- name: Basic System Setup hosts: node1 - become: yes + become: true tasks: - - - name: Install Apache + - name: Update all security-related packages ansible.builtin.dnf: - name: httpd + name: '*' + state: latest + security: true + + - name: Create a new user + ansible.builtin.user: + name: myuser + state: present + create_home: true ``` -> **Tip** -> -> Since playbooks are written in YAML, alignment of the lines and keywords is crucial. Make sure to vertically align the *t* in `task` with the *b* in `become`. Once you are more familiar with Ansible, make sure to take some time and study a bit the [YAML Syntax](https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html). - -In the added lines: +> NOTE: Updating the packages may take a few minutes prior to the Ansible playbook completing. -* We started the tasks part with the keyword `tasks:`. -* A task is named and the module for the task is referenced. Here it uses the `dnf` module. -* Parameters for the module are added: - * `name:` to identify the package name - * `state:` to define the wanted state of the package +* About the `dnf` module: This module is used for package management with DNF (Dandified YUM) on RHEL and other Fedora-based systems. -> **Tip** -> -> The module parameters are individual to each module. If in doubt, look them up again with `ansible-doc`. - -Save your playbook and exit your editor. +* About the `user` module: This module is used to manage user accounts. ### Step 3 - Running the Playbook -With the introduction of Ansible Automation Platform 2, several new key components are being introduced as a part of the overall developer experience. Execution environments have been introduced to provide predictable environments to be used during automation runtime. All collection dependencies are contained within the execution environment to ensure that automation created in development environments runs the same as in production environments. - -What do you find within an execution environment? - -* RHEL UBI 8 -* Ansible 2.9 or Ansible Core 2.11 -* Python 3.8 -* Any content Collections -* Collection python or binary dependencies. - -Why use execution environments? - -They provide a standardized way to define, build and distribute the environments that the automation runs in. In a nutshell, Automation execution environments are container images that allow for easier administration of Ansible by the platform administrator. - -Considering the shift towards containerized execution of automation, automation development workflow and tooling that existed before Ansible Automation Platform 2 have had to be reimagined. In short, `ansible-navigator` replaces `ansible-playbook` and other `ansible-*` command line utilities. - -With this change, Ansible playbooks are executed using the `ansible-navigator` command on the control node. - -The prerequisites and best practices for using `ansible-navigator` have been done for you within this lab. - -These include: -* Installing the `ansible-navigator` package -* Creating a default settings `/home/student/.ansible-navigator.yml` for all your projects (optional) -* All execution environment (EE) logs are stored within `/home/student/.ansible-navigator/logs/ansible-navigator.log` -* Playbook artifacts are saved under `/tmp/artifact.json` - -For more information on the [Ansible navigator settings](https://github.com/ansible/ansible-navigator/blob/main/docs/settings.rst) - -> **Tip** -> -> The parameters for ansible-navigator maybe modified for your specific environment. The current settings use a default `ansible-navigator.yml` for all projects, but a specific `ansible-navigator.yml` can be created for each project and is the recommended practice. - -To run your playbook, use the `ansible-navigator run ` command as follows: +Execute your playbook using the `ansible-navigator` command: ```bash -[student@ansible-1 ansible-files]$ ansible-navigator run apache.yml +[student@ansible-1 lab_inventory]$ ansible-navigator run system_setup.yml -m stdout ``` -> **Tip** -> -> The existing `ansible-navigator.yml` file provides the location of your inventory file. If this was not set within your `ansible-navigator.yml` file, the command to run the playbook would be: `ansible-navigator run apache.yml -i /home/student/lab_inventory/hosts` - -When running the playbook, you'll be displayed a text user interface (TUI) that displays the play name among other information about the playbook that is currently run. - -```bash - PLAY NAME OK CHANGED UNREACHABLE FAILED SKIPPED IGNORED IN PROGRESS TASK COUNT PROGRESS -0│Apache server installed 2 1 0 0 0 0 0 2 COMPLETE -``` +Review the output to ensure each task is completed successfully. -If you notice, prior to the play name `Apache server installed`, you'll see a `0`. By pressing the `0` key on your keyboard, you will be provided a new window view displaying the different tasks that ran for the playbook completion. In this example, those tasks included the "Gathering Facts" and "Install Apache". The "Gathering Facts" is a built-in task that runs automatically at the beginning of each play. It collects information about the managed nodes. Exercises later on will cover this in more detail. The "Install Apache" was the task created within the `apache.yml` file that installed `httpd`. +### Step 4 - Checking the Playbook +Now, let’s create a second playbook for post-configuration checks, named `system_checks.yml`: -The display should look something like this: - -```bash - RESULT HOST NUMBER CHANGED TASK TASK ACTION DURATION -0│OK node1 0 False Gathering Facts gather_facts 1s -1│OK node1 1 True Install Apache dnf 4s -``` - -Taking a closer look, you'll notice that each task is associated with a number. Task 1, "Install Apache", had a change and used the `dnf` module. In this case, the change is the installation of Apache (`httpd` package) on the host `node1`. - -By pressing `0` or `1` on your keyboard, you can see further details of the task being run. If a more traditional output view is desired, type `:st` within the text user interface. - -Once you've completed, reviewing your Ansible playbook, you can exit out of the TUI via the Esc key on your keyboard. - -> **Tip** -> -> The Esc key only takes you back to the previous screen. Once at the main overview screen an additional Esc key will take you back to the terminal window. - - -Once the playbook has completed, connect to `node1` via SSH to make sure Apache has been installed: - -```bash -[student@ansible-1 ansible-files]$ ssh node1 -Last login: Wed May 15 14:03:45 2019 from 44.55.66.77 -Managed by Ansible -``` - -Use the command `rpm -qi httpd` to verify httpd is installed: - -```bash -[ec2-user@node1 ~]$ rpm -qi httpd -Name : httpd -Version : 2.4.37 -[...] -``` - -Log out of `node1` with the command `exit` so that you are back on the control host and verify the installed package with an Ansible playbook labeled `package.yml` - -{% raw %} ```yaml --- -- name: Check packages +- name: System Configuration Checks hosts: node1 become: true - vars: - package: "httpd" - tasks: - - name: Gather the package facts - ansible.builtin.package_facts: - manager: auto - - - name: Check whether a {{ package }} is installed + - name: Check user existence + ansible.builtin.command: + cmd: id myuser + register: user_check + + - name: Report user status ansible.builtin.debug: - msg: "{{ package }} {{ ansible_facts.packages[ package ][0].version }} is installed!" - when: "package in ansible_facts.packages" - -``` -{% endraw %} - - -```bash -[student@ansible-1 ~]$ ansible-navigator run package.yml -m stdout -``` - -```bash - -PLAY [Check packages] ********************************************************** - -TASK [Gathering Facts] ********************************************************* -ok: [ansible] - -TASK [Gather the package facts] ************************************************ -ok: [ansible] - -TASK [Check whether a httpd is installed] ************************************* -ok: [ansible] => { - "msg": "httpd 2.4.37 is installed!" -} - -PLAY RECAP ********************************************************************* -ansible : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 -``` - -Run the the `ansible-navigator run apache.yml` playbook for a second time, and compare the output. The output "CHANGED" now shows `0` instead of `1` and the color changed from yellow to green. This makes it easier to spot when changes have occured when running the Ansible playbook. - -### Step 4 - Extend your Playbook: Start & Enable Apache - -The next part of the Ansible playbook makes sure the Apache application is enabled and started on `node1`. - -On the control host, as your student user, edit the file `~/ansible-files/apache.yml` to add a second task using the `service` module. The Playbook should now look like this: - -```yaml ---- -- name: Apache server installed - hosts: node1 - become: true - tasks: - - - name: Install Apache - ansible.builtin.dnf: - name: httpd - - - name: Apache enabled and running - ansible.builtin.service: - name: httpd - enabled: true - state: started + msg: "User 'myuser' exists." + when: user_check.rc == 0 ``` -What exactly did we do? - -* a second task named "Apache enabled and running" is created -* a module is specified (`service`) -* The module `service` takes the name of the service (`httpd`), if it should be permanently set (`enabled`), and its current state (`started`) - - -Thus with the second task we make sure the Apache server is indeed running on the target machine. Run your extended Playbook: +Run the checks playbook: ```bash -[student@ansible-1 ~]$ ansible-navigator run apache.yml -``` - -Notice in the output, we see the play had `1` "CHANGED" shown in yellow and if we press `0` to enter the play output, you can see that task 2, "Apache enabled and running", was the task that incorporated the latest change by the "CHANGED" value being set to True and highlighted in yellow. - - -* Run the playbook a second time using `ansible-navigator` to get used to the change in the output. - -* Use an Ansible playbook labeled service_state.yml to make sure the Apache (httpd) service is running on `node1`, e.g. with: `systemctl status httpd`. - -{% raw %} -```yaml ---- -- name: Check Status - hosts: node1 - become: true - vars: - package: "httpd" - - tasks: - - name: Check status of {{ package }} service - ansible.builtin.service_facts: - register: service_state - - - ansible.builtin.debug: - var: service_state.ansible_facts.services["{{ package }}.service"].state -``` - -```bash -{% endraw %} - -[student@ansible-1 ~]$ ansible-navigator run service_state.yml +[student@ansible-1 lab_inventory]$ ansible-navigator run system_checks.yml -m stdout ``` -### Step 5 - Extend your Playbook: Create an web.html - -Check that the tasks were executed correctly and Apache is accepting connections: Make an HTTP request using Ansible’s `uri` module in a playbook named check_httpd.yml from the control node to `node1`. - -{% raw %} -```yaml ---- -- name: Check URL - hosts: control - vars: - node: "node1" - - tasks: - - name: Check that you can connect (GET) to a page and it returns a status 200 - ansible.builtin.uri: - url: "http://{{ node }}" - -``` -{% endraw %} - -> **Warning** -> -> **Expect a lot of red lines and a 403 status\!** - -```bash -[student@ansible-1 ~]$ ansible-navigator run check_httpd.yml -m stdout -``` - -There are a lot of red lines and an error: As long as there is not at least an `web.html` file to be served by Apache, it will throw an ugly "HTTP Error 403: Forbidden" status and Ansible will report an error. - -So why not use Ansible to deploy a simple `web.html` file? On the ansible control host, as the `student` user, create the directory `files` to hold file resources in `~/ansible-files/`: - -```bash -[student@ansible-1 ansible-files]$ mkdir files -``` - -Then create the file `~/ansible-files/files/web.html` on the control node: - -```html - -

Apache is running fine

- -``` - -In a previous example, you used Ansible’s `copy` module to write text supplied on the command line into a file. Now you’ll use the module in your playbook to copy a file. - -On the control node as your student user edit the file `~/ansible-files/apache.yml` and add a new task utilizing the `copy` module. It should now look like this: - -```yaml ---- -- name: Apache server installed - hosts: node1 - become: true - tasks: - - - name: Install Apache - ansible.builtin.dnf: - name: httpd - - - name: Apache enabled and running - ansible.builtin.service: - name: httpd - enabled: true - state: started - - - name: Copy index.html - ansible.builtin.copy: - src: web.html - dest: /var/www/html/index.html - mode: '644' -``` - -What does this new copy task do? The new task uses the `copy` module and defines the source and destination options for the copy operation as parameters. - -Run your extended Playbook: - -```bash -[student@ansible-1 ansible-files]$ ansible-navigator run apache.yml -m stdout -``` - -* Have a good look at the output, notice the changes of "CHANGED" and the tasks associated with that change. - -* Run the Ansible playbook check_httpd.yml using the "uri" module from above again to test Apache. The command should now return a friendly green "status: 200" line, amongst other information. - -### Step 6 - Practice: Apply to Multiple Host - -While the above, shows the simplicity of applying changes to a particular host. What about if you want to set changes to many hosts? This is where you'll notice the real power of Ansible as it applies the same set of tasks reliably to many hosts. - -* So what about changing the apache.yml Playbook to run on `node1` **and** `node2` **and** `node3`? - -As you might remember, the inventory lists all nodes as members of the group `web`: - -```ini -[web] -node1 ansible_host=11.22.33.44 -node2 ansible_host=22.33.44.55 -node3 ansible_host=33.44.55.66 -``` - -> **Tip** -> -> The IP addresses shown here are just examples, your nodes will have different IP addresses. - -Change the playbook `hosts` parameter to point to `web` instead of `node1`: - -```yaml ---- -- name: Apache server installed - hosts: web - become: true - tasks: - - - name: Install Apache - ansible.builtin.dnf: - name: httpd - - - name: Apache enabled and running - ansible.builtin.service: - name: httpd - enabled: true - state: started - - - name: Copy index.html - ansible.builtin.copy: - src: web.html - dest: /var/www/html/index.html - mode: '644' - -``` - -Now run the playbook: - -```bash -[student@ansible-1 ansible-files]$ ansible-navigator run apache.yml -m stdout -``` - -Verify if Apache is now running on all web servers (node1, node2, node3). All output should be green. - ---- -**Navigation** -
+Review the output to ensure the user creation was successful. -{% if page.url contains 'ansible_rhel_90' %} -[Previous Exercise](../2-thebasics) - [Next Exercise](../4-variables) -{% else %} -[Previous Exercise](../1.2-thebasics) - [Next Exercise](../1.4-variables) -{% endif %} -

-[Click here to return to the Ansible for Red Hat Enterprise Linux Workshop](../README.md#section-1---ansible-engine-exercises) diff --git a/exercises/ansible_rhel/1.4-variables/README.md b/exercises/ansible_rhel/1.4-variables/README.md index 6134e294f..83591f537 100644 --- a/exercises/ansible_rhel/1.4-variables/README.md +++ b/exercises/ansible_rhel/1.4-variables/README.md @@ -5,352 +5,150 @@ ## Table of Contents -* [Objective](#objective) -* [Guide](#guide) -* [Intro to Variables](#intro-to-variables) -* [Step 1 - Create Variable Files](#step-1---create-variable-files) -* [Step 2 - Create index.html Files](#step-2---create-indexhtml-files) -* [Step 3 - Create the Playbook](#step-3---create-the-playbook) -* [Step 4 - Test the Result](#step-4---test-the-result) -* [Step 5 - Ansible Facts](#step-5---ansible-facts) -* [Step 6 - Challenge Lab: Facts](#step-6---challenge-lab-facts) -* [Step 7 - Using Facts in Playbooks](#step-7---using-facts-in-playbooks) +- [Workshop Exercise - Using Variables](##workshop-exercise---using-variables) + - [Objective](#objective) + - [Guide](#guide) + - [Step 1 - Understanding Variables](#step-1---understanding-variables) + - [Step 2 - Variable Syntax and Creation](#step-2---variable-syntax-and-creation) + - [Step 3 - Running the Modified Playbook](#step-3---running-the-modified-playbook) + - [Step 4 - Advanced Variable Usage in Checks Playbook](#step-4---advanced-variable-usage-in-checks-playbook) -## Objective - -Ansible supports variables to store values that can be used in Ansible playbooks. Variables can be defined in a variety of places and have a clear precedence. Ansible substitutes the variable with its value when a task is executed. -This exercise covers variables, specifically - -* How to use variable delimiters `{{` and `}}` -* What `host_vars` and `group_vars` are and when to use them -* How to use `ansible_facts` -* How to use the `debug` module to print variables to the console window +## Objective +Extending our playbooks from Exercise 1.3, the focus turns to the creation and usage of variables in Ansible. You'll learn the syntax for defining and using variables, an essential skill for creating dynamic and adaptable playbooks. ## Guide +Variables in Ansible are powerful tools for making your playbooks flexible and reusable. They allow you to store and reuse values, making your playbooks more dynamic and adaptable. -### Intro to Variables - -Variables are referenced in Ansible Playbooks by placing the variable name in double curly braces: - - - -```yaml -Here comes a variable {{ variable1 }} -``` - - - -Variables and their values can be defined in various places: the inventory, additional files, on the command line, etc. - -The recommended practice to provide variables in the inventory is to define them in files located in two directories named `host_vars` and `group_vars`: - -* To define variables for a group "servers", a YAML file named `group_vars/servers.yml` with the variable definitions is created. -* To define variables specifically for a host `node1`, the file `host_vars/node1.yml` with the variable definitions is created. - -> **Tip** -> -> Host variables take precedence over group variables (more about precedence can be found in the [docs](https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable)). - -### Step 1 - Create Variable Files - -For understanding and practice let’s do a lab. Following up on the theme "Let’s build a web server. Or two. Or even more…​", you will change the `index.html` to show the development environment (dev/prod) a server is deployed in. - -On the ansible control host, as the `student` user, create the directories to hold the variable definitions in `~/ansible-files/`: - -```bash -[student@ansible-1 ansible-files]$ mkdir host_vars group_vars -``` - -Now create two files containing variable definitions. We’ll define a variable named `stage` which will point to different environments, `dev` or `prod`: - -* Create the file `~/ansible-files/group_vars/web.yml` with this content: - -```yaml ---- -stage: dev -``` - -* Create the file `~/ansible-files/host_vars/node2.yml` with this content: - -```yaml ---- -stage: prod -``` - -What is this about? - -* For all servers in the `web` group the variable `stage` with value `dev` is defined. So as default we flag them as members of the dev environment. -* For server `node2` this is overridden and the host is flagged as a production server. - -### Step 2 - Create web.html Files - -Now create two files in `~/ansible-files/files/`: - -One called `prod_web.html` with the following content: - -```html - -

This is a production webserver, take care!

- -``` - -And the other called `dev_web.html` with the following content: - -```html - -

This is a development webserver, have fun!

- -``` - -### Step 3 - Create the Playbook +### Step 1 - Understanding Variables +A variable in Ansible is a named representation of some data. Variables can contain simple values like strings and numbers, or more complex data like lists and dictionaries. -Now you need a Playbook that copies the prod or dev `web.html` file - according to the "stage" variable. +### Step 2 - Variable Syntax and Creation +The creation and usage of variables involve a specific syntax: -Create a new Playbook called `deploy_index_html.yml` in the `~/ansible-files/` directory. +1. Defining Variables: Variables are defined in the `vars` section of a playbook or in separate files for larger projects. +2. Variable Naming: Variable names should be descriptive and adhere to rules such as: + * Starting with a letter or underscore. + * Containing only letters, numbers, and underscores. +3. Using Variables: Variables are referenced in tasks using the double curly braces in quotes `"{{ variable_name }}"`. This syntax tells Ansible to replace it with the variable's value at runtime. -> **Tip** -> -> Note how the variable "stage" is used in the name of the file to copy. - - +Update the `system_setup.yml` playbook to include and use a variable: ```yaml --- -- name: Copy web.html - hosts: web +- name: Basic System Setup + hosts: node1 become: true + vars: + user_name: 'Roger' tasks: - - name: copy web.html - ansible.builtin.copy: - src: "{{ stage }}_web.html" - dest: /var/www/html/index.html -``` - - - -* Run the Playbook: - -```bash -[student@ansible-1 ansible-files]$ ansible-navigator run deploy_index_html.yml + - name: Update all security-related packages + ansible.builtin.dnf: + name: '*' + state: latest + security: true + + - name: Create a new user + ansible.builtin.user: + name: "{{ user_name }}" + state: present + create_home: true ``` -### Step 4 - Test the Result +Run this playbook with `ansible-navigator`. -The Ansible Playbook copies different files as index.html to the hosts, use `curl` to test it. +### Step 3 - Running the Modified Playbook -For node1: +Execute the updated playbook: ```bash -[student@ansible-1 ansible-files]$ curl http://node1 - -

This is a development webserver, have fun!

- +[student@ansible-1 lab_inventory]$ ansible-navigator run system_setup.yml -m stdout ``` -For node2: - -```bash -[student@ansible-1 ansible-files]$ curl http://node2 - -

This is a production webserver, take care!

- ``` +PLAY [Basic System Setup] ****************************************************** -For node3: - -```bash -[student@ansible-1 ansible-files]$ curl http://node3 - -

This is a development webserver, have fun!

- -``` - -> **Tip** -> -> If by now you think: There has to be a smarter way to change content in files…​ you are absolutely right. This lab was done to introduce variables, you are about to learn about templates in one of the next chapters. - -### Step 5 - Ansible Facts - -Ansible facts are variables that are automatically discovered by Ansible from a managed host. Remember the "Gathering Facts" task listed in the output of each `ansible-navigator` execution? At that moment the facts are gathered for each managed nodes. Facts can also be pulled by the `setup` module. They contain useful information stored into variables that administrators can reuse. - -To get an idea what facts Ansible collects by default, on your control node as your student user run the following playbook to get the setup details of `node1`: - -```yaml ---- -- name: Capture Setup - hosts: node1 - - tasks: +TASK [Gathering Facts] ********************************************************* +ok: [node1] - - name: Collect only facts returned by facter - ansible.builtin.setup: - gather_subset: - - 'all' - register: setup +TASK [Update all security-related packages] ************************************ +ok: [node1] - - ansible.builtin.debug: - var: setup -``` +TASK [Create a new user] ******************************************************* +changed: [node1] -```bash -[student@ansible-1 ansible-files]$ cd ~ -[student@ansible-1 ~]$ ansible-navigator run setup.yml -m stdout +PLAY RECAP ********************************************************************* +node1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ``` -This might be a bit too much, you can use filters to limit the output to certain facts, the expression is shell-style wildcard within your playbook. Create a playbook labeled `setup_filter.yml` as shown below. In this example, we filter to get the `eth0` facts as well as memory details of `node1`. +Notice how the updated playbook shows a status of changed in the Create a new user task. The user, ‘Roger’, specified within the vars section has been created. -```yaml ---- -- name: Capture Setup - hosts: node1 - - tasks: - - - name: Collect only specific facts - ansible.builtin.setup: - filter: - - 'ansible_eth0' - - 'ansible_*_mb' - register: setup - - - debug: - var: setup -``` +Verify the user creation via: ```bash -[student@ansible-1 ansible-files]$ ansible-navigator run setup_filter.yml -m stdout +[student@ansible-1 lab_inventory]$ ssh node1 id Roger ``` -### Step 6 - Challenge Lab: Facts +### Step 4 - Advanced Variable Usage in Checks Playbook +Enhance the `system_checks.yml` playbook to check for the ‘Roger’ user within the system using the `register` variable and `when` conditional statement. -* Try to find and print the distribution (Red Hat) of your managed hosts using a playbook. +The register keyword in Ansible is used to capture the output of a task and save it as a variable. -> **Tip** -> -> Use the wildcard to find the fact within your filter, then apply a filter to only print this fact. -> **Warning** -> -> **Solution below\!** +Update the `system_checks.yml` playbook: ```yaml --- -- name: Capture Setup +- name: System Configuration Checks hosts: node1 - + become: true + vars: + user_name: 'Roger' tasks: - - - name: Collect only specific facts - ansible.builtin.setup: - filter: - - '*distribution' - register: setup - - - ansible.builtin.debug: - var: setup + - name: Check user existence + ansible.builtin.command: + cmd: "id {{ user_name }}" + register: user_check + + - name: Report user status + ansible.builtin.debug: + msg: "User {{ user_name }} exists." + when: user_check.rc == 0 ``` -With the wildcard in place, the output shows: +Playbook Details: -```bash +* `register: user_check:` This captures the output of the id command into the variable user_check. +* `when: user_check.rc == 0:` This line is a conditional statement. It checks if the return code (rc) of the previous task (stored in user_check) is 0, indicating success. The debug message will only be displayed if this condition is met. -TASK [debug] ******************************************************************* -ok: [ansible] => { - "setup": { - "ansible_facts": { - "ansible_distribution": "RedHat" - }, - "changed": false, - "failed": false - } -} -``` - -With this we can conclude the variable we are looking for is labeled `ansible_distribution`. +This setup provides a practical example of how variables can be used to control the flow of tasks based on the outcomes of previous steps. -Then we can update the playbook to be explicit in its findings and change the following line: -```yaml -filter: -- '*distribution' -``` - -to: - -```yaml -filter: -- 'ansible_distribution' -``` +Run the checks playbook: ```bash -[student@ansible-1 ansible-files]$ ansible-navigator run setup_filter.yml -m stdout +[student@ansible-1 lab_inventory]$ ansible-navigator run system_checks.yml -m stdout ``` -### Step 7 - Using Facts in Playbooks - -Facts can be used in a Playbook like variables, using the proper naming, of course. Create this Playbook as `facts.yml` in the `~/ansible-files/` directory: +Output: - - -```yaml ---- -- name: Output facts within a playbook - hosts: all - tasks: - - name: Prints Ansible facts - ansible.builtin.debug: - msg: The default IPv4 address of {{ ansible_fqdn }} is {{ ansible_default_ipv4.address }} ``` - - - -> **Tip** -> -> The "debug" module is handy for e.g. debugging variables or expressions. - -Execute it to see how the facts are printed: - -```bash -[student@ansible-1 ansible-files]$ ansible-navigator run facts.yml -``` - -Within the text user interface (TUI) window, type `:st` to capture the following output: - -```bash -PLAY [Output facts within a playbook] ****************************************** +PLAY [System Configuration Checks] ********************************************* TASK [Gathering Facts] ********************************************************* -ok: [node3] -ok: [node2] ok: [node1] -ok: [ansible-1] - -TASK [Prints Ansible facts] **************************************************** -ok: [node1] => - msg: The default IPv4 address of node1 is 172.16.190.143 -ok: [node2] => - msg: The default IPv4 address of node2 is 172.16.30.170 -ok: [node3] => - msg: The default IPv4 address of node3 is 172.16.140.196 -ok: [ansible-1] => - msg: The default IPv4 address of ansible is 172.16.2.10 + +TASK [Check user existence] **************************************************** +changed: [node1] + +TASK [Report user status] ****************************************************** +ok: [node1] => { + "msg": "User Roger exists." +} PLAY RECAP ********************************************************************* -ansible-1 : ok=2 changed=0 unreachable=0 failed=0 -node1 : ok=2 changed=0 unreachable=0 failed=0 -node2 : ok=2 changed=0 unreachable=0 failed=0 -node3 : ok=2 changed=0 unreachable=0 failed=0 +node1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ``` ---- -**Navigation** -
- -{% if page.url contains 'ansible_rhel_90' %} -[Previous Exercise](../3-playbook) - [Next Exercise](../5-surveys) -{% else %} -[Previous Exercise](../1.3-playbook) - [Next Exercise](../1.5-handlers) -{% endif %} -

-[Click here to return to the Ansible for Red Hat Enterprise Linux Workshop](../README.md) +Review the output to confirm the user existence check is correctly using the variable and conditional logic. + diff --git a/exercises/ansible_rhel/1.5-handlers/README.md b/exercises/ansible_rhel/1.5-handlers/README.md index 560cc0ac0..f435156a8 100644 --- a/exercises/ansible_rhel/1.5-handlers/README.md +++ b/exercises/ansible_rhel/1.5-handlers/README.md @@ -4,304 +4,246 @@ **Read this in other languages**:
![uk](../../../images/uk.png) [English](README.md), ![japan](../../../images/japan.png)[日本語](README.ja.md), ![brazil](../../../images/brazil.png) [Portugues do Brasil](README.pt-br.md), ![france](../../../images/fr.png) [Française](README.fr.md),![Español](../../../images/col.png) [Español](README.es.md). - -## Table of Contents +# Workshop Exercises - Using Conditionals, Handlers, and Loops +## Table of Contents - [Objective](#objective) - [Guide](#guide) - - [Step 1 - Conditionals](#step-1---conditionals) - - [Step 2 - Handlers](#step-2---handlers) - - [Step 3 - Simple Loops](#step-3---simple-loops) - - [Step 4 - Loops over hashes](#step-4---loops-over-hashes) + - [Step 1 - Understanding Conditionals, Handlers, and Loops](#step-1---understanding-conditionals-handlers-and-loops) + - [Step 2 - Conditionals](#step-2---conditionals) + - [Step 3 - Handlers](#step-3---handlers) + - [Step 4 - Loops](#step-4---loops) ## Objective -Three foundational Ansible features are: - -* [Conditionals](https://docs.ansible.com/ansible/latest/user_guide/playbooks_conditionals.html) -* [Handlers](https://docs.ansible.com/ansible/latest/user_guide/playbooks_intro.html#handlers-running-operations-on-change) -* [Loops](https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html) +Expanding on Exercise 1.4, this exercise introduces the application of conditionals, handlers, and loops in Ansible playbooks. You'll learn to control task execution with conditionals, manage service responses with handlers, and efficiently handle repetitive tasks using loops. ## Guide -### Step 1 - Conditionals - -Ansible can use conditionals to execute tasks or plays when certain conditions are met. - -To implement a conditional, the `when` statement must be used, followed by the condition to test. The condition is expressed using one of the available operators like e.g. for comparison: - -| | | -| ---- | ---------------------------------------------------------------------- | -| \== | Compares two objects for equality. | -| \!= | Compares two objects for inequality. | -| \> | true if the left hand side is greater than the right hand side. | -| \>= | true if the left hand side is greater or equal to the right hand side. | -| \< | true if the left hand side is lower than the right hand side. | -| \<= | true if the left hand side is lower or equal to the right hand side. | - -For more on this, please refer to the documentation: - -As an example you would like to install an FTP server, but only on hosts that are in the "ftpserver" inventory group. - -To do that, first edit the inventory to add another group, and place `node2` in it. The section to add looks like this: - -``` ini -[ftpserver] -node2 -``` +Conditionals, handlers, and loops are advanced features in Ansible that enhance control, efficiency, and flexibility in your automation playbooks. -Edit the inventory `~/lab_inventory/hosts` to add those lines. When you are done, it will look similar to the following listing: +### Step 1 - Understanding Conditionals, Handlers, and Loops -> **Tip** -> -> The ansible_host variable only needs to be specified once for a node. When adding a node to other groups, you do not need to -> specify the variable again. +- **Conditionals**: Enable tasks to be executed based on specific conditions. +- **Handlers**: Special tasks triggered by a `notify` directive, typically used for restarting services after changes. +- **Loops**: Used to repeat a task multiple times, particularly useful when the task is similar but needs to be applied to different items. -**Important** Do not copy/paste the example below. Just edit the file to add the above lines. +### Step 2 - Conditionals -```ini -[web] -node1 ansible_host=xx.xx.xx.xx -node2 ansible_host=xx.xx.xx.xx -node3 ansible_host=xx.xx.xx.xx +Conditionals in Ansible control whether a task should run based on certain conditions. +Let's add to the system_setup.yml playbook the ability to install the Apache HTTP Server (`httpd`) only on hosts that belong to the `web` group in our inventory. -[ftpserver] -node2 - -[control] -ansible-1 ansible_host=xx.xx.xx.xx -``` - -Next create the file `ftpserver.yml` on your control host in the `~/ansible-files/` directory: +> NOTE: Previous examples had hosts set to node1 but now it is set to all. This means when you run this updated Ansible playbook you will notice updates for the new systems being automated against, the user Roger created on all new systems and the Apache web server package httpd installed on all the hosts within the web group. ```yaml --- -- name: Install vsftpd on ftpservers +- name: Basic System Setup hosts: all become: true + vars: + user_name: 'Roger' + package_name: httpd tasks: - - name: Install FTP server when host in ftpserver group - ansible.builtin.yum: - name: vsftpd + - name: Update all security-related packages + ansible.builtin.dnf: + name: '*' state: latest - when: inventory_hostname in groups["ftpserver"] -``` + security: true + update_only: true -> **Tip** -> -> By now you should know how to run Ansible Playbooks, we’ll start to be less verbose in this guide. Go create and run it. :-) - -Run it and examine the output. The expected outcome: The task is skipped on node1, node3 and the ansible host (your control host) because they are not in the ftpserver group in your inventory file. + - name: Create a new user + ansible.builtin.user: + name: "{{ user_name }}" + state: present + create_home: true -```bash -TASK [Install FTP server when host in ftpserver group] ******************************************* -skipping: [ansible-1] -skipping: [node1] -skipping: [node3] -changed: [node2] + - name: Install Apache on web servers + ansible.builtin.dnf: + name: "{{ package_name }}" + state: present + when: inventory_hostname in groups['web'] ``` -### Step 2 - Handlers - -Sometimes when a task does make a change to the system, an additional task or tasks may need to be run. For example, a change to a service’s configuration file may then require that the service be restarted so that the changed configuration takes effect. - -Here Ansible’s handlers come into play. Handlers can be seen as inactive tasks that only get triggered when explicitly invoked using the "notify" statement. Read more about them in the [Ansible Handlers](http://docs.ansible.com/ansible/latest/playbooks_intro.html#handlers-running-operations-on-change) documentation. - -As a an example, let’s write a playbook that: +In this example, `inventory_hostname in groups['web']` is the conditional statement. `inventory_hostname` refers to the name of the current host that Ansible is working on in the playbook. The condition checks if this host is part of the `web` group defined in your inventory file. If true, the task will execute and install Apache on that host. -* manages Apache’s configuration file `/etc/httpd/conf/httpd.conf` on all hosts in the `web` group -* restarts Apache when the file has changed +### Step 3 - Handlers -First we need the file Ansible will deploy, let’s just take the one from node1. Remember to replace the IP address shown in the listing below with the IP address from your individual `node1`. +Handlers are used for tasks that should only run when notified by another task. Typically, they are used to restart services after a configuration change. -```bash -[student@ansible-1 ansible-files]$ scp node1:/etc/httpd/conf/httpd.conf ~/ansible-files/files/. -httpd.conf -``` +Let's say we want to ensure the firewall is configured correctly on all web servers and then reload the firewall service to apply any new settings. We'll define a handler that reloads the firewall service and is notified by a task that ensures the desired firewall rules are in place: -Next, create the Playbook `httpd_conf.yml`. Make sure that you are in the directory `~/ansible-files`. ```yaml --- -- name: Manage httpd.conf - hosts: web +- name: Basic System Setup + hosts: all become: true - tasks: + . + . + . + - name: Install firewalld + ansible.builtin.dnf: + name: firewalld + state: present - - name: Copy Apache configuration file - ansible.builtin.copy: - src: httpd.conf - dest: /etc/httpd/conf/ - mode: '644' - notify: - - Restart_apache + - name: Ensure firewalld is running + ansible.builtin.service: + name: firewalld + state: started + enabled: true + + - name: Allow HTTPS traffic on web servers + ansible.posix.firewalld: + service: https + permanent: true + state: enabled + when: inventory_hostname in groups['web'] + notify: Reload Firewall handlers: - - name: Restart_apache + - name: Reload Firewall ansible.builtin.service: - name: httpd - state: restarted -``` - -So what’s new here? - -* The "notify" section calls the handler only when the copy task actually changes the file. That way the service is only restarted if needed - and not each time the playbook is run. -* The "handlers" section defines a task that is only run on notification. + name: firewalld + state: reloaded -
+``` -Run the playbook. We didn’t change anything in the file yet so there should not be any `changed` lines in the output and of course the handler shouldn’t have fired. +The handler Restart Apache is triggered only if the task “Allow HTTPS traffic on web servers” makes any changes. -* Now change the `Listen 80` line in `~/ansible-files/files/httpd.conf` to: +> NOTE: Notice how the name of the handler is used within the notify section of the “Reload Firewall” configuration task. This ensures that the proper handler is executed as there can be multiple handlers within an Ansible playbook. -```ini -Listen 8080 ``` +PLAY [Basic System Setup] ****************************************************** -* Run the playbook again. Now the Ansible’s output should be a lot more interesting: - - * httpd.conf should have been copied over - * The handler should have restarted Apache +TASK [Gathering Facts] ********************************************************* +ok: [node1] +ok: [node2] +ok: [ansible-1] +ok: [node3] -Apache should now listen on port 8080. Easy enough to verify: +TASK [Update all security-related packages] ************************************ +ok: [node2] +ok: [node1] +ok: [ansible-1] +ok: [node3] -```bash -[student@ansible-1 ansible-files]$ curl http://node1 -curl: (7) Failed to connect to node1 port 80: Connection refused -[student@ansible-1 ansible-files]$ curl http://node1:8080 - -

Apache is running fine

- -``` +TASK [Create a new user] ******************************************************* +ok: [node1] +ok: [node2] +ok: [ansible-1] +ok: [node3] -Leave the setting for listen on port 8080. We'll use this in a later exercise. +TASK [Install Apache on web servers] ******************************************* +skipping: [ansible-1] +ok: [node2] +ok: [node1] +ok: [node3] -### Step 3 - Simple Loops +TASK [Install firewalld] ******************************************************* +changed: [ansible-1] +changed: [node2] +changed: [node1] +changed: [node3] -Loops enable us to repeat the same task over and over again. For example, lets say you want to create multiple users. By using an Ansible loop, you can do that in a single task. Loops can also iterate over more than just basic lists. For example, if you have a list of users with their coresponding group, loop can iterate over them as well. Find out more about loops in the [Ansible Loops](https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html) documentation. +TASK [Ensure firewalld is running] ********************************************* +changed: [node3] +changed: [ansible-1] +changed: [node2] +changed: [node1] -To show the loops feature we will generate three new users on `node1`. For that, create the file `loop_users.yml` in `~/ansible-files` on your control node as your student user. We will use the `user` module to generate the user accounts. +TASK [Allow HTTPS traffic on web servers] ************************************** +skipping: [ansible-1] +changed: [node2] +changed: [node1] +changed: [node3] - +RUNNING HANDLER [Reload Firewall] ********************************************** +changed: [node2] +changed: [node1] +changed: [node3] -```yaml ---- -- name: Ensure users - hosts: node1 - become: true +PLAY RECAP ********************************************************************* +ansible-1 : ok=5 changed=2 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0 +node1 : ok=8 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 +node2 : ok=8 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 +node3 : ok=8 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 - tasks: - - name: Ensure three users are present - ansible.builtin.user: - name: "{{ item }}" - state: present - loop: - - dev_user - - qa_user - - prod_user ``` - +### Step 4 - Loops -Understand the playbook and the output: +Loops in Ansible allow you to perform a task multiple times with different values. This feature is particularly useful for tasks like creating multiple user accounts in our given example. +In the original system_setup.yml playbook from Exercise 1.4, we had a task for creating a single user: - -* The names are not provided to the user module directly. Instead, there is only a variable called `{{ item }}` for the parameter `name`. -* The `loop` keyword lists the actual user names. Those replace the `{{ item }}` during the actual execution of the playbook. -* During execution the task is only listed once, but there are three changes listed underneath it. - - +```yaml +- name: Create a new user + ansible.builtin.user: + name: "{{ user_name }}" + state: present + create_home: true -### Step 4 - Loops over hashes +``` -As mentioned loops can also be over lists of hashes. Imagine that the users should be assigned to different additional groups: +Now, let's modify this task to create multiple users using a loop: ```yaml -- username: dev_user - groups: ftp -- username: qa_user - groups: ftp -- username: prod_user - groups: apache +- name: Create a new user + ansible.builtin.user: + name: "{{ item }}" + state: present + create_home: true + loop: + - alice + - bob + - carol ``` -The `user` module has the optional parameter `groups` to list additional users. To reference items in a hash, the `{{ item }}` keyword needs to reference the subkey: `{{ item.groups }}` for example. +What Changed? -Let's rewrite the playbook to create the users with additional user rights: +1. Loop Directive: The loop keyword is used to iterate over a list of items. In this case, the list contains the names of users we want to create: alice, bob, and carol. - +2. User Creation with Loop: Instead of creating a single user, the modified task now iterates over each item in the loop list. The `{{ item }}` placeholder is dynamically replaced with each username in the list, so the ansible.builtin.user module creates each user in turn. -```yaml ---- -- name: Ensure users - hosts: node1 - become: true +When you run the updated playbook, this task is executed three times, once for each user specified in the loop. It's an efficient way to handle repetitive tasks with varying input data. - tasks: - - name: Ensure three users are present - ansible.builtin.user: - name: "{{ item.username }}" - state: present - groups: "{{ item.groups }}" - loop: - - { username: 'dev_user', groups: 'ftp' } - - { username: 'qa_user', groups: 'ftp' } - - { username: 'prod_user', groups: 'apache' } +Snippet of the output for creating a new user on all the nodes. ``` +[student@ansible-1 ~lab_inventory]$ ansible-navigator run system_setup.yml -m stdout - +PLAY [Basic System Setup] ****************************************************** -Check the output: +. +. +. -* Again the task is listed once, but three changes are listed. Each loop with its content is shown. +TASK [Create a new user] ******************************************************* +changed: [node2] => (item=alice) +changed: [ansible-1] => (item=alice) +changed: [node1] => (item=alice) +changed: [node3] => (item=alice) +changed: [node1] => (item=bob) +changed: [ansible-1] => (item=bob) +changed: [node3] => (item=bob) +changed: [node2] => (item=bob) +changed: [node1] => (item=carol) +changed: [node3] => (item=carol) +changed: [ansible-1] => (item=carol) +changed: [node2] => (item=carol) -Verify that the user `dev_user` was indeed created on `node1` using the following playbook: +. +. +. -{% raw %} -```yaml ---- -- name: Get user ID - hosts: node1 - vars: - myuser: "dev_user" - tasks: - - name: Get {{ myuser }} info - ansible.builtin.getent: - database: passwd - key: "{{ myuser }}" - - ansible.builtin.debug: - msg: - - "{{ myuser }} uid: {{ getent_passwd[myuser].1 }}" -``` -{% endraw %} -```bash -$ ansible-navigator run user_id.yml -m stdout - -PLAY [Get user ID] ************************************************************* - -TASK [Gathering Facts] ********************************************************* -ok: [node1] - -TASK [Get dev_user info] ******************************************************* -ok: [node1] +PLAY RECAP ********************************************************************* +ansible-1 : ok=5 changed=1 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0 +node1 : ok=7 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 +node2 : ok=7 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 +node3 : ok=7 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 -TASK [debug] ******************************************************************* -ok: [node1] => { - "msg": [ - "dev_user uid: 1002" - ] -} -PLAY RECAP ********************************************************************* -node1 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ``` ---- -**Navigation** -
-[Previous Exercise](../1.4-variables) - [Next Exercise](../1.6-templates) -[Click here to return to the Ansible for Red Hat Enterprise Linux Workshop](../README.md#section-1---ansible-engine-exercises)