4. Ansible核心机制与RHEL适配

Ansible 在 RHEL 上之所以能成为自动化运维平台的中枢,不是因为它“能连 SSH”,而是因为它把主机清单、任务编排、可复用角色、模块执行、插件扩展和幂等控制组织成了一条可预测的执行链。理解这条链,才能判断一次 playbook 运行究竟是“配置收敛”还是“重复扰动”。

Inventory:把主机、组和变量分层

Inventory 是 Ansible 的目标视图。静态 INI 适合小规模固定环境,YAML 适合嵌套变量,动态 inventory 则用于对接云主机或 CMDB。RHEL 环境中常见做法是按“环境—角色—机房”三层分组,例如 prod_web、prod_db、prod_all,并在 group_vars/ 与 host_vars/ 中分层覆盖变量。变量优先级必须提前约定:host_vars 覆盖 group_vars,子组覆盖父组,命令行 -e 覆盖一切。否则同一台 RHEL 主机在不同 play 中会拿到不同 ansible_user 或 ansible_python_interpreter,导致连接行为不一致。

RHEL 8/9 默认使用 Python 3,通常无需再指定解释器;但若目标机是 RHEL 7 或最小化安装,建议显式设置 ansible_python_interpreter=/usr/bin/python3,避免因缺少 Python 2 而出现“module not found”。

Playbook:任务、处理器与执行顺序

Playbook 是 YAML 描述的任务序列,核心结构是 hosts、become、tasks、handlers、vars。RHEL 上最常用的模式是:先安装软件包,再分发配置模板,最后用 handler 重启服务。这里的关键不是“写几个 task”,而是理解执行顺序与触发条件:task 按书写顺序执行,handler 只在被 notify 且 task 状态为 changed 时,在 play 末尾统一运行一次。

- hosts: prod_web
  become: true
  tasks:
    - name: 安装 httpd
      ansible.builtin.dnf:
        name: httpd
        state: present
    - name: 分发 vhost 配置
      ansible.builtin.template:
        src: vhost.conf.j2
        dest: /etc/httpd/conf.d/vhost.conf
        owner: root
        group: root
        mode: '0644'
      notify: 重启 httpd
  handlers:
    - name: 重启 httpd
      ansible.builtin.service:
        name: httpd
        state: restarted

这段 playbook 在 RHEL 上执行时,若 httpd 已安装且配置未变,两个 task 都返回 ok,handler 不触发;若模板内容变化,template 返回 changed,handler 才重启服务。这就是幂等性的直接体现:重复执行不改变系统状态,只有期望状态与实际状态不一致时才动作。

Role:把可复用逻辑拆成标准目录

当 playbook 超过几十行,就应拆成 Role。Role 的标准目录包括 tasks/、handlers/、templates/、files/、vars/、defaults/、meta/。RHEL 平台工程中,建议把“基础加固”“用户与 sudo”“时间同步”“监控 agent”分别做成独立 Role,再在 site.yml 中按顺序引用。这样做的价值是:变量边界清晰,defaults 提供可覆盖默认值,vars 存放高优先级固定值,meta 声明依赖关系。

例如一个 chrony Role,defaults 中写 chrony_servers: ['ntp1.example.com'],tasks 中安装 chrony、模板化 /etc/chrony.conf、启动服务。不同环境只需在 group_vars 中覆盖 chrony_servers,无需改 Role 本身。

模块与插件:执行单元与扩展点

模块是 Ansible 在目标机上执行的原子单元,RHEL 上高频模块包括 dnf、service、firewalld、selinux、user、copy、template、lineinfile。插件则运行在控制端,扩展连接、回调、查找和过滤能力。RHEL 适配中,firewalld 模块和 selinux 模块尤其重要:前者管理 zone 与 service,后者管理布尔值和上下文。若用 command 或 shell 绕过这些模块,就会失去幂等性,也会让审计变得困难。

在 RHEL 上优先使用 ansible.builtin.dnf 而非 yum 模块;优先使用 ansible.posix.firewalld 而非直接调用 firewall-cmd。

幂等性:判断“是否 changed”的工程标准

幂等性不是“跑两次不报错”,而是“第二次运行不产生额外变更”。判断依据是每个 task 的返回状态:ok 表示已符合期望,changed 表示发生了实际修改,failed 表示未达成。RHEL 上常见的非幂等写法包括:用 shell 追加内容、用 command 启动服务、用 copy 覆盖已存在文件却不校验 checksum。正确做法是使用 lineinfile、blockinfile、template、service 等声明式模块,让 Ansible 自己比较状态。

Ansible 控制端 → RHEL 被管节点:执行链与幂等判断 Inventory 主机 / 组 / 变量分层 prod_web、prod_db Playbook tasks / handlers 顺序执行 + notify Role tasks / defaults / vars templates / meta 模块 / 插件 dnf、service firewalld、selinux SSH 连接 RHEL 被管节点 Python 3 解释器 / become 提权 期望状态 httpd 已安装、配置一致 实际状态比对 模块内部检查当前状态 结果判定 ok / changed / failed 幂等性判据:第二次运行不产生额外变更;changed 才触发 handler,ok 不触发 RHEL 适配要点:优先声明式模块,避免 shell/command 破坏幂等与审计

RHEL 上的最佳实践

第一,控制端与被管端版本对齐。RHEL 9 自带 ansible-core,若使用 ansible-navigator 或 AAP,应确认执行环境中的 Python 与目标机兼容。第二,用 dnf 模块管理软件包,并配合 state: present 而非 latest,避免非计划升级。第三,SELinux 与 firewalld 必须纳入 playbook,例如用 ansible.posix.selinux 设置 httpd_can_network_connect,用 firewalld 放行 80/tcp,而不是手工执行 setenforce 0。第四,敏感变量用 ansible-vault 加密,并在 RHEL 控制端限制 vault 密码文件权限为 0600。第五,用 ansible-lint 和 --check --diff 做变更前验证,在 RHEL 上先跑 check 模式观察哪些 task 会 changed,再决定是否正式执行。

不要用 shell: systemctl restart httpd 代替 service 模块;前者每次都会 changed,后者只在服务未运行或配置变化时动作。

把 Inventory 管好、Playbook 写清、Role 拆细、模块用对、插件按需扩展、幂等性作为验收标准,Ansible 在 RHEL 上才真正成为可重复、可审计、可回滚的自动化平台,而不是一批一次性脚本的集合。