在 RHEL 上控制 Ansible Playbook 执行

Red Hat Enterprise LinuxBeginner
立即练习

介绍

在本实验中,你将学习如何在 Red Hat Enterprise Linux (RHEL) 系统上控制 Ansible playbook 的执行流程。你将从编写一个利用基本控制结构(包括用于高效重复任务的循环和仅在满足特定条件时运行任务的条件语句)的 playbook 开始。你还将实现 handlers,以便仅在发生更改时触发操作(例如服务重启),从而使你的自动化更智能、更高效。

在掌握了这些基础技能后,你将探索管理 playbook 执行的更高级技术。这包括使用 block 和 rescue 语句来优雅地处理任务失败,以及使用 changed_whenfailed_when 来精细控制任务状态。为了完成本次实验,你将在一个实际练习中应用所有这些概念来部署一个安全的 Web 服务器,从而巩固你创建健壮且可靠的 Ansible 自动化的能力。

编写带有循环和条件的 Playbook

在本步骤中,你将学习 Ansible 中控制任务执行的两个基本概念:循环(loops)和条件语句(conditionals)。循环允许你使用不同的值重复执行某个任务,这对于安装多个软件包或创建多个用户等任务非常高效。条件语句使用 when 关键字,允许你仅在满足特定条件时才执行任务,例如操作系统是特定版本或文件已存在。

首先,让我们确保你的 LabEx VM 上已安装 Ansible。我们将使用 DNF 包管理器来完成此操作。

sudo dnf install -y ansible-core

你应该会看到输出表明 ansible-core 及其依赖项正在安装。

...
Installed:
  ansible-core-2.x.x-1.el9.x86_64
  ...
Complete!

现在,让我们设置我们的项目目录。本次实验的所有工作都将在一个专用目录中进行,以保持整洁。

cd ~/project
mkdir control-flow-lab
cd control-flow-lab

Ansible 项目需要一个清单文件(inventory file),该文件定义了你要管理的宿主。对于本次实验,我们将管理本地机器 localhost

使用 nano 编辑器创建一个名为 inventory 的清单文件:

nano inventory

向文件中添加以下行。这会告诉 Ansible 在 localhost 上运行 playbook,并直接连接到它,而不是使用 SSH。

localhost ansible_connection=local

保存文件并按 Ctrl+X,然后按 Y,最后按 Enter 退出 nano

接下来,我们将创建第一个 playbook playbook.yml 来演示循环。此 playbook 将安装一系列有用的命令行工具。

nano playbook.yml

在编辑器中输入以下 YAML 内容。此 playbook 定义了一个使用 ansible.builtin.dnf 模块安装软件包的任务。become: yes 指令告诉 Ansible 使用 sudo 权限执行任务,这对于安装软件包是必需的。loop 关键字提供了一个软件包名称列表。Ansible 将为列表中的每个项运行此任务一次,将 {{ item }} 占位符替换为当前的软件包名称。

---
- name: Install common tools
  hosts: localhost
  become: yes
  tasks:
    - name: Install specified packages
      ansible.builtin.dnf:
        name: "{{ item }}"
        state: present
      loop:
        - git
        - tree
        - wget

保存并退出编辑器。现在,使用 ansible-playbook 命令运行 playbook,并使用 -i 标志指定你的清单文件。

ansible-playbook -i inventory playbook.yml

输出将显示 playbook 的执行情况。Ansible 将检查每个软件包,并在其尚未安装时进行安装。最后的 PLAY RECAP 总结了结果。

PLAY [Install tools and run conditional tasks] *********************************

TASK [Gathering Facts] *********************************************************
ok: [localhost]

TASK [Install specified packages] **********************************************
changed: [localhost] => (item=git)
changed: [localhost] => (item=tree)
changed: [localhost] => (item=wget)

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

现在,让我们修改 playbook 以包含一个条件任务。我们将添加一个任务,该任务将打印一条消息,但仅当操作系统是 Red Hat Enterprise Linux 时。这是根据特定环境定制自动化的常见用例。

再次打开 playbook.yml 文件:

nano playbook.yml

将以下任务添加到文件末尾。when 关键字会评估给定的表达式。ansible_facts['distribution'] 是 Ansible 自动发现的关于被管理宿主的一个变量。第一个任务将运行,因为我们的环境是 RHEL,而第二个任务将被跳过。

