在 RHEL 上排查 Ansible Playbook 和主机问题

AnsibleAnsibleBeginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

引言

在本实验中,你将学习如何排查在使用 Ansible 在 Red Hat Enterprise Linux 上工作时遇到的常见问题。你将获得实践经验,识别并解决各种问题,从初始环境设置到常见的 playbook 错误和托管主机连接问题。实验内容涵盖修复 YAML 语法、纠正 Jinja2 模板错误以及诊断远程系统上的问题。

你将首先准备一个 RHEL 环境并配置 Ansible 以实现有效的日志记录。然后,你将深入进行实际的故障排除场景,使用 Ansible 的检查模式(check mode)来诊断与服务相关的问题,并纠正防火墙配置以解决主机不可达的问题。通过本实验的结束,你将掌握一套全面的技能,用于维护健壮的 Ansible 自动化工作流。

准备 RHEL 环境并配置 Ansible 日志

在此步骤中,你将为 Ansible 自动化准备你的 Red Hat Enterprise Linux 环境。这包括安装必要的软件、创建一个专用的项目目录以及设置一个基本配置文件来控制 Ansible 的行为并启用日志记录。正确的设置是有效自动化和故障排除的第一步。

  1. 安装 Ansible

    首先,你需要安装 Ansible。核心自动化引擎由 ansible-core 包提供。使用 dnf 包管理器和 sudo 进行安装。-y 标志会自动对任何确认提示回答“是”。

    sudo dnf install -y ansible-core

    你应该会看到输出,表明该软件包及其依赖项正在安装。

    Last metadata expiration check: ...
    Dependencies resolved.
    ================================================================================
     Package             Architecture   Version                Repository      Size
    ================================================================================
    Installing:
     ansible-core        x86_64         <version>              <repo>          2.8 M
    ...
    Transaction Summary
    ================================================================================
    Install  XX Packages
    
    Total download size: XX M
    Installed size: XX M
    ...
    Complete!
  2. 创建项目目录

    将 Ansible 项目组织在专用目录中是一种最佳实践。这可以使你的 playbook、清单(inventory)和配置文件整洁地分开。让我们在你的主项目文件夹中创建一个名为 ansible_troubleshooting 的目录,并进入该目录。

    mkdir -p ~/project/ansible_troubleshooting
    cd ~/project/ansible_troubleshooting

    从现在开始,本实验中的所有命令都将在 ~/project/ansible_troubleshooting 目录内执行。

  3. 创建 Ansible 清单文件

    清单(inventory)是一个列出 Ansible 将要管理的宿主机(或节点)的文件。由于你正在单台 LabEx VM 上工作,你将配置 Ansible 来管理本地机器本身。

    创建一个名为 inventory 的文件,并在其中添加 localhostansible_connection=local 部分告诉 Ansible 直接在控制节点(你的 VM)上执行命令,而无需使用 SSH。

    echo "localhost ansible_connection=local" > inventory

    你可以使用 cat 命令验证文件内容:

    cat inventory

    预期输出:

    localhost ansible_connection=local
  4. 配置 Ansible 日志

    ansible.cfg 文件允许你为特定项目自定义 Ansible 的行为。当放置在项目目录中时,其设置将覆盖系统范围的默认设置。在这里,你将创建此文件来指定你的清单位置并启用日志记录。日志记录对于故障排除至关重要,因为它会记录每次 playbook 运行的详细信息。

    使用 nano 编辑器创建 ansible.cfg 文件。

    nano ansible.cfg

    现在,将以下内容复制并粘贴到 nano 编辑器中。此配置告诉 Ansible 使用当前目录中的 inventory 文件,并将所有日志输出写入名为 ansible.log 的文件。

    [defaults]
    inventory = /home/labex/project/ansible_troubleshooting/inventory
    log_path = /home/labex/project/ansible_troubleshooting/ansible.log

    要在 nano 中保存文件,请按 Ctrl+X,然后按 Y 确认,最后按 Enter 写入文件。

    你的环境现在已完全准备就绪。你已安装 Ansible,并配置了一个包含本地清单和已启用日志记录的项目目录,为接下来的步骤做好了准备。

