如何在 Ansible Playbook 中显示脚本输出

AnsibleBeginner
立即练习

介绍

Ansible 是一个强大的自动化工具,可以帮助系统管理员和开发人员高效地管理基础设施。当通过 Ansible playbook 运行脚本或命令时,捕获并显示它们的输出对于监控和故障排除至关重要。

在这个实践实验(Lab)中,你将学习如何在 Ansible playbook 中有效地显示脚本输出。我们将从 Ansible playbook 的基本结构开始,然后探索各种捕获和显示命令输出的技术,最后介绍一些用于格式化和过滤输出数据的进阶方法。

通过完成这个实验,你将获得处理 Ansible 中脚本输出的不同方法的实践经验,从而能够创建更具信息量且更易于调试的自动化工作流程。

设置你的第一个 Ansible Playbook

在探索如何显示脚本输出之前,我们需要设置一个基本的 Ansible 环境并创建我们的第一个 playbook。

安装 Ansible

让我们首先确保在我们的系统上安装了 Ansible:

sudo apt update
sudo apt install -y ansible

这个命令将在你的 Ubuntu 系统上安装 Ansible。安装完成后,通过检查 Ansible 的版本来验证它:

ansible --version

你应该看到类似于以下内容的输出,这确认了 Ansible 已正确安装:

ansible [core 2.12.x]
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/labex/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3/dist-packages/ansible
  ansible collection location = /home/labex/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.10.x (default, Mar 14 2023, 14:50:41) [GCC 11.3.0]
  jinja version = 3.0.3
  libyaml = True

理解 Ansible Inventory(清单)

Ansible 需要知道要连接到哪些主机。对于这个实验,我们将使用一个简单的清单文件,其中仅包含本地机器。

为我们的 Ansible 项目创建一个目录:

mkdir -p ~/project/ansible-lab
cd ~/project/ansible-lab

现在,创建一个名为 inventory.ini 的清单文件:

echo "localhost ansible_connection=local" > inventory.ini

这个清单文件告诉 Ansible 在本地机器上运行命令,而无需使用 SSH。

创建你的第一个 Playbook

现在,让我们创建一个基本的 Ansible playbook。在 WebIDE 中,在 ~/project/ansible-lab 目录中创建一个名为 first_playbook.yml 的新文件,内容如下:

---
- name: My First Ansible Playbook
  hosts: localhost
  gather_facts: yes

  tasks:
    - name: Display a simple message
      debug:
        msg: "Hello from Ansible!"

    - name: Display system information
      debug:
        msg: "You are running {{ ansible_distribution }} {{ ansible_distribution_version }}"

这个简单的 playbook 有两个任务:

  1. 显示一个简单的问候消息
  2. 显示关于你正在运行的操作系统的信息

让我们运行这个 playbook 看看它是如何工作的:

cd ~/project/ansible-lab
ansible-playbook -i inventory.ini first_playbook.yml

你应该看到类似于这样的输出:

PLAY [My First Ansible Playbook] *********************************************************

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

TASK [Display a simple message] **********************************************************
ok: [localhost] => {
    "msg": "Hello from Ansible!"
}

TASK [Display system information] ********************************************************
ok: [localhost] => {
    "msg": "You are running Ubuntu 22.04"
}

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

这个输出显示:

  • playbook 执行成功
  • 所有任务都以 "ok" 状态完成
  • 显示了每个 debug 任务的输出

现在我们有了一个可以运行的基本 playbook,我们可以更详细地探索如何运行脚本并显示它们的输出。

运行脚本并捕获输出

现在我们已经设置了一个基本的 Ansible 环境,让我们探索如何使用 commandshell 模块运行脚本并捕获它们的输出。

Command 和 Shell 模块的区别

Ansible 提供了两个主要的模块用于执行命令:

  • command:在远程节点上执行命令,不通过 shell,这意味着 shell 操作符,如 |><& 不起作用。
  • shell:通过 shell 执行命令,允许使用 shell 操作符和环境变量。

创建一个简单的脚本来运行

首先,让我们创建一个简单的 shell 脚本,我们可以用 Ansible 运行它。在你的 ~/project/ansible-lab 目录中创建一个名为 system_info.sh 的文件:

cd ~/project/ansible-lab

在 WebIDE 中,使用以下内容创建文件:

#!/bin/bash

echo "=== System Information ==="
echo "Hostname: $(hostname)"
echo "Kernel Version: $(uname -r)"
echo "CPU Info: $(grep 'model name' /proc/cpuinfo | head -1 | cut -d':' -f2 | xargs)"
echo "Memory Info: $(free -h | grep Mem | awk '{print $2 " total, " $3 " used, " $4 " free"}')"
echo "Disk Usage: $(df -h / | grep / | awk '{print $5 " of " $2 " used"}')"

使脚本可执行:

chmod +x ~/project/ansible-lab/system_info.sh

现在让我们手动运行这个脚本,看看它会产生什么输出:

./system_info.sh

你应该看到类似于以下的输出:

=== System Information ===
Hostname: labex-xxxxxxxx
Kernel Version: 5.15.0-xx-generic
CPU Info: Intel(R) Xeon(R) xxxxxx
Memory Info: 8.0G total, 1.2G used, 5.8G free
Disk Usage: 15% of 50G used

使用 Ansible 运行脚本

现在,让我们创建一个新的 playbook,它将运行这个脚本并捕获其输出。在你的 ~/project/ansible-lab 目录中创建一个名为 script_output.yml 的文件:

---
- name: Run Script and Capture Output
  hosts: localhost
  gather_facts: no

  tasks:
    - name: Run system info script
      command: ./system_info.sh
      register: script_result

    - name: Display script output
      debug:
        msg: "{{ script_result.stdout }}"

这个 playbook 做了两件事:

  1. 使用 command 模块运行我们的 system_info.sh 脚本
  2. 使用 register 关键字将输出存储在一个名为 script_result 的变量中
  3. 使用 debug 模块显示捕获的输出

让我们运行这个 playbook:

cd ~/project/ansible-lab
ansible-playbook -i inventory.ini script_output.yml

你应该看到类似于以下的输出:

PLAY [Run Script and Capture Output] ***************************************************

TASK [Run system info script] ***********************************************************
changed: [localhost]

TASK [Display script output] ************************************************************
ok: [localhost] => {
    "msg": "=== System Information ===\nHostname: labex-xxxxxxxx\nKernel Version: 5.15.0-xx-generic\nCPU Info: Intel(R) Xeon(R) xxxxxx\nMemory Info: 8.0G total, 1.2G used, 5.8G free\nDisk Usage: 15% of 50G used"
}

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

注意输出是如何以一个带有换行符 (\n) 的字符串形式出现的。让我们在下一节中改进显示。

理解 Register 变量

register 关键字创建一个包含多个属性的变量,而不仅仅是命令的输出。让我们创建一个新的 playbook 来探索这些属性。

在你的 ~/project/ansible-lab 目录中创建一个名为 register_details.yml 的文件:

---
- name: Explore Register Variable
  hosts: localhost
  gather_facts: no

  tasks:
    - name: Run system info script
      command: ./system_info.sh
      register: script_result

    - name: Display all register properties
      debug:
        var: script_result

    - name: Display just stdout
      debug:
        var: script_result.stdout

    - name: Display stdout as a list of lines
      debug:
        var: script_result.stdout_lines

运行这个 playbook:

ansible-playbook -i inventory.ini register_details.yml

你将看到一个更详细的输出,显示了 script_result 变量的所有属性,包括:

  • stdout:标准输出,作为一个字符串
  • stderr:标准错误(如果有)
  • rc:返回码(0 表示成功)
  • stdout_lines:标准输出被分割成一个行列表

最后一个任务特别有用,因为它将输出格式化为行列表,使其更具可读性。

高级输出处理技术

现在我们了解了捕获脚本输出的基础知识,让我们探索一些更高级的技术,用于在 Ansible playbooks 中处理和显示输出。

处理脚本错误

在运行脚本时,正确处理错误情况非常重要。如果命令返回非零退出码,Ansible 会将任务标记为失败。让我们创建一个演示错误处理的 playbook。

在你的 ~/project/ansible-lab 目录中创建一个名为 error_handling.yml 的文件:

---
- name: Handling Script Errors
  hosts: localhost
  gather_facts: no

  tasks:
    - name: Run a command that might fail
      command: ls /nonexistent_directory
      register: cmd_result
      ignore_errors: yes

    - name: Show error message if command failed
      debug:
        msg: "Command failed with error: {{ cmd_result.stderr }}"
      when: cmd_result.rc != 0

    - name: Show success message if command succeeded
      debug:
        msg: "Command succeeded with output: {{ cmd_result.stdout }}"
      when: cmd_result.rc == 0

在这个 playbook 中:

  1. 我们尝试列出一个不存在的目录
  2. 我们使用 ignore_errors: yes 来防止 playbook 在命令失败时停止
  3. 我们检查返回码 (cmd_result.rc) 以确定命令是成功还是失败
  4. 我们根据结果显示适当的消息

让我们运行这个 playbook:

cd ~/project/ansible-lab
ansible-playbook -i inventory.ini error_handling.yml

你应该看到类似于以下的输出:

PLAY [Handling Script Errors] ***********************************************************

TASK [Run a command that might fail] ***************************************************
fatal: [localhost]: FAILED! => {"changed": true, "cmd": ["ls", "/nonexistent_directory"], "delta": "0:00:00.003398", "end": "2023-09-15 12:34:56.789012", "msg": "non-zero return code", "rc": 2, "start": "2023-09-15 12:34:56.785614", "stderr": "ls: cannot access '/nonexistent_directory': No such file or directory", "stderr_lines": ["ls: cannot access '/nonexistent_directory': No such file or directory"], "stdout": "", "stdout_lines": []}
...ignoring

