Ansible 核心为几乎所有用例提供了数百个 Ansible 模块。您可以在Ansible 文档页面上找到所有 Ansible 模块的完整列表及其描述。有时,您只需要像在 bash shell 上一样直接在目标主机上执行命令。这就是 Ansible shell 模块派上用场的地方。在本指南中,您将了解 Ansible shell 模块、它的工作原理以及如何使用它对托管节点执行命令。
什么是 Ansible Shell 模块?
Ansible shell 模块是一个方便的模块,它允许您直接在远程目标的 shell 上执行命令,就像您已登录一样。这样做有助于保持命令执行的原创性。
shell 模块与命令模块密切相关。两者都有助于实现相同的结果。但是,两者之间存在一些差异:
- shell 模块直接在目标主机的 shell 上执行命令。默认情况下,shell 模块使用/bin/shshell 来运行命令,尽管您可以将其配置为使用其他 shell。使用 command 模块,执行的命令不通过 shell 处理。
- 由于命令不在 shell 上执行,命令模块不支持环境变量、管道和其他运算符,例如 '>' 、 '<' 、 '&'、';' 和'| |'。使用 shell 模块,完全支持管道、重定向和变量。因此,shell 模块提供了更大的灵活性。
- 如果在目标系统上安全地运行命令是您的首要任务,请使用命令模块。与 shell 模块不同,command 模块不影响远程用户的 shell 环境。
Ansible Shell 模块与其他模块
commandAnsible shell 模块与、script和模块属于同一类别raw。这些统称为运行命令。
虽然可以快速高效地完成任务,但运行命令只能作为最后的手段使用。这是因为它们在执行任务时应用最少的逻辑,并且不存在所需状态的概念。如果已经满足导致错误的条件,则 shell 命令的后续执行可能会失败。
此外,除非您注册第一个命令的结果,否则无法使用 shell 模块捕获错误。然后,您必须在剧本中执行后续任务以实施条件逻辑来确认错误是否发生,然后进行处理。这可能会导致严重破坏自动化的瓶颈。
出于这个原因,shell 命令应该仅限于在远程系统上执行简单的任务。如果需要服务或应用程序的理想状态,建议使用特定于任务的模块,例如service、copy、file和lineinfile,仅举几例。这使得剧本更加通用和可重用。
先决条件
要遵循本指南,您应该:
- 一个 Ansible 控制节点。在本教程中,我们运行的是 Ubuntu 20.04。如果您还没有安装 Ansible,请按照我们关于如何在 Ubuntu 20.04 上安装和配置 Ansible的指南进行操作。
- 您将对其运行命令的远程主机。
运行 Ansible Ad-hoc Shell 命令
Ansible 的真正力量在于剧本。但是,剧本用于在目标主机上执行复杂的任务。假设您想要非常快速地运行命令而不保存它们供以后使用。你会怎么做?这就是临时命令派上用场的地方。
Ad-hoc 命令是单行命令,您无需编写剧本即可即时运行。
例如,您可能想要检查远程主机的正常运行时间或磁盘空间利用率。与其为此类任务编写完整的剧本,更好的方法是对您的主机运行临时命令。
临时命令采用以下语法:
ansible [pattern] -m [MODULE] -a {COMMAND_OPTIONS}
该模式指定目标主机所属的主机组。该-m选项指定模块的类型,而该-a选项采用命令参数。
让我们举几个例子。
要检查所有目标主机的正常运行时间,请运行以下命令:
sudo ansible all -m shell -a 'uptime -p'
要检查内存使用情况,请运行:
sudo ansible all -m shell -a 'free -m'
查看db_server组下主机的磁盘空间利用率,执行:
sudo ansible db_server -m shell -a 'df -Th'
使用 Ansible Shell 模块运行单个命令
除了运行临时命令外,Ansible shell 模块还用于 playbook 以指定要在远程主机上执行的任务。
考虑下面的剧本。
--- - name: Shell module example hsots: webservers tasks: - name: Check system information shell: "lsb_release -a" register: os_info - debug: msg: "{{os_info.stdout_lines}}"
在此示例中,剧本针对名为 webservers 的主机组运行,并执行一个lsb_release -a命令来检索有关操作系统版本的详细信息。然后将输出保存在一个名为 的寄存器变量中os_info。
最后一行使用模块将存储在os_info变量中的输出打印到标准输出。debug
这是剧本执行的输出。
如果文件不存在,则使用 Shell 模块运行命令
该creates参数允许您在文件不存在时运行命令。它指定文件的路径,如果存在,则跳过要执行的命令。
显示的剧本检查文件是否hello.txt存在于目标主机的主目录中。如果该文件不存在,则执行指定的 shell 命令。如果该文件存在,则 shell 命令将中止。
--- - name: Create a file in the home directory if it doesn't exist hosts: webservers tasks: - name: Create a file in the home directory shell: echo "Hey guys!" > $HOME/hello.txt args: creates: "$HOME/hello.txt"
由于该文件在远程目标上不存在,因此 shell 命令已成功执行,如您从 playbook 执行中所见。
下面的命令确认hello.txt文件是在远程目标的主目录中创建的。
ssh root@173.82.120.115 "ls -l ~"
如果文件存在,则使用 Shell 模块运行命令
参数指定文件名,removes如果文件存在,则执行命令。在前面的示例中,该hello.txt文件是在远程目标的主目录中创建的。
在此剧本中,removes参数检查此文件是否存在于远程目标上。由于您已经创建了它,剧本会继续执行删除文件的 shell 命令。
--- - name: Remove a file in the home directory only if it exists hosts: webservers tasks: - name: Remove a file in the home directory shell: rm $HOME/hello.txt args: removes: "$HOME/hello.txt" warn: false
剧本执行确认文件已被删除。
在不同的目录中运行命令
要在特定目录中运行 shell 命令,请使用chdir参数。下面的剧本将 Apache 二进制文件下载到/usr/local/src路径中。
--- - name: Download Apache source file to /usr/local/src directory hosts: webservers tasks: - name: Download Apache tarball file shell: wget https://dlcdn.apache.org/httpd/httpd-2.4.52.tar.gz args: chdir: /usr/local/src warn: false
剧本确认任务已成功执行。
将 Ansible shell 模块与环境变量一起使用
shell 模块还使您能够设置新的环境变量。使用environment参数可以做到这一点。考虑下面的剧本。
--- - name: Environment variable example hosts: webservers tasks: - name: Ansible Shell module set an environment variable shell: echo $NEW_VAR register: command_result envnironment: NEW_VAR: "john_doe" - debug: msg: "{{ command_result.stdout_lines }}"
该剧本将NEW_VAR环境变量设置为john_doe。
注意:环境变量仅为该特定任务设置。在后续任务中,NEW_VAR 变量将不可用。
使用 Ansible Shell 模块运行多个命令
到目前为止,您已经看到 shell 模块在托管主机上运行单个命令。您还可以指定一组按时间顺序执行的命令。
要实现这一点,请使用竖线开始 shell 命令,然后是要执行的任务列表。在本例中,date、uptime、 和echo命令的输出分别保存到date.txt、uptime.txt和hello.txt文件中,然后保存在/tmp目录中。
--- - name: Shell module example hsots: webservers tasks: - name: Run multiple commands shell: | date > /tmp/date.txt uptime > /tmp/uptime.txt echo "Hello World" > /tmp/hello.txt
剧本从第一个任务到最后一个任务按顺序运行任务。
使用管道和重定向运行命令
如前所述,shell 模块也接受管道和重定向。事实上,之前的剧本利用重定向符号 ( >) 将列出的命令的输出保存到单独的文件中。
假设您要列出在/tmp目录中创建的所有文本文件,并将结果保存到同一目录中另一个名为dirlist.txt的文件中。
这是 shell 命令的样子。
ls -l /tmp | grep .txt > /tmp/dirlist.txt
命令的第一部分列出了/tmp目录中的所有文件。然后将结果通过管道传输到 grep 命令,该命令过滤输出以仅包含文本文件。然后使用“大于”重定向符号将最终输出保存到dirlist.txt文件。
现在让我们更进一步,将结果打印到标准输出。为此,需要第二项任务。目标是将dirlist.txt文件的内容显示到标准输出。列出文件内容的 shell 命令是:
cat /tmp/dirlist.txt
命令的输出随后被注册在一个名为displayfile的变量中,并且使用调试模块将消息显示到标准输出。这是整个剧本的样子。
--- - name: Shell module example hosts: webservers tasks: - name: List text files in tmp directory and save result in output file shell: "ls -l /tmp | grep .txt > /tmp/dirlist.txt - name: Display the contents of the output file shell: cat /tmp/dirlist.txt register: displayfile - debug: msg: "{{ displayfile.stdout_lines }}"
执行 playbook 时,/tmp目录中的所有文本文件(包括dirlist.txt文件)都会打印到标准输出:
防止 Shell 注入
由于它在远程目标的 shell 上运行命令,因此 shell 模块容易受到 shell 命令注入。Shell 注入是一种攻击媒介,攻击者在其中在主机上运行任意命令以破坏底层基础设施。
使用 Shell 模块变量时,Ansible 建议使用quote过滤器来阻止 shell 注入威胁。
{{ variable_name | quote }}
考虑下面的一个简单剧本。
--- - name: Ansible quote filter demo hosts: webservers vars: - username: cherry tasks: - name: Print variable debug: msg: " Running playbook as user {{ username | quote }}
该username变量在最后由msg使用过滤器的参数引用,quote以防止在注入用户名变量时执行任意命令字符串。
结论
Ansible shell 模块是一个有用的模块,可以帮助您在托管主机上快速执行简单任务。shell 模块的完美替代品是commandmodule。它提供了一种更可靠、更安全的任务执行方式,因为命令不会在远程目标的 shell 上处理。但是,如果您坚持使用 shell 模块,请不要忘记quote在 playbook 中使用模板化变量时包含过滤器以防止 shell 注入攻击。