在 Playbook 中修复 YAML 语法和缩进错误

在此步骤中,你将学习如何诊断和修复 Ansible playbook 中最常见的两种错误类型:YAML 语法错误和不正确的缩进。YAML 是用于编写 playbook 的语言,对结构要求非常严格。一个错放的空格或未加引号的特殊字符都可能导致 playbook 无法运行。你将使用 ansible-playbook --syntax-check 命令,这是在执行前验证 playbook 的一个重要工具。

  1. 创建包含故意错误的 Playbook

    首先,你将在项目目录(~/project/ansible_troubleshooting)中创建一个名为 webserver.yml 的新 playbook 文件。此文件包含你将要修复的故意错误。

    使用 nano 创建文件:

    nano webserver.yml

    将以下内容复制并粘贴到编辑器中。请注意两个故意设置的错误:一个包含冒号的未加引号字符串和第二个任务不正确的缩进。

    ---
    - name: Configure Web Server
      hosts: localhost
      vars:
        ## ERROR 1: Unquoted colon in string
        package_comment: This is a package: httpd
      tasks:
        - name: Install httpd package
          ansible.builtin.dnf:
            name: httpd
            state: present
    
        ## ERROR 2: Incorrect indentation
          - name: Create a test index page
            ansible.builtin.copy:
              content: "<h1>Welcome to Ansible</h1>"
              dest: /var/www/html/index.html

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

  2. 识别并修复 YAML 语法错误(未加引号的冒号)

    现在,对你刚刚创建的 playbook 运行语法检查。此命令将解析文件并报告任何语法问题,而不会实际运行任务。

    ansible-playbook --syntax-check webserver.yml

    预期输出(错误): 你将看到一个错误,因为 package_comment 的值包含一个冒号(:),但未用引号括起来。YAML 将冒号解释为键值分隔符,从而导致语法错误。

    ERROR! We were unable to read either as JSON nor YAML, these are the errors we found:
    - Syntax Error while loading YAML.
      did not find expected ':'
    
    The error appears to be in '/home/labex/project/ansible_troubleshooting/webserver.yml': line 6, column 41, but may be elsewhere in the file depending on the exact syntax problem.
    
    The offending line appears to be:
    
      vars:
        package_comment: This is a package: httpd
                                            ^ here

    解决方案: 要解决此问题,你必须将字符串用双引号括起来。再次使用 nano 打开文件:

    nano webserver.yml

    修改 vars 下面的行以添加引号:

    ## ... (rest of the file)
    vars:
      ## FIX: Add quotes around the string with a colon
      package_comment: "This is a package: httpd"
    ## ... (rest of the file)

    保存并退出编辑器。

  3. 识别并修复 YAML 缩进错误

    修复第一个错误后,再次运行语法检查。

    ansible-playbook --syntax-check webserver.yml

    预期输出(错误): 这次,Ansible 将报告与 playbook 结构相关的另一个错误。

    ERROR! A malformed block was encountered.
    
    The error appears to be in '/home/labex/project/ansible_troubleshooting/webserver.yml': line 13, column 11, but may be elsewhere in the file depending on the exact syntax problem.
    
    The offending line appears to be:
    
    
          ## ERROR 2: Incorrect indentation
          - name: Create a test index page
            ^ here

    此错误发生是因为 YAML 使用缩进来定义结构。列表中的所有项(在本例中是任务,它们是以 - 开头的列表项)必须具有相同的缩进级别。第二个任务 Create a test index page 的缩进过深。

    解决方案: 再打开一次文件以更正缩进。

    nano webserver.yml

    删除第二个任务前面的额外空格,使其连字符(-)与第一个任务的连字符完美对齐。

    ## ... (rest of the file)
    tasks:
      - name: Install httpd package
        ansible.builtin.dnf:
          name: httpd
          state: present
    
      ## FIX: Correct the indentation to align with the previous task
      - name: Create a test index page
        ansible.builtin.copy:
          content: "<h1>Welcome to Ansible</h1>"
          dest: /var/www/html/index.html

    保存并退出编辑器。

  4. 验证已更正的 Playbook

    最后,再运行一次语法检查。

    ansible-playbook --syntax-check webserver.yml

    这次,命令应该会成功完成,没有任何错误,你将看到 playbook 的名称被打印出来,确认语法现在是正确的。

    预期输出(成功):

    playbook: webserver.yml