---
- name: Install tools and run conditional tasks
  hosts: localhost
  become: yes
  tasks:
    - name: Install specified packages
      ansible.builtin.dnf:
        name: "{{ item }}"
        state: present
      loop:
        - git
        - tree
        - wget

    - name: Show message on Red Hat systems
      ansible.builtin.debug:
        msg: "This system is a Red Hat family distribution."
      when: ansible_facts['distribution'] == "RedHat"

    - name: Show message on other systems
      ansible.builtin.debug:
        msg: "This system is NOT a Red Hat family distribution."
      when: ansible_facts['distribution'] != "RedHat"

保存并退出编辑器。运行更新后的 playbook:

ansible-playbook -i inventory playbook.yml

仔细观察输出。由于软件包已安装,软件包安装任务可能会报告所有项都为 ok。更重要的是,你将看到第一个调试消息被打印出来,而第二个消息被标记为 skipping

PLAY [Install tools and run conditional tasks] *********************************

TASK [Gathering Facts] *********************************************************
ok: [localhost]

TASK [Install specified packages] **********************************************
ok: [localhost] => (item=git)
ok: [localhost] => (item=tree)
ok: [localhost] => (item=wget)

TASK [Show message on Red Hat systems] *****************************************
ok: [localhost] => {
    "msg": "This system is a Red Hat family distribution."
}

TASK [Show message on other systems] *******************************************
skipping: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=4    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

你已成功编写并执行了一个 Ansible playbook,该 playbook 使用循环执行重复性操作,并使用条件语句根据系统事实控制任务执行。

实现 Handlers 以触发服务重启

在本步骤中,你将了解 Ansible handlers。Handlers 是特殊的任务,仅在被其他任务“通知”(notified)时运行。它们通常用于仅在发生更改时才执行的操作,例如在更新配置文件后重新启动服务。这种方法比每次 playbook 运行时都重新启动服务更有效,因为它确保了操作仅在必要时才执行。

我们将构建一个 playbook,它将安装 Nginx Web 服务器,部署自定义主页,并使用 handler 在主页内容更改时重新加载 Nginx。

首先,让我们为本次实验创建一个新目录,以保持项目整洁。

cd ~/project
mkdir control-handlers-lab
cd control-handlers-lab

和之前一样,我们需要一个清单文件来告诉 Ansible 在哪里运行 playbook。

nano inventory

添加以下行以指定本地机器。

localhost ansible_connection=local

保存并退出编辑器 (Ctrl+X, Y, Enter)。

接下来,我们需要一个文件作为我们 Web 服务器的主页。我们将创建一个 files 目录来存放它。

mkdir files

现在,在 files 目录中创建一个简单的 index.html 文件。

nano files/index.html

添加以下 HTML 内容:

<h1>Welcome to the Ansible Handler Lab!</h1>

保存并退出编辑器。

现在,你将创建 playbook deploy_nginx.yml。此 playbook 将执行三个主要操作:安装 Nginx,复制 index.html 文件,并定义一个重新加载 Nginx 的 handler。

nano deploy_nginx.yml

输入以下内容。请密切关注“Copy homepage”任务中的 notify 关键字以及末尾相应的 handlers 部分。become: yes 指令告诉 Ansible 使用 sudo 权限执行任务,这对于安装软件包和管理服务是必需的。

---
- name: Deploy Nginx with a handler
  hosts: localhost
  become: yes
  tasks:
    - name: Ensure Nginx is installed
      ansible.builtin.dnf:
        name: nginx
        state: present

    - name: Start and enable Nginx service
      ansible.builtin.systemd:
        name: nginx
        state: started
        enabled: yes

    - name: Copy homepage
      ansible.builtin.copy:
        src: files/index.html
        dest: /usr/share/nginx/html/index.html
      notify: reload nginx

  handlers:
    - name: reload nginx
      ansible.builtin.systemd:
        name: nginx
        state: reloaded

保存并退出编辑器。

现在,第一次运行 playbook。

ansible-playbook -i inventory deploy_nginx.yml

你将看到输出显示 Nginx 已安装(或已存在),Nginx 服务已启动并启用,index.html 文件已复制(状态为 changed),最重要的是,handler 在 play 结束时被通知并执行。

...
TASK [Copy homepage] ***********************************************************
changed: [localhost]

RUNNING HANDLER [reload nginx] *************************************************
changed: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

你可以使用 curl 来验证 Web 服务器是否正在运行并提供你的自定义页面。

