Linux Ansible 高级用法与模块

1次阅读

优先选 ansible.builtin.command(除非需管道/重定向/变量展开);command 更安全轻量,shell 易出错且有子shell陷阱。

Linux Ansible 高级用法与模块

ansible.builtin.shell 和 ansible.builtin.command 选哪个?

shellcommand 看似都能执行命令,但底层行为完全不同。前者调用 /bin/sh(或指定 shell),支持管道、重定向、变量展开;后者绕过 shell,直接 execve,不解析 $PATH、不支持 |&&

常见错误现象:用 commandls /tmp | grep log 报错 unrecognized arguments: |;或者用 shell 执行 cd /opt && ./deploy.sh,结果发现 cd 不生效——因为每条 shell 任务都是独立子 shell。

使用场景:

  • command:调用绝对路径二进制(如 /usr/bin/touch)、避免 shell 注入、需要确定性执行环境
  • shell:必须用管道/重定向/环境变量、依赖当前用户 shell 配置(比如 ~/.bashrc 里的 alias)

性能影响:command 更轻量,无 shell 解析开销;shell 多一层进程 fork,且可能引入意外的 shell 行为(比如 glob 展开)。

- command: /bin/echo "hello" - shell: echo $HOME | tr 'a-z' 'A-Z'

如何让 loop 变量在 when 条件里安全使用?

Ansible 的 loop(或旧式 with_items)和 when 组合时,容易踩「变量未定义」或「类型不匹配」的坑。比如遍历一个字典列表,但某一项缺 key,when: item.port == 8080 就会报 Error while evaluating Conditional

根本原因是 Ansible 默认开启 jinja2_strict_undefined,访问不存在的 key 直接失败。

解决办法只有两个:

  • item.port | default(80) 显式兜底
  • 改用 item.get('port', 80)(推荐,更接近 Python 习惯)

别写 when: item.port is defined and item.port == 8080——它不能防止 item 本身是 None 或字符串

另外注意:loop 中的 item 类型完全取决于你传进去的数据,不是所有情况都是 dict。如果源是 [1, 2, "abc"]item.port 永远是错的。

- name: restart only nginx services with port 80   service:     name: "{{ item.name }}"     state: restarted   loop: "{{ services }}"   when: item.port | default(80) == 80

ansible.builtin.include_tasks vs. ansible.builtin.import_tasks 怎么选?

关键区别就一条:import_tasks 是静态包含,在 playbook 解析阶段就加载并展开全部任务;include_tasks 是动态包含,运行时才读取文件、才决定是否执行(支持 whenloop 控制)。

常见错误现象:

  • include_tasks 的文件里用了 loop,但外层 task 写了 when: false,结果文件还是被读取了(只是任务不执行)——这没问题;
  • 但反过来,用 import_tasks + when,条件不满足时整个 import 会被跳过,连语法检查都做不了,容易掩盖 YAML 错误。

使用场景:

  • import_tasks:通用初始化任务(如共用的权限设置)、需要被 tags--start-at-task 定位的稳定流程
  • include_tasks:根据变量动态加载逻辑(如不同 OS 加载不同包安装任务)、配合 loop 分批执行、调试时想临时跳过某段

兼容性注意:Ansible 2.12+ 已弃用 include(不带 _tasks 后缀),只保留这两个明确语义的模块。

为什么 register 的变量在后续 play 里用不了?

Ansible 的变量作用域默认是「play 级」,register 存的变量只在当前 play 内有效。跨 play 访问会得到 UndefinedVariable

这不是 bug,是设计使然:避免隐式依赖、提升可读性、防止状态污染。

如果你真需要跨 play 共享,只有两个合规方式:

  • set_fact + cacheable: true(需启用 fact cache,如 fact_caching: jsonfile
  • 把值写到文件(copylineinfile),下个 play 再读(适合简单字符串)

别用 hostvars[inventory_hostname] 强取——它只对当前 play 的 hostvars 有效,跨 play 依然空。

还有一个易忽略点:register 的变量名如果含破折号(如 my-result),Jinja2 里必须写成 hostvars[inventory_hostname]['my-result'],不能点号访问。

- name: get uptime   command: uptime   register: uptime_result <ul><li><p>name: use it in same play — OK debug: var=uptime_result.stdout</p></li><li><p>name: use it in next play — FAIL unless set_fact or cache debug: var=uptime_result</p>

复杂点在于:Ansible 不提供“全局变量”机制,所有跨 play 数据传递都得显式落地。很多人卡在这儿,不是不会写,是没意识到这是故意的设计约束。

text=ZqhQzanResources