解决 Jinja2 引号和模板路径错误

在此步骤中,你将处理与 Jinja2(Ansible 强大的模板引擎)相关的错误。你将了解为什么 Jinja2 表达式通常需要加引号,以及如何调试 playbook 找不到指定模板文件的问题。这些是 playbook 通过语法检查后常见的运行时错误。

  1. 创建 Jinja2 模板文件

    首先,你需要一个模板文件。与静态文件不同,模板可以包含变量,Ansible 在 playbook 执行期间会用实际值替换这些变量。你将创建一个简单的 HTML 模板。

    使用 nano 在项目目录(~/project/ansible_troubleshooting)中创建一个名为 index.html.j2 的文件。.j2 扩展名是 Jinja2 模板的常见约定。

    nano index.html.j2

    将以下 HTML 内容复制并粘贴到编辑器中。请注意 {{ welcome_message }} 占位符,这是一个 Jinja2 变量。

    <h1>{{ welcome_message }}</h1>
    <p>This page was deployed by Ansible.</p>

    保存文件并退出 nanoCtrl+XYEnter)。

  2. 修改 Playbook 以使用模板并引入错误

    现在,修改你的 webserver.yml playbook 以使用 ansible.builtin.template 模块。你还将引入两个新错误:一个未加引号的 Jinja2 变量和一个不正确的模板路径。

    使用 nano 打开 webserver.yml

    nano webserver.yml

    将文件中的所有内容替换为以下内容。become: true 指令告诉 Ansible 使用管理员权限(使用 sudo)执行任务,这对于安装软件和将文件写入 /var/www/html 等系统目录是必需的。

    ---
    - name: Configure Web Server
      hosts: localhost
      become: true
      vars:
        package_name: httpd
        welcome_message: "Welcome to Ansible with Jinja2"
      tasks:
        - name: Install httpd package
          ansible.builtin.dnf:
            ## ERROR 1: Unquoted Jinja2 variable
            name: { { package_name } }
            state: present
    
        - name: Create a test index page from template
          ansible.builtin.template:
            ## ERROR 2: Incorrect template source path
            src: index.j2
            dest: /var/www/html/index.html

    保存并退出编辑器。

  3. 识别并修复 Jinja2 引号错误

    即使这是一个 Jinja2 问题,它也可能表现为 YAML 语法错误。运行语法检查器,看看 Ansible 如何解释它。

    ansible-playbook --syntax-check webserver.yml

    预期输出(错误): 你将收到一个语法错误,因为以 {{ 开头的 YAML 值被视为特殊构造,必须用引号括起来才能被解释为字符串。

    ERROR! A malformed block was encountered.
    
    The error appears to be in '/home/labex/project/ansible_troubleshooting/webserver.yml': line 11, column 19, but may be elsewhere in the file depending on the exact syntax problem.
    
    The offending line appears to be:
    
              ## ERROR 1: Unquoted Jinja2 variable
              name: {{ package_name }}
                      ^ here

    解决方案: 打开 webserver.yml 并将 Jinja2 变量用双引号括起来。

    nano webserver.yml

    修改 Install httpd package 任务:

    ## ... (rest of the file)
    tasks:
      - name: Install httpd package
        ansible.builtin.dnf:
          ## FIX: Quote the Jinja2 expression
          name: "{{ package_name }}"
          state: present
    ## ... (rest of the file)

    保存并退出。语法检查现在应该通过了。

  4. 识别并修复模板路径错误

    现在语法正确了,尝试运行 playbook。

    ansible-playbook webserver.yml

    预期输出(错误): Playbook 将会失败,但这次是运行时错误,而不是语法错误。错误消息清楚地表明找不到源文件 index.j2

    TASK [Create a test index page from template] **********************************
    fatal: [localhost]: FAILED! => {"changed": false, "msg": "Could not find or access '/home/labex/project/ansible_troubleshooting/index.j2' on the Ansible Controller."}

    这是因为你的 playbook 中的 src 参数指向 index.j2,但你创建的文件名为 index.html.j2

    解决方案: 最后一次打开 webserver.yml 并更正文件名。

    nano webserver.yml

    修改 Create a test index page from template 任务中的 src 参数:

    ## ... (rest of the file)
    - name: Create a test index page from template
      ansible.builtin.template:
        ## FIX: Correct template source filename
        src: index.html.j2
        dest: /var/www/html/index.html
    ## ... (rest of the file)

    保存并退出编辑器。

  5. 成功运行 Playbook

    再次运行 playbook。它现在应该成功完成所有任务。

    ansible-playbook webserver.yml

    预期输出(成功):

    PLAY [Configure Web Server] ****************************************************
    
    TASK [Gathering Facts] *********************************************************
    ok: [localhost]
    
    TASK [Install httpd package] ***************************************************
    changed: [localhost]
    
    TASK [Create a test index page from template] **********************************
    changed: [localhost]
    
    PLAY RECAP *********************************************************************
    localhost                  : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

使用 Check Mode 解决托管主机服务错误

在此步骤中,你将学习使用 Ansible 最强大的故障排除功能之一:Check Mode。Check mode(通过 --check 标志激活)允许你运行 playbook 来查看 做出哪些更改,而无需实际修改系统上的任何内容。这对于安全地测试 playbook 和诊断问题(例如不正确的服务名称)在它们引起实际问题之前非常有用。

  1. 创建管理服务的 Playbook

    你现在将创建一个名为 service.yml 的新 playbook,旨在确保 httpd Web 服务器服务正在运行。但是,你将故意使用不正确的服务名称来模拟一个常见错误。

    使用 nano 在你的 ~/project/ansible_troubleshooting 目录中创建 service.yml 文件。

    nano service.yml

    复制并粘贴以下内容。请注意,服务名称设置为 apache2,这是 Apache Web 服务器在其他 Linux 发行版上的常见名称,但对于 RHEL 是不正确的。

    ---
    - name: Manage Web Server Service
      hosts: localhost
      become: true
      tasks:
        - name: Ensure web server service is started
          ansible.builtin.service:
            ## ERROR: Incorrect service name for RHEL
            name: apache2
            state: started
            enabled: true

    保存文件并退出 nanoCtrl+XYEnter)。

  2. 使用 Check Mode 识别服务错误

    不要正常运行 playbook,而是以 check mode 执行它。这将阻止 Ansible 进行任何更改,但允许它检查系统状态并报告它 做什么。

    ansible-playbook --check service.yml

    预期输出(错误): Playbook 将会失败。错误消息将清楚地表明找不到名为 apache2 的服务。这立即告诉你 playbook 中的 name 参数是错误的。

    TASK [Ensure web server service is started] ************************************
    fatal: [localhost]: FAILED! => {"changed": false, "msg": "Could not find the requested service 'apache2': host"}
    
    PLAY RECAP *********************************************************************
    localhost                  : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0
  3. 查找正确的服务名称

    要修复 playbook,你需要找到 RHEL 上 httpd 包的正确服务名称。一种可靠的方法是列出包安装的文件,并查找服务单元文件,该文件通常位于 /usr/lib/systemd/system/

    使用 rpm 命令查询 httpd 包:

    rpm -ql httpd | grep systemd

    预期输出: 此命令将列出与 systemd 相关的​​文件,包括服务文件。

    /usr/lib/systemd/system/httpd.service
    /usr/lib/systemd/system/[email protected]
    ...

    输出 httpd.service 表明正确的服务名称是 httpd

  4. 更正 Playbook 并再次以 Check Mode 运行

    现在你知道了正确的服务名称,请编辑 service.yml 文件。

    nano service.yml

    将服务 nameapache2 更改为 httpd

    ## ... (rest of the file)
    - name: Ensure web server service is started
      ansible.builtin.service:
        ## FIX: Correct service name for RHEL
        name: httpd
        state: started
        enabled: true

    保存并退出编辑器。现在,再次以 check mode 运行 playbook。

    ansible-playbook --check service.yml

    预期输出(Check Mode 下成功): 这次,playbook 应该报告一个 changed 状态。在 check mode 下,changed 意味着“如果这是实际运行,将进行更改”。它表明你的 playbook 逻辑现在是正确的,并且 Ansible 已识别出需要启动 httpd 服务。

    TASK [Ensure web server service is started] ************************************
    changed: [localhost]
    
    PLAY RECAP *********************************************************************
    localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

    注意: 在这个特定的基于容器的实验环境中,没有运行完整的 systemd init 系统。虽然 check mode 可以正常工作,但 ansible.builtin.service 模块的正常运行可能仍然会遇到问题。这里的关键教训是使用 check mode 来验证你的 playbook 逻辑是否与系统的配置相符。

修正防火墙配置和主机不可达问题

在最后这个步骤中,你将解决两个关键的运行时问题:由防火墙等不正确的系统配置引起的问题,以及由 Ansible inventory 文件中的错误导致的连接问题。掌握这些将帮助你解决自动化中最常见的障碍。

第一部分:修正防火墙配置

服务器配置中的一个常见任务是在防火墙中打开端口。如果 playbook 引用了目标系统中不存在的防火墙服务,它可能会失败。

  1. 安装和准备 firewalld

    首先,确保安装了 firewalld 包,因为它在 RHEL 上提供了防火墙管理服务。

    sudo dnf install -y firewalld

    启动 firewalld 服务。

    sudo systemctl start firewalld

    你还需要安装 ansible.posix collection,其中包含此实验中使用的 firewalld 模块。

    ansible-galaxy collection install ansible.posix

    注意: 你可能会看到关于 Ansible 版本兼容性的警告,但该 collection 在此实验中仍能正常工作。

  2. 创建带有防火墙错误的 Playbook

    创建一个名为 firewall.yml 的新 playbook,它尝试启用 http 服务。但是,你将故意使用不正确的服务名称 web 来触发错误。

    nano firewall.yml

    将以下内容复制并粘贴到编辑器中:

    ---
    - name: Configure System Firewall
      hosts: localhost
      become: true
      tasks:
        - name: Allow web traffic through firewall
          ansible.posix.firewalld:
            ## ERROR: 'web' is not a standard firewalld service
            service: web
            permanent: true
            state: enabled

    保存并退出 nanoCtrl+XYEnter)。

  3. 运行 Playbook 并诊断失败

    执行 playbook。它将失败,因为 firewalld 不识别名为 web 的服务。

    ansible-playbook firewall.yml

    预期输出(错误): 错误消息清楚地表明 web 不是受支持的服务,直接指出了问题所在。

    TASK [Allow web traffic through firewall] **************************************
    fatal: [localhost]: FAILED! => {"changed": false, "msg": "web is not a supported service. This is what I have."}
    
    PLAY RECAP *********************************************************************
    localhost                  : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0
  4. 查找正确的防火墙服务名称

    要查找有效的、预定义的可用服务名称列表,你可以使用 firewall-cmd 命令行工具。

    firewall-cmd --get-services

    预期输出: 你将看到一个长长的可用服务列表。浏览列表以找到正确的 Web 流量服务,即 http

    RH-Satellite-6 ... ftp http https imaps ipp ipp-client ...
  5. 更正 Playbook 并成功运行

    编辑 firewall.yml 并将不正确的服务名称 web 替换为正确的名称 http

    nano firewall.yml

    更正后的任务应如下所示:

    ## ... (rest of the file)
    - name: Allow web traffic through firewall
      ansible.posix.firewalld:
        ## FIX: Use the correct firewalld service name
        service: http
        permanent: true
        state: enabled

    保存并退出。现在,再次运行 playbook。它应该成功完成。

    ansible-playbook firewall.yml