curl http://localhost

输出应该是你的 index.html 文件内容。

<h1>Welcome to the Ansible Handler Lab!</h1>

现在,再次运行完全相同的 playbook,但不做任何更改。

ansible-playbook -i inventory deploy_nginx.yml

这次,请观察输出。由于文件在目标上已与源匹配,“Copy homepage”任务将报告 ok 而不是 changed。“Start and enable Nginx service”任务也将报告 ok,因为服务已在运行并启用。由于没有任务通知 handler,因此 handler 未运行。

...
TASK [Copy homepage] ***********************************************************
ok: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

为了再次看到 handler 的作用,让我们修改源 index.html 文件。

nano files/index.html

将内容更改为以下内容:

<h1>The Handler Ran Again!</h1>

保存并退出。现在,再运行一次 playbook。

ansible-playbook -i inventory deploy_nginx.yml

由于源文件已更改,“Copy homepage”任务将再次报告 changed,这会触发并运行 reload nginx handler。

...
TASK [Copy homepage] ***********************************************************
changed: [localhost]

RUNNING HANDLER [reload nginx] *************************************************
changed: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

最后一次使用 curl 验证更改。

curl http://localhost

你应该会看到更新后的消息。

<h1>The Handler Ran Again!</h1>

本次实验展示了 handlers 在响应配置更改时管理服务状态的强大功能和效率。

使用 Block 和 Rescue 管理任务失败

在本步骤中,你将学习如何在 Ansible playbook 中优雅地处理错误。默认情况下,如果任何任务失败,Ansible 将停止在该宿主上执行整个 playbook。虽然这是一个安全的默认设置,但有时你需要更多的控制。你将探索两种错误处理方法:简单的 ignore_errors 指令以及更强大的 blockrescuealways 结构,它提供了一种尝试执行任务并在它们失败时定义恢复操作的方法。

首先,让我们为本次实验创建一个新目录。

cd ~/project
mkdir control-errors-lab
cd control-errors-lab

localhost 创建标准的 inventory 文件。

nano inventory

添加以下内容:

localhost ansible_connection=local

保存并退出编辑器 (Ctrl+X, Y, Enter)。

现在,让我们创建一个名为 playbook.yml 的 playbook,该 playbook 被设计为会失败。第一个任务将尝试安装一个不存在的软件包。

nano playbook.yml

输入以下内容。此 playbook 尝试安装一个假的 httpd-fake 软件包,然后安装一个真实的软件包 mariadb-server

---
- name: Demonstrate Task Failure
  hosts: localhost
  become: yes
  tasks:
    - name: Attempt to install a non-existent package
      ansible.builtin.dnf:
        name: httpd-fake
        state: present

    - name: Install MariaDB server
      ansible.builtin.dnf:
        name: mariadb-server
        state: present

保存并退出编辑器。现在,运行 playbook。

ansible-playbook -i inventory playbook.yml

你将看到第一个任务因错误消息而失败,因为找不到 httpd-fake 软件包。至关重要的是,Ansible 将停止执行,第二个任务“Install MariaDB server”将不会被执行。

...
TASK [Attempt to install a non-existent package] *******************************
fatal: [localhost]: FAILED! => {"changed": false, "msg": "No match for argument: httpd-fake", "rc": 1, "results": []}

PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

现在,让我们使用 blockrescue 来更优雅地处理此失败。block 关键字用于对一组任务进行分组。如果 block 中的任何任务失败,Ansible 将跳过 block 中其余的任务并执行 rescue 部分中的任务。无论 blockrescue 部分成功还是失败,always 部分都会运行。

修改 playbook.yml 以使用此结构。

nano playbook.yml

用以下内容替换整个文件。在这里,我们在 block 中尝试安装假的软件包。当它失败时,将运行 rescue 部分,将 mariadb-server 作为恢复步骤进行安装。always 部分将在最后打印一条消息。

---
- name: Handle Task Failure with Block and Rescue
  hosts: localhost
  become: yes
  tasks:
    - name: Attempt primary task, with recovery
      block:
        - name: Attempt to install a non-existent package
          ansible.builtin.dnf:
            name: httpd-fake
            state: present
        - name: This task will be skipped
          ansible.builtin.debug:
            msg: "This message will not appear because the previous task fails."
      rescue:
        - name: Install MariaDB server on failure
          ansible.builtin.dnf:
            name: mariadb-server
            state: present
      always:
        - name: This always runs
          ansible.builtin.debug:
            msg: "The block has completed, either by success or rescue."

