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 绕过这些模块,就会失去幂等性,也会让审计变得困难。
ansible.builtin.dnf 而非 yum 模块;优先使用 ansible.posix.firewalld 而非直接调用 firewall-cmd。幂等性:判断“是否 changed”的工程标准
幂等性不是“跑两次不报错”,而是“第二次运行不产生额外变更”。判断依据是每个 task 的返回状态:ok 表示已符合期望,changed 表示发生了实际修改,failed 表示未达成。RHEL 上常见的非幂等写法包括:用 shell 追加内容、用 command 启动服务、用 copy 覆盖已存在文件却不校验 checksum。正确做法是使用 lineinfile、blockinfile、template、service 等声明式模块,让 Ansible 自己比较状态。
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 上才真正成为可重复、可审计、可回滚的自动化平台,而不是一批一次性脚本的集合。