第二部分:排查主机不可达问题

“unreachable”错误意味着 Ansible 无法连接到 inventory 中列出的主机。这通常是由主机名中的简单拼写错误引起的。

  1. 模拟一个不可达的主机

    故意在你的 inventory 文件中引入一个拼写错误,并删除本地连接设置。这将强制 Ansible 尝试连接到拼写错误的​​主机名。

    nano inventory

    localhost 更改为 localhossst 并删除 ansible_connection=local

    ## ERROR: Intentional typo in hostname, no local connection
    localhossst

    保存并退出编辑器。

  2. 修改 Playbook 以使用 Inventory 主机

    首先,你需要修改 webserver.yml playbook 以使用 inventory 主机而不是硬编码的 localhost。当 playbook 使用 hosts: localhost 时,Ansible 会将其视为特殊情况,并完全绕过 inventory 文件。

    nano webserver.yml

    hosts 行从 localhost 更改为 all

    ---
    - name: Configure Web Server
      hosts: all ## Changed from 'localhost' to use inventory hosts
      become: true
      ## ... rest of the playbook remains the same

    保存并退出编辑器。

  3. 运行 Playbook 以触发错误

    现在尝试运行修改后的 playbook。它将失败,因为 inventory 包含拼写错误 localhossst

    ansible-playbook webserver.yml

    预期输出(错误): Ansible 将会失败并报告主机为 UNREACHABLE。错误消息表明无法解析主机名。

    PLAY [Configure Web Server] ****************************************************
    
    TASK [Gathering Facts] **********************************************************
    fatal: [localhossst]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: Could not resolve hostname localhossst: Name or service not known", "unreachable": true}
    
    PLAY RECAP *********************************************************************
    localhossst                : ok=0    changed=0    unreachable=1    failed=0    skipped=0    rescued=0    ignored=0
  4. 更正 Inventory 文件

    UNREACHABLE 状态是检查主机名和网络连接的提示。在这种情况下,修复方法是更正 inventory 文件中的拼写错误。

    nano inventory

    localhossst 改回 localhost

    ## FIX: Corrected hostname
    localhost ansible_connection=local

    保存并退出。重新运行 ansible-playbook webserver.yml 现在将成功。

  5. 可选:恢复原始 Playbook

    如果你想将 playbook 恢复为使用 hosts: localhost 以便将来的实验,你可以将其改回:

    nano webserver.yml

    hosts 行改回 localhost

    ---
    - name: Configure Web Server
      hosts: localhost ## Restored to original
      become: true
      ## ... rest of the playbook

    保存并退出。此步骤演示了使用硬编码的 localhost(它会绕过 inventory)与使用 inventory 定义的主机之间的区别。

总结

在此次实验中,你通过安装 ansible-core 和配置日志记录来准备 Red Hat Enterprise Linux 环境以供 Ansible 使用,然后着手排查各种常见问题。你学会了诊断和解决 playbook 中的错误,例如修复不正确的 YAML 语法、缩进、Jinja2 引号和无效的模板路径。这些技能是编写有效且可靠的自动化代码的基础。

此外,你还解决了与被管理主机环境相关的问题。你利用 Ansible 的检查模式(check mode)安全地执行预演(dry run),并在目标节点上识别潜在的服务故障,而无需进行实际更改。本次实验最后解决了连接问题,你修正了防火墙配置以解决主机不可达的问题,从而提供了一种从控制节点到被管理主机的全面的调试方法。