保存并退出。再次运行 playbook。

ansible-playbook -i inventory playbook.yml

观察输出。block 中的第一个任务如预期失败。block 中的第二个任务被跳过。然后 Ansible 会进入 rescue 部分并成功安装 mariadb-server。最后,always 部分运行。

...
TASK [Attempt to install a non-existent package] *******************************
fatal: [localhost]: FAILED! => ...

TASK [This task will be skipped] ***********************************************
skipping: [localhost]

RESCUE START *******************************************************************

TASK [Install MariaDB server on failure] ***************************************
changed: [localhost]

ALWAYS START *******************************************************************

TASK [This always runs] ********************************************************
ok: [localhost] => {
    "msg": "The block has completed, either by success or rescue."
}

PLAY RECAP *********************************************************************
localhost                  : ok=3    changed=1    unreachable=0    failed=0    skipped=1    rescued=1    ignored=0

现在,让我们看看当 block 成功时会发生什么。编辑 playbook 并修复软件包名称。

nano playbook.yml

httpd-fake 改为一个真实的软件包 httpd

## ... (rest of the playbook)
block:
  - name: Attempt to install a valid package
    ansible.builtin.dnf:
      name: httpd ## Corrected from httpd-fake
      state: present
  - name: This task will now run
    ansible.builtin.debug:
      msg: "This message will now appear because the previous task succeeds."
## ... (rest of the playbook)

保存并退出。最后一次运行 playbook。

ansible-playbook -i inventory playbook.yml

这次,block 中的两个任务都成功了。由于 block 在没有错误的情况下完成,因此 rescue 部分被完全跳过。如其名称所示,always 部分仍然运行。

...
TASK [Attempt to install a valid package] **************************************
changed: [localhost]

TASK [This task will now run] **************************************************
ok: [localhost] => {
    "msg": "This message will now appear because the previous task succeeds."
}

RESCUE START *******************************************************************
skipping rescue

ALWAYS START *******************************************************************

TASK [This always runs] ********************************************************
ok: [localhost] => {
    "msg": "The block has completed, either by success or rescue."
}

PLAY RECAP *********************************************************************
localhost                  : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

你现在已经成功使用了 block/rescue/always 结构来创建一个健壮的 playbook,该 playbook 可以处理失败并执行恢复操作。

使用 changed_when 和 failed_when 控制任务状态

在本步骤中,你将学习如何更精细地控制 Ansible 如何解释任务的执行结果。你将了解两个强大的指令:changed_whenfailed_when

  • changed_when: 默认情况下,像 ansible.builtin.commandansible.builtin.shell 这样的模块几乎总是报告“changed”状态,即使它们运行的命令没有改变系统。changed_when 允许你定义一个自定义条件,以确定任务是否应报告为“changed”。这对于编写幂等(idempotent)playbook 和准确触发 handlers 至关重要。
  • failed_when: 有时,即使命令的执行结果是可以接受的,命令也可能以非零退出状态码退出(Ansible 将此视为失败)。failed_when 允许你覆盖默认的失败条件,使你的 playbook 可以基于更智能的标准继续执行,例如命令的输出或特定的退出码。

让我们开始设置一个新的项目目录。

cd ~/project
mkdir control-state-lab
cd control-state-lab

localhost 创建标准的 inventory 文件。

nano inventory

添加以下内容:

localhost ansible_connection=local

保存并退出编辑器 (Ctrl+X, Y, Enter)。

使用 changed_when

首先,让我们看看命令任务默认的行为。我们将创建一个运行 date 命令的 playbook。此命令仅打印日期,不改变系统,但 command 模块会将其报告为更改。

创建一个名为 playbook.yml 的新 playbook。

nano playbook.yml

输入以下内容:

---
- name: Control Task State
  hosts: localhost
  tasks:
    - name: Check local time (default behavior)
      ansible.builtin.command: date

保存并退出。现在,运行 playbook。

ansible-playbook -i inventory playbook.yml

在输出中注意到,该任务被报告为 changed=1,即使系统上没有任何内容被修改。

...
TASK [Check local time (default behavior)] *************************************
changed: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0    ...

现在,让我们使用 changed_when 来告诉 Ansible 此命令永远不会改变系统。修改 playbook.yml

nano playbook.yml