TASK [Show error message if command failed] *********************************************
ok: [localhost] => {
    "msg": "Command failed with error: ls: cannot access '/nonexistent_directory': No such file or directory"
}

TASK [Show success message if command succeeded] ****************************************
skipping: [localhost]

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

请注意,尽管命令失败了,playbook 仍然继续运行并显示错误消息。

格式化多行输出

让我们创建一个更复杂的脚本,生成多行输出,然后在我们的 playbook 中对其进行良好格式化。

在你的 ~/project/ansible-lab 目录中创建一个名为 package_info.sh 的文件:

#!/bin/bash

echo "TOP 5 LARGEST INSTALLED PACKAGES"
echo "================================"
dpkg-query -W -f='${Installed-Size}\t${Package}\n' | sort -n -r | head -5

使其可执行:

chmod +x ~/project/ansible-lab/package_info.sh

现在创建一个 playbook 来运行这个脚本并对输出进行良好格式化。创建一个名为 formatted_output.yml 的文件:

---
- name: Format Script Output
  hosts: localhost
  gather_facts: no

  tasks:
    - name: Run package info script
      shell: ./package_info.sh
      register: pkg_info

    - name: Display raw output
      debug:
        msg: "{{ pkg_info.stdout }}"

    - name: Display formatted output with a title
      debug:
        msg: |
          Below is the package information:
          --------------------------------
          {% for line in pkg_info.stdout_lines %}
          {{ line }}
          {% endfor %}
          --------------------------------
          Total lines: {{ pkg_info.stdout_lines | length }}

这个 playbook:

  1. 运行我们的 package_info.sh 脚本
  2. 显示原始输出
  3. 使用 Jinja2 模板来格式化输出,带有标题和页脚

让我们运行它:

ansible-playbook -i inventory.ini formatted_output.yml

你应该看到格式良好的输出:

PLAY [Format Script Output] ***********************************************************

TASK [Run package info script] ********************************************************
changed: [localhost]

TASK [Display raw output] *************************************************************
ok: [localhost] => {
    "msg": "TOP 5 LARGEST INSTALLED PACKAGES\n================================\n112233\tsome-large-package\n99887\tanother-package\n...\n"
}

TASK [Display formatted output with a title] ******************************************
ok: [localhost] => {
    "msg": "Below is the package information:\n--------------------------------\nTOP 5 LARGEST INSTALLED PACKAGES\n================================\n112233\tsome-large-package\n99887\tanother-package\n...\n--------------------------------\nTotal lines: 7"
}

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

有条件地显示输出

有时你只想在特定条件下显示输出,例如在详细模式下运行。让我们看看如何做到这一点。

在你的 ~/project/ansible-lab 目录中创建一个名为 conditional_output.yml 的文件:

---
- name: Conditional Output Display
  hosts: localhost
  gather_facts: yes

  tasks:
    - name: Get disk usage information
      shell: df -h
      register: disk_info

    - name: Display disk usage (always shown)
      debug:
        msg: "{{ disk_info.stdout_lines[0:2] }}"

    - name: Display detailed disk usage (only in verbose mode)
      debug:
        msg: "{{ disk_info.stdout_lines }}"
        verbosity: 1

在这个 playbook 中:

  1. 我们捕获磁盘使用信息
  2. 我们总是显示一个摘要(前两行)
  3. 我们仅在详细模式下运行(使用 -v 标志)时显示完整的详细信息

让我们首先在普通模式下运行它:

ansible-playbook -i inventory.ini conditional_output.yml

现在,让我们在详细模式下运行它:

ansible-playbook -i inventory.ini conditional_output.yml -v

你将注意到,在详细模式下,你将获得完整的磁盘使用信息,而在普通模式下,你只看到摘要。

这种技术对于默认情况下隐藏潜在的敏感或压倒性的输出非常有用,同时在需要调试时仍然可以使用。

总结

在这个实验中,你学习了在 Ansible playbooks 中捕获和显示脚本输出的各种技术:

  1. 你设置了一个基本的 Ansible 环境并创建了你的第一个 playbook
  2. 你学习了如何使用 commandshell 模块运行脚本
  3. 你使用 register 指令捕获了脚本输出
  4. 你使用 debug 模块以不同的格式显示输出
  5. 你处理了错误情况并实现了条件输出显示
  6. 你应用了格式化技术,使输出更具可读性

这些技能使你能够创建更具信息量的 Ansible playbooks,这些 playbooks 在执行期间提供清晰的反馈。这种可见性对于监控自动化流程和在出现问题时进行故障排除至关重要。

通过利用这些输出处理技术,你可以构建更强大的自动化工作流程,这些工作流程不仅执行任务,而且清楚地传达了每个步骤中发生的事情。这使得你的自动化更透明,更容易维护,尤其是在团队环境或复杂基础设施中工作时。