向任务添加 changed_when: false

---
- name: Control Task State
  hosts: localhost
  tasks:
    - name: Check local time (with changed_when)
      ansible.builtin.command: date
      changed_when: false

保存并退出。再次运行 playbook。

ansible-playbook -i inventory playbook.yml

这次,任务报告为 ok,最终的 recap 显示 changed=0。你已成功覆盖了默认行为。

...
TASK [Check local time (with changed_when)] ************************************
ok: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    ...

使用 failed_when

接下来,让我们探索 failed_when。我们将创建一个检查不存在文件的任务。命令默认会“失败”。

首先,创建一个虚拟文件进行搜索。

echo "System is running" > status.txt

现在,修改 playbook.yml 以在文件中搜索单词“ERROR”。grep 命令将以退出码 1 退出,因为找不到该单词,Ansible 将其解释为失败。

nano playbook.yml

用以下内容替换:

---
- name: Control Task State
  hosts: localhost
  tasks:
    - name: Check for ERROR in status file (will fail)
      ansible.builtin.command: grep ERROR status.txt

保存并退出。运行 playbook。

ansible-playbook -i inventory playbook.yml

正如预期的那样,playbook 执行因 FAILED! 消息而停止。

...
TASK [Check for ERROR in status file (will fail)] ******************************
fatal: [localhost]: FAILED! => {"changed": true, "cmd": ["grep", "ERROR", "status.txt"], "delta": "...", "end": "...", "msg": "non-zero return code", "rc": 1, ...}
...

这不是我们想要的。对我们来说,“ERROR”的缺失是一个成功的条件。我们可以使用 failed_when 来重新定义什么构成失败。我们将告诉 Ansible,只有当命令的返回码大于 1 时才算失败。返回码 1(未找到模式)现在将被视为成功。我们还需要 register 任务的结果来检查其返回码 (rc)。

最后一次修改 playbook.yml

nano playbook.yml

使用 registerfailed_when 更新 playbook。

---
- name: Control Task State
  hosts: localhost
  tasks:
    - name: Check for ERROR in status file (with failed_when)
      ansible.builtin.command: grep ERROR status.txt
      register: grep_result
      failed_when: grep_result.rc > 1
      changed_when: false

我们还添加了 changed_when: false,因为 grep 是一个只读操作,不会改变系统。

保存并退出。运行最终的 playbook。

ansible-playbook -i inventory playbook.yml

成功!该任务现在报告为 ok,因为其返回码为 1,不满足我们新的失败条件 (rc > 1)。Playbook 成功完成。

...
TASK [Check for ERROR in status file (with failed_when)] ***********************
ok: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    ...

你现在已经学会了如何使用 changed_whenfailed_when 来精确定义任务的成功、更改和失败状态,从而实现更健壮和智能的自动化。

使用任务控制部署安全 Web 服务器

在最后这个步骤中,你将结合你学到的所有概念——循环(loops)、条件(conditionals)、handlers 和错误处理(error handling)——来构建一个单一的、健壮的 playbook。目标是部署 Apache Web 服务器 (httpd),使用 mod_ssl 来保护它,生成一个自签名 SSL 证书,并部署一个自定义主页。这个实践性的实验模拟了一个真实的自动化任务。

首先,让我们为这个综合实验设置项目目录。

cd ~/project
mkdir control-review-lab
cd control-review-lab

一如既往,创建一个 inventory 文件来定义你的目标宿主。

nano inventory

添加 localhost 条目:

localhost ansible_connection=local

保存并退出编辑器 (Ctrl+X, Y, Enter)。

接下来,我们需要一个目录来存储我们的 playbook 将要部署的文件。

mkdir files

现在,在 files 目录下创建一个自定义主页 index.html

nano files/index.html

添加以下 HTML 内容。这将是我们安全 Web 服务器提供的页面。

<h1>Secure Web Server Deployed by Ansible!</h1>
<p>This page is served over HTTPS.</p>

保存并退出编辑器。

现在是时候构建主 playbook deploy_secure_web.yml 了。这个 playbook 将比之前的更复杂,整合了多个概念。

nano deploy_secure_web.yml

输入以下完整的 playbook。阅读代码中的注释,以理解每个部分如何为整体目标做出贡献。

---
- name: Deploy a Secure Apache Web Server
  hosts: localhost
  become: yes
  vars:
    packages_to_install:
      - httpd
      - mod_ssl
    ssl_cert_path: /etc/pki/tls/certs/localhost.crt
    ssl_key_path: /etc/pki/tls/private/localhost.key

  tasks:
    - name: Stop nginx to free port 80
      ansible.builtin.systemd:
        name: nginx
        state: stopped
      ignore_errors: yes

    - name: Install httpd and mod_ssl packages
      ansible.builtin.dnf:
        name: "{{ packages_to_install }}"
        state: present

    - name: Generate self-signed SSL certificate if it does not exist
      ansible.builtin.command: >
        openssl req -new -nodes -x509
        -subj "/C=US/ST=None/L=None/O=LabEx/CN=localhost"
        -keyout {{ ssl_key_path }}
        -out {{ ssl_cert_path }}
      args:
        creates: "{{ ssl_cert_path }}"

    - name: Deploy custom index.html
      ansible.builtin.copy:
        src: files/index.html
        dest: /var/www/html/index.html
      notify: restart httpd

    - name: Start and enable httpd service
      ansible.builtin.systemd:
        name: httpd
        state: started
        enabled: yes

  handlers:
    - name: restart httpd
      ansible.builtin.systemd:
        name: httpd
        state: restarted

让我们分解一下这个 playbook 的作用:

  • vars: 定义要安装的软件包以及 SSL 证书和密钥的路径的变量,使 playbook 更易于阅读和维护。
  • 停止 Nginx 任务: 停止上一个实验步骤中的 nginx 服务,为 Apache 释放端口 80。如果 nginx 未运行,则使用 ignore_errors: yes
  • 安装任务: 使用 packages_to_install 变量安装 httpdmod_ssl
  • 生成证书任务: 这是一个关键任务。它使用 openssl 命令创建一个自签名证书。args: { creates: ... } 指令使此任务具有幂等性。只有当证书文件 (/etc/pki/tls/certs/localhost.crt) 不存在时,命令才会运行。
  • 部署主页任务: 复制你的自定义 index.html。至关重要的是,它使用 notify: restart httpd 在文件更改时触发 handler。
  • 启动服务任务: 使用 systemd 模块在所有配置就绪后启动并启用 httpd 服务,确保它在启动时运行。
  • Handler: restart httpd handler 使用 systemd 重新启动 Apache,这仅在配置文件或内容文件更改时触发。

保存并退出编辑器。现在,执行你的综合 playbook。

ansible-playbook -i inventory deploy_secure_web.yml

首次运行时,你应该会看到多个任务报告 changed,包括停止 nginx、安装软件包、生成证书、复制文件和启动服务。

...
TASK [Start and enable httpd service] ******************************************
changed: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=6    changed=5    unreachable=0    failed=0    ...

最后,验证你的安全 Web 服务器是否正常工作。首先测试 HTTP 版本,然后使用 -k 标志测试 HTTPS 版本,以忽略关于自签名证书的警告。

curl http://localhost

你应该会看到你的自定义主页的内容。

<h1>Secure Web Server Deployed by Ansible!</h1>
<p>This page is served over HTTPS.</p>

你也可以测试 HTTPS 版本:

curl -k https://localhost

如果再次运行 playbook,你将看到没有任务报告 changed,并且 handler 未被运行,这证明了你的 playbook 是幂等的。

恭喜!你已成功构建了一个实用的、健壮的 Ansible playbook,它结合了循环、变量、幂等的命令执行和 handlers 来部署一个安全的应用。

总结

在本实验中,你学会了在 RHEL 系统上控制 Ansible playbook 的执行。你首先设置了一个基本的项目环境,包括安装 Ansible 和创建 inventory 文件。然后,你探索了基本的控制流结构,使用循环(loops)高效地用不同的输入重复任务,并使用 when 语句的条件(conditionals)仅在特定情况下运行任务。在此基础上,你实现了 handlers 来创建响应式的自动化,例如仅在配置文件被修改时触发服务重启。

本次实验还涵盖了管理 playbook 执行的高级技术。你学会了如何使用 blockrescue 子句来优雅地处理任务失败,从而构建更健壮的 playbook。此外,你通过使用 changed_whenfailed_when 来定义自定义的成功和失败条件,获得了对任务结果的精细控制。最后,你通过将所有这些技能应用于一个实际场景来巩固它们:部署一个安全的 Web 服务器,展示了如何在实际的自动化工作流程中有效地结合循环、条件、handlers 和错误处理。