Ansible 实战手册
1. 技术背景与版本说明
RHEL 10 下 ansible-navigator 的 RPM 已不再通过 dnf 直接安装,官方改为:
DevTools 容器(
ghcr.io/ansible/community-ansible-dev-tools)VSCode + Ansible 插件 作为开发环境
这意味着 RHEL 10 环境中:
# RHEL 9 可直接 dnf 安装
sudo dnf install ansible-navigator # RHEL 9 ✅
# RHEL 10 需通过容器或 pip
pip3 install ansible-navigator --user # RHEL 10 临时方案 ✅
# 或使用官方 DevTools 容器(推荐生产/考试环境)2. 推荐目录结构
/home/greg/ansible/
├── ansible.cfg # Ansible 主配置文件
├── ansible-navigator.yml # Navigator 配置(新增!)
├── inventory # 主机清单
├── requirements.yml # Collections 依赖(根目录)
├── roles/
│ ├── requirements.yml # Galaxy 角色依赖(与 collection 分开)
│ ├── apache/ # 自定义角色示例
│ │ ├── tasks/
│ │ │ └── main.yml
│ │ ├── templates/
│ │ │ └── index.html.j2
│ │ ├── handlers/
│ │ │ └── main.yml
│ │ └── defaults/
│ │ └── main.yml
│ ├── balancer/ # Galaxy 安装的角色
│ └── phpinfo/
├── mycollection/ # 本地 collection 存放路径
├── group_vars/
│ ├── all.yml # 所有主机共用变量
│ └── webservers.yml # webservers 组专用变量
├── host_vars/
│ └── servera.yml # 单台主机专用变量
├── locker.yml # Vault 加密的密码文件
├── user_list.yml # 用户列表变量文件
└── secret.txt # Vault 密码文件(勿提交 Git)设计原则说明:
Collections 依赖(
requirements.yml)和角色依赖(roles/requirements.yml)分开存放,职责清晰group_vars/和host_vars/是 Ansible 自动加载的目录,无需在 playbook 中显式引用secret.txt不应提交到版本控制系统
3.基础配置文件
3.1 ansible.cfg
[defaults]
inventory = /home/greg/ansible/inventory
host_key_checking = False
remote_user = greg
roles_path = /home/greg/ansible/roles:/usr/share/ansible/roles
collections_path = /home/greg/ansible/mycollection:/usr/share/ansible/collections
forks = 5
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False
[vault]
vault_password_file = /home/greg/ansible/secret.txt关键参数解释:
参数 | 作用 | 注意事项 |
host_key_checking = False | 跳过 SSH 指纹验证 | 仅用于实验/考试环境,生产环境应为 True |
roles_path | 角色搜索路径,冒号分隔多个 | 保留系统默认路径确保系统角色可用 |
collections_path | Collection 搜索路径 | 同上 |
vault_password_file | 自动读取 Vault 密码 | 避免每次输入密码,复习省时 |
forks | 并发连接数 | 默认 5,大型环境可调大 |
3.2 ansible-navigator.yml(RHEL 10)
---
ansible-navigator:
ansible:
inventories:
- /home/greg/ansible/inventory
execution-environment:
enabled: true
image: registry.redhat.io/ansible-automation-platform-25/ee-supported-rhel9:latest
pull:
policy: missing # 仅在本地无镜像时拉取,节省时间
mode: stdout # 默认使用 stdout 模式,输出更直观
logging:
level: warning
playbook-artifact:
enable: false # 关闭录制,加快速度Navigator 常用命令速查:
# 运行 playbook(等效 ansible-playbook)
ansible-navigator run site.yml -m stdout
# 查看可用 collection
ansible-navigator collections
# 查看模块文档
ansible-navigator doc ansible.builtin.dnf -m stdout
# 查看主机清单
ansible-navigator inventory --list -m stdout
# 查看当前配置
ansible-navigator config dump -m stdout3.3 常用验证命令
# 查看当前生效的非默认配置项(最快速确认配置)
ansible-config dump --only-changed
# 以树形图展示主机清单结构
ansible-inventory -i inventory --graph
# 列出已安装的 collections
ansible-galaxy collection list
# 列出已安装的角色
ansible-galaxy role list
# 测试所有主机连通性(Ad-Hoc)
ansible all -m ping
# 语法检查 playbook
ansible-playbook site.yml --syntax-check
# 模拟执行(不真正改变系统状态)
ansible-playbook site.yml --check4. Inventory 主机清单
4.1 标准 inventory 文件
[dev]
workstation
[test]
servera
[prod]
serverb
serverc
[balancers]
serverd
[webservers:children]
prod组关系说明:
[webservers:children]是嵌套组,表示prod组的所有主机同时属于webservers组all是内置组,自动包含 inventory 中的全部主机ungrouped是内置组,包含未被分组的主机
4.2 带变量的 inventory
[prod]
serverb ansible_host=192.168.1.11
serverc ansible_host=192.168.1.12
[prod:vars]
http_port=80最佳实践:主机变量和组变量优先放到 host_vars/ 和 group_vars/ 目录,而不是直接写在 inventory 里,便于维护。
5.初始化与安装
5.1 RHEL 9 环境安装
# 安装核心工具
sudo dnf -y install ansible-core ansible-navigator
# 创建项目目录结构
mkdir -p /home/greg/ansible/{roles,mycollection,group_vars,host_vars}
cd /home/greg/ansible
# 生成配置文件模板(之后手工整理)
ansible-config init --disabled > ansible.cfg
# 确认版本
ansible --version
ansible-navigator --version5.2 RHEL 10 环境安装
# ansible-core 可直接安装
sudo dnf -y install ansible-core
# ansible-navigator 通过 pip 安装(RHEL 10 无官方 RPM)
pip3 install ansible-navigator --user
# 或使用 DevTools 容器方案(推荐)
podman pull ghcr.io/ansible/community-ansible-dev-tools:latest
# 创建目录结构(同 RHEL 9)
mkdir -p /home/greg/ansible/{roles,mycollection,group_vars,host_vars}6. Collection 与 Galaxy 角色管理
6.1 Collections 依赖文件
/home/greg/ansible/requirements.yml
---
collections:
# 离线课堂材料(考试/内网环境)
- name: http://classroom/materials/redhat-insights-1.0.7.tar.gz
- name: http://classroom/materials/community-general-5.5.0.tar.gz
- name: http://classroom/materials/redhat-rhel_system_roles-1.19.3.tar.gz
# 公网环境写法(锁定版本)
# - name: community.general
# version: ">=5.5.0"安装命令:
# 安装 collections 到指定路径
ansible-galaxy collection install \
-r /home/greg/ansible/requirements.yml \
-p /home/greg/ansible/mycollection
# 验证安装结果
ansible-galaxy collection list6.2 角色依赖文件
/home/greg/ansible/roles/requirements.yml
---
- src: http://materials/haproxy.tar
name: balancer
- src: http://materials/phpinfo.tar
name: phpinfo安装命令:
# 安装角色到指定路径
ansible-galaxy install \
-r /home/greg/ansible/roles/requirements.yml \
-p /home/greg/ansible/roles
# 验证安装结果
ansible-galaxy role list6.3 重要区别
对比项 | Collections | Roles(Galaxy 角色) |
安装命令 | ansible-galaxy collection install | ansible-galaxy install |
依赖文件 | 根目录 requirements.yml | roles/requirements.yml |
安装路径参数 | -p mycollection/ | -p roles/ |
使用方式 | FQCN:namespace.collection.module | 直接用角色名 |
7. 软件仓库配置
---
- name: Configure YUM repositories on all nodes
hosts: all
become: true
tasks:
- name: Configure BaseOS repository
ansible.builtin.yum_repository:
name: EX_BASEOS
description: RHEL 10 BaseOS Repository
file: ex10
baseurl: http://content/rhel10.0/x86_64/dvd/BaseOS
gpgcheck: true
enabled: true
gpgkey: http://content/rhel10.0/x86_64/dvd/RPM-GPG-KEY-redhat-release
- name: Configure AppStream repository
ansible.builtin.yum_repository:
name: EX_APPSTREAM
description: RHEL 10 AppStream Repository
file: ex10
baseurl: http://content/rhel10.0/x86_64/dvd/AppStream
gpgcheck: true
enabled: true
gpgkey: http://content/rhel10.0/x86_64/dvd/RPM-GPG-KEY-redhat-release注意事项:
file参数指定.repo文件名(不含.repo后缀),多个仓库可写同一file值,合并到一个文件x86_64的拼写不能错(中间是数字 6,不是字母)仓库 URL 以实验/考试环境提供的内容为准,不要照搬
8. 软件包管理
8.1 安装指定软件包
---
- name: Install php and mariadb on dev/test/prod
hosts: dev,test,prod
become: true
tasks:
- name: Install packages
ansible.builtin.dnf:
name:
- php
- mariadb
state: present # 推荐:存在即可,不强制升级
# state: latest # 谨慎使用:会升级到最新版,改变系统状态8.2 安装软件包组
---
- name: Install RPM Development Tools on dev
hosts: dev
become: true
tasks:
- name: Install package group
ansible.builtin.dnf:
name: "@RPM Development Tools" # 包组必须加 @ 前缀,用引号包裹
state: present8.3 升级所有软件包
---
- name: Upgrade all packages on dev
hosts: dev
become: true
tasks:
- name: Upgrade all packages to latest
ansible.builtin.dnf:
name: "*" # 通配符匹配所有包
state: latest三种 state 的区别:
state | 含义 | 使用场景 |
present | 安装即可,不升级 | 日常安装,推荐默认 |
latest | 安装并升级到最新 | 明确要求"最新"时使用 |
absent | 卸载 | 删除软件包 |
9. RHEL 系统角色
---
- name: Configure SELinux using system role
hosts: all
become: true
vars:
selinux_policy: targeted
selinux_state: enforcing
tasks:
- name: Apply SELinux role
ansible.builtin.include_role:
name: redhat.rhel_system_roles.selinuxSELinux 相关 Ad-Hoc 命令:
# 查看 SELinux 状态
ansible all -m command -a "getenforce"
# 查看文件 SELinux 上下文
ansible all -m command -a "ls -Z /var/www/html"9.3 时间同步(timesync)
---
- name: Configure NTP time synchronization
hosts: all
become: true
tasks:
- name: Apply timesync role
ansible.builtin.include_role:
name: redhat.rhel_system_roles.timesync
vars:
timesync_ntp_servers:
- hostname: 172.25.254.254
iburst: true
prefer: true # 可选:标记为首选 NTP 服务器9.4 网络配置(network)
---
- name: Configure network interface using system role
hosts: all
become: true
vars:
network_connections:
- name: eth0
type: ethernet
state: up
ip:
dhcp4: true
tasks:
- name: Apply network role
ansible.builtin.include_role:
name: redhat.rhel_system_roles.network10. 文件与模板操作
10.1 模块选择原则
场景 | 推荐模块 | 原因 |
写入固定内容的完整文件 | copy | 简单直接,内容可控 |
根据变量动态生成文件 | template | Jinja2 渲染,支持循环和判断 |
修改文件中的某一行 | lineinfile | 精准替换单行配置 |
向文件插入一整段内容 | blockinfile | 有标记包裹,可重复执行 |
从控制节点下载文件 | get_url | 支持 HTTP/FTP |
从被管节点拉回文件 | fetch | 下载到控制节点 |
10.2 生成主机配置文件(template)
playbook:
---
- name: Create /etc/myhosts on dev hosts
hosts: all
become: true
gather_facts: true # 必须开启,模板需要 facts 数据
tasks:
- name: Deploy myhosts file
ansible.builtin.template:
src: /home/greg/ansible/hosts.j2
dest: /etc/myhosts
owner: root
group: root
mode: '0644'
when: "'dev' in group_names"hosts.j2 模板:
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
{% for host in groups['all'] | sort %}
{{ hostvars[host]['ansible_facts']['default_ipv4']['address'] | default('NONE') }} {{ hostvars[host]['ansible_facts']['fqdn'] | default(host) }} {{ hostvars[host]['ansible_facts']['hostname'] | default(host) }}
{% endfor %}为什么加 default('NONE'):若某台主机 facts 采集失败或网卡不存在,不加 default 会直接报错导致整个 play 失败;加上后会输出 NONE 占位,playbook 继续执行。
10.3 修改 /etc/issue
使用 copy 模块:
---
- name: Set /etc/issue content based on host group
hosts: all
become: true
tasks:
- name: Set issue to Development
ansible.builtin.copy:
content: "Development\n"
dest: /etc/issue
when: "'dev' in group_names"
- name: Set issue to Test
ansible.builtin.copy:
content: "Test\n"
dest: /etc/issue
when: "'test' in group_names"
- name: Set issue to Production
ansible.builtin.copy:
content: "Production\n"
dest: /etc/issue
when: "'prod' in group_names"若确实需要 lineinfile(修改配置文件中特定行):
# 示例:修改 sshd_config 中 PasswordAuthentication 的值
- name: Disable password authentication
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PasswordAuthentication' # 精确匹配关键词开头,而非 '^.*$'
line: 'PasswordAuthentication no'
backup: true10.4 创建 Web 内容目录与符号链接
---
- name: Configure webdev directory
hosts: dev
become: true
roles:
- apache
tasks:
- name: Ensure webdev group exists
ansible.builtin.group:
name: webdev
state: present
- name: Create /webdev directory
ansible.builtin.file:
path: /webdev
state: directory
owner: root
group: webdev
mode: '2775' # SGID 权限:新建文件继承 webdev 组
setype: httpd_sys_content_t
- name: Create symbolic link
ansible.builtin.file:
src: /webdev
dest: /var/www/html/webdev
state: link
setype: httpd_sys_content_t
- name: Create index.html
ansible.builtin.copy:
content: "Development\n"
dest: /webdev/index.html
owner: root
group: webdev
mode: '0644'
setype: httpd_sys_content_t权限 2775 的含义:
2= SGID(Set Group ID):目录下新建文件自动继承该目录的所属组7= 所有者:rwx7= 所属组:rwx5= 其他人:r-x
10.5 生成硬件报告
---
- name: Generate hardware report
hosts: all
become: true
gather_facts: true
tasks:
- name: Download report template
ansible.builtin.get_url: # 原文档此处模块名有误,已修正
url: http://classroom/materials/hwreport.empty
dest: /root/hwreport.txt
force: true # 强制重新下载,确保模板是最新的
- name: Fill HOST field
ansible.builtin.lineinfile:
path: /root/hwreport.txt
regexp: '^HOST='
line: "HOST={{ inventory_hostname }}"
- name: Fill MEMORY field
ansible.builtin.lineinfile:
path: /root/hwreport.txt
regexp: '^MEMORY='
line: "MEMORY={{ ansible_memtotal_mb | default('NONE') }}"
- name: Fill BIOS field
ansible.builtin.lineinfile:
path: /root/hwreport.txt
regexp: '^BIOS='
line: "BIOS={{ ansible_bios_version | default('NONE') }}"
- name: Fill DISK_SIZE_VDA field
ansible.builtin.lineinfile:
path: /root/hwreport.txt
regexp: '^DISK_SIZE_VDA='
line: "DISK_SIZE_VDA={{ ansible_devices.vda.size if ansible_devices.vda is defined else 'NONE' }}"
- name: Fill DISK_SIZE_VDB field
ansible.builtin.lineinfile:
path: /root/hwreport.txt
regexp: '^DISK_SIZE_VDB='
line: "DISK_SIZE_VDB={{ ansible_devices.vdb.size if ansible_devices.vdb is defined else 'NONE' }}"11. 存储管理:LVM 与分区
11.1 逻辑卷创建与格式化
---
- name: Create and format logical volume
hosts: all
become: true
tasks:
- block:
- name: Create logical volume data with 1500 MiB
community.general.lvol:
vg: research
lv: data
size: 1500M # ✅ 大写 M 是规范写法
rescue:
- name: Report size failure and create smaller LV
ansible.builtin.debug:
msg: "Could not create 1500M logical volume, trying 800M"
- name: Create logical volume data with 800 MiB
community.general.lvol:
vg: research
lv: data
size: 800M
always:
- name: Report if VG does not exist
ansible.builtin.debug:
msg: "Volume group 'research' does not exist"
when: >
ansible_facts.lvm is not defined
or 'research' not in ansible_facts.lvm.vgs
- name: Format logical volume as ext4
community.general.filesystem:
fstype: ext4
dev: /dev/research/data
when: >
ansible_facts.lvm is defined
and 'research' in ansible_facts.lvm.vgsblock / rescue / always 执行逻辑:
block 执行成功 → always 执行
block 执行失败 → rescue 执行 → always 执行
rescue 执行失败 → always 执行(rescue 的错误会被抛出)
11.2 分区、格式化与挂载
---
- name: Create partitions and mount
hosts: prod
become: true
tasks:
- block:
- name: Create 1500 MiB partition on vdf and vdg
community.general.parted:
device: "{{ item }}"
number: 1
state: present
part_type: primary
part_end: 1500MiB
loop:
- /dev/vdf
- /dev/vdg
rescue:
- name: Report error and create 800 MiB partition
ansible.builtin.debug:
msg: "Could not create 1500MiB partition, trying 800MiB"
- name: Create 800 MiB partition
community.general.parted:
device: "{{ item }}"
number: 1
state: present
part_type: primary
part_end: 800MiB
loop:
- /dev/vdf
- /dev/vdg
always:
- name: Create ext4 filesystem
community.general.filesystem:
fstype: ext4
dev: "{{ item }}"
loop:
- /dev/vdf1
- /dev/vdg1
- name: Mount /newpart (vdf1)
ansible.posix.mount:
path: /newpart
src: /dev/vdf1
fstype: ext4
state: mounted
when: ansible_devices.vdf is defined
- name: Mount /newpart1 (vdg1)
ansible.posix.mount:
path: /newpart1
src: /dev/vdg1
fstype: ext4
state: mounted
when: ansible_devices.vdg is defined
- name: Report if vdg does not exist
ansible.builtin.debug:
msg: "Disk /dev/vdg does not exist on this host"
when: ansible_devices.vdg is not defined12. Vault 加密与用户账户
12.1 创建 Vault 加密文件
step 1:准备明文变量文件
# /home/greg/ansible/locker.yml(加密前)
pw_developer: Imadev
pw_manager: Imamgrstep 2:加密文件
ansible-vault encrypt /home/greg/ansible/locker.yml
# 按提示输入 Vault 密码 (例如:strongpasswd)# ✅ 正确写法(无换行)
printf 'strongpasswd' > /home/greg/ansible/secret.txt
# ❌ 错误写法(echo 默认会加换行,某些情况下导致密码不匹配)
# echo strongpasswd > /home/greg/ansible/secret.txtVault 常用命令速查:
# 加密文件
ansible-vault encrypt locker.yml
# 解密文件(会覆盖原文件为明文)
ansible-vault decrypt locker.yml
# 查看加密文件内容(不解密)
ansible-vault view locker.yml
# 修改加密文件内容
ansible-vault edit locker.yml
# 更换 Vault 密码(rekey 后记得同步更新 secret.txt)
ansible-vault rekey locker.yml
# 加密单个字符串(嵌入到 playbook 变量中)
ansible-vault encrypt_string 'MyPassword123' --name 'db_password'12.2 用户列表文件
user_list.yml
# /home/greg/ansible/user_list.yml(明文,无需加密)
users:
- name: alice
job: developer
- name: bob
job: manager
- name: carol
job: developer12.3 创建用户账户 Playbook
---
- name: Create developer users on dev and test
hosts: dev,test
become: true
vars_files:
- /home/greg/ansible/locker.yml # 加密文件,自动解密
- /home/greg/ansible/user_list.yml # 用户列表
tasks:
- name: Ensure devops group exists
ansible.builtin.group:
name: devops
state: present
- name: Create developer users
ansible.builtin.user:
name: "{{ item.name }}"
groups: devops
append: true # 追加到组,不替换现有组
password: "{{ pw_developer | password_hash('sha512', 'mysalt') }}"
password_expire_max: 30 # 密码 30 天后过期
loop: "{{ users }}"
when: item.job == 'developer'
---
- name: Create manager users on prod
hosts: prod
become: true
vars_files:
- /home/greg/ansible/locker.yml
- /home/greg/ansible/user_list.yml
tasks:
- name: Ensure opsmgr group exists
ansible.builtin.group:
name: opsmgr
state: present
- name: Create manager users
ansible.builtin.user:
name: "{{ item.name }}"
groups: opsmgr
append: true
password: "{{ pw_manager | password_hash('sha512', 'mysalt') }}"
password_expire_max: 30
loop: "{{ users }}"
when: item.job == 'manager'生产环境密码加盐建议: password: "{{ pw_developer | password_hash('sha512', 65535 | random(seed=inventory_hostname) | string) }}" 使用 inventory_hostname 作为随机种子,每台主机的盐不同,但可复现。
13. Cron 计划任务
---
- name: Create cron job for natasha
hosts: test
become: true
tasks:
- name: Schedule logger job every 2 minutes
ansible.builtin.cron:
name: "check dirs" # 任务唯一标识(删除时也用此名)
minute: "*/2" # 每 2 分钟
hour: "*"
day: "*"
month: "*"
weekday: "*"
job: 'logger "Ex200 in progress"' # 注意外双内单引号,避免 shell 解析冲突
user: natasha # 以 natasha 用户运行
state: presentcron 模块关键参数:
参数 | 含义 | 示例 |
name | 任务名称(用于幂等标识) | "daily backup" |
minute | 分钟 | "0" / "*/5" / "0,30" |
hour | 小时 | "2" / "*/6" |
job | 执行的命令 | "/usr/bin/backup.sh" |
user | 以哪个用户运行 | "www-data" |
state | present 创建 / absent 删除 | |
disabled | true 注释掉该条目(不删除) |
14. 自定义角色开发
14.1 Apache 角色目录结构
roles/apache/
├── defaults/
│ └── main.yml # 默认变量(优先级最低)
├── tasks/
│ └── main.yml # 主任务文件
├── handlers/
│ └── main.yml # 处理程序(触发式任务)
├── templates/
│ └── index.html.j2 # Jinja2 模板
└── meta/
└── main.yml # 角色元数据(依赖关系)14.2 tasks/main.yml
---
- name: Install httpd
ansible.builtin.dnf:
name: httpd
state: present
notify: Restart httpd
- name: Ensure firewalld is installed
ansible.builtin.dnf:
name: firewalld
state: present
- name: Enable and start firewalld
ansible.builtin.service:
name: firewalld
state: started
enabled: true
- name: Allow http through firewall
ansible.posix.firewalld:
service: http
state: enabled
permanent: true
immediate: true # 立即生效,无需重启 firewalld
- name: Enable and start httpd
ansible.builtin.service:
name: httpd
state: started
enabled: true
- name: Deploy index.html
ansible.builtin.template:
src: index.html.j2
dest: /var/www/html/index.html
owner: apache
group: apache
mode: '0644'
notify: Restart httpd14.3 handlers/main.yml
---
- name: Restart httpd
ansible.builtin.service:
name: httpd
state: restartedHandler 的特点:
同一个 handler 在一次 play 中无论被
notify多少次,只执行一次(在所有任务完成后)只有被 notify 才会执行;如果对应任务没有
changed,不会触发
14.4 defaults/main.yml
---
http_port: 80
web_root: /var/www/html
server_admin: admin@example.com14.5 调用角色的 Playbook
---
- name: Deploy apache on webservers
hosts: webservers
become: true
roles:
- apache # 简写
# 或者带参数的写法
roles:
- role: apache
vars:
http_port: 8080 # 覆盖角色默认变量14.6 使用 Galaxy 角色
---
- name: Configure load balancer
hosts: balancers
become: true
roles:
- balancer
---
- name: Deploy phpinfo app
hosts: webservers
become: true
roles:
- phpinfo15. Podman 容器管理
15.1 安装容器管理依赖
---
- name: Install Podman and dependencies
hosts: all
become: true
tasks:
- name: Install podman and python module
ansible.builtin.dnf:
name:
- podman
- python3-podman # 可选,用于 API 访问
state: present15.2 拉取镜像并运行容器
---
- name: Run a web container with Podman
hosts: dev
become: false # Podman 推荐以普通用户运行(rootless)
tasks:
- name: Pull nginx image
containers.podman.podman_image:
name: docker.io/library/nginx
state: present
- name: Run nginx container
containers.podman.podman_container:
name: my_nginx
image: docker.io/library/nginx
state: started
ports:
- "8080:80"
restart_policy: always15.3 配置 systemd 服务
Quadlet 方式,RHEL 10 推荐
---
- name: Deploy container as systemd service
hosts: prod
become: true
tasks:
- name: Create Quadlet unit file directory
ansible.builtin.file:
path: /etc/containers/systemd
state: directory
mode: '0755'
- name: Create container unit file
ansible.builtin.copy:
dest: /etc/containers/systemd/my_app.container
content: |
[Unit]
Description=My Application Container
[Container]
Image=docker.io/library/nginx:latest
PublishPort=8080:80
[Service]
Restart=always
[Install]
WantedBy=multi-user.target
- name: Reload systemd daemon
ansible.builtin.systemd:
daemon_reload: true
- name: Enable and start container service
ansible.builtin.systemd:
name: my_app
state: started
enabled: true16. Ad-Hoc 命令速查
Ad-Hoc 命令格式:ansible <主机/组> -m <模块> -a "<参数>"
16.1 常用 Ad-Hoc 命令
# 测试连通性
ansible all -m ping
# 执行 shell 命令(支持管道)
ansible all -m shell -a "df -h | grep /dev"
# 执行简单命令(不支持管道,更安全)
ansible all -m command -a "uptime"
# 安装软件包
ansible webservers -m dnf -a "name=httpd state=present" --become
# 启动服务
ansible webservers -m service -a "name=httpd state=started enabled=yes" --become
# 复制文件到远程主机
ansible all -m copy -a "src=/tmp/test.txt dest=/tmp/test.txt mode=0644" --become
# 查看远程主机所有 facts
ansible servera -m setup
# 过滤 facts(只看 IP 相关)
ansible servera -m setup -a "filter=ansible_default_ipv4"
# 创建目录
ansible all -m file -a "path=/opt/myapp state=directory mode=0755" --become
# 删除文件
ansible all -m file -a "path=/tmp/test.txt state=absent" --become
# 重启服务
ansible all -m service -a "name=httpd state=restarted" --become
# 执行完后打印输出
ansible all -m command -a "hostname" -o16.2 Ad-Hoc vs Playbook 的选择
场景 | 推荐方式 |
临时查询(看磁盘、看进程) | Ad-Hoc |
一次性简单任务 | Ad-Hoc |
需要重复执行的配置 | Playbook |
涉及多个步骤或条件判断 | Playbook |
需要错误处理 | Playbook |
需要版本控制和审计 | Playbook |
17. 变量、Facts 与优先级
17.1 变量优先级
由低到高,共 22 级
核心记住: 命令行 extra vars(-e)> playbook vars > role defaults
优先级 | 变量来源 | 说明 |
1 | role defaults/main.yml | 角色默认值,最容易被覆盖 |
2 | inventory 文件中的变量 | [group:vars] 区块 |
3 | group_vars/all | 所有组的变量文件 |
4 | group_vars/<组名> | 特定组的变量文件 |
5 | host_vars/<主机名> | 特定主机的变量文件 |
6 | host facts(gather_facts) | 系统自动收集的事实 |
7 | play 中的 vars: | playbook 内定义的变量 |
8 | play 中的 vars_files: | 从文件加载的变量 |
9 | role vars(vars/main.yml) | 角色内部变量,优先级较高 |
10 | task 中的 vars: | 单个任务的变量 |
11 | register 注册的变量 | 任务执行结果 |
22 | 命令行 -e / --extra-vars | 最高优先级,覆盖一切 |
17.2 Facts(系统事实)常用变量
# 查看主机的所有 facts
ansible servera -m setup
# 常用 facts 变量
ansible_hostname # 主机名(短)
ansible_fqdn # 完全限定域名
ansible_default_ipv4 # 默认 IPv4 信息(字典)
.address # IP 地址
.interface # 网卡名
.gateway # 网关
ansible_memtotal_mb # 总内存(MB)
ansible_processor_vcpus # CPU 核数
ansible_os_family # OS 家族(RedHat / Debian)
ansible_distribution # 发行版名称(RHEL / CentOS)
ansible_distribution_major_version # 主版本号
ansible_devices # 所有块设备信息(字典)
ansible_selinux.status # SELinux 状态17.3 变量的三种作用域
作用域 | 来源 | 说明 |
全局(Global) | 命令行 -e、ansible.cfg | 对所有 host 和 play 生效 |
Play | vars:、vars_files:、group_vars/ | 对该 play 内的所有任务生效 |
Host | host_vars/、facts、register | 只对特定主机生效 |
17.4 register 注册变量
- name: Check if file exists
ansible.builtin.stat:
path: /etc/myconfig.conf
register: config_stat # 将任务结果注册到变量
- name: Print result
ansible.builtin.debug:
msg: "File exists: {{ config_stat.stat.exists }}"
- name: Act based on result
ansible.builtin.copy:
content: "default config\n"
dest: /etc/myconfig.conf
when: not config_stat.stat.exists18. 错误处理与调试
18.1 block / rescue / always
tasks:
- block:
- name: 尝试主要操作
ansible.builtin.command: /opt/risky_command.sh
rescue:
- name: 主要操作失败后的补救措施
ansible.builtin.debug:
msg: "主要操作失败:{{ ansible_failed_result.msg }}"
always:
- name: 无论成功失败都执行(清理/报告)
ansible.builtin.debug:
msg: "任务执行完毕"18.2 忽略错误
- name: 允许失败,继续执行
ansible.builtin.command: /opt/optional_command.sh
ignore_errors: true
- name: 失败时不标记为 changed(用于检查类任务)
ansible.builtin.command: /opt/check.sh
failed_when: false18.3 自定义失败条件
- name: 根据返回值决定是否失败
ansible.builtin.command: /opt/check.sh
register: result
failed_when: result.rc not in [0, 1] # rc=0 或 1 都视为成功
- name: 根据输出内容决定
ansible.builtin.shell: systemctl status httpd
register: httpd_status
failed_when: "'dead' in httpd_status.stdout"18.4 调试技巧
# 增加详细程度(-v 到 -vvvv)
ansible-playbook site.yml -v # 任务结果
ansible-playbook site.yml -vv # 任务输入/输出
ansible-playbook site.yml -vvv # 连接信息
ansible-playbook site.yml -vvvv # 完整调试
# 在特定主机上开始执行(跳过之前的任务)
ansible-playbook site.yml --start-at-task="Install httpd"
# 只执行打了特定 tag 的任务
ansible-playbook site.yml --tags "config,deploy"
ansible-playbook site.yml --skip-tags "debug"
# 检查 playbook 语法
ansible-playbook site.yml --syntax-check
# 试运行(Check Mode)
ansible-playbook site.yml --check
# 列出所有将要执行的任务(不执行)
ansible-playbook site.yml --list-tasks
# 列出将受影响的主机
ansible-playbook site.yml --list-hosts19. 高频问题 Q&A
Q1:Ansible 是什么?它的核心优势是什么?
答: Ansible 是一个基于 Python 开发的开源自动化运维工具。核心优势:
无代理(Agentless):基于 SSH(Linux)/ WinRM(Windows)通信,被管节点无需安装 Agent
幂等性(Idempotent):多次执行同一 Playbook,结果一致,不会重复操作
YAML 语法:人类可读,学习成本低
Push 架构:控制节点主动推送,无需轮询
丰富的模块生态:3000+ 官方模块,覆盖云平台、网络设备、中间件等
Q2:Ansible 的工作原理是什么?
答: 执行流程分四步:
读取配置:加载
ansible.cfg,确定 inventory、插件路径等连接目标主机:通过 SSH(默认)建立连接
推送模块:将 Python 模块文件推送到目标主机的临时目录
执行并清理:执行模块,收集返回值,删除临时文件
Q3:Playbook 和 Ad-Hoc 命令的区别?
答:
对比维度 | Ad-Hoc | Playbook |
适用场景 | 临时、一次性任务 | 复杂、可重复的配置管理 |
复杂度 | 单模块单任务 | 多 play 多任务,支持条件/循环 |
版本控制 | 不易管理 | 易于纳入 Git |
错误处理 | 不支持 | 支持 block/rescue/always |
幂等性 | 视模块而定 | 设计上追求幂等 |
Q4:Ansible 如何处理敏感数据?
答: 使用 Ansible Vault 加密敏感文件或字符串:
ansible-vault encrypt:加密整个文件ansible-vault encrypt_string:加密单个变量值(嵌入 playbook)密码文件(
vault_password_file)避免每次手动输入最佳实践:不要把
secret.txt(vault 密码文件)提交到 Git
Q5:Ansible 的变量优先级怎么理解?
答: 简记为三个层次:
最低:角色
defaults/main.yml(设计为可被覆盖的默认值)中间:
group_vars、host_vars、playvars:(正常使用的变量)最高:命令行
-e(临时覆盖,调试时用)
面试时能说出"extra vars 优先级最高,role defaults 最低"即可加分。
Q6:如何实现任务失败后的优雅降级?
答: 使用 block / rescue / always:
block: → 正常逻辑
rescue: → block 失败时执行(降级方案)
always: → 无论成功失败都执行(清理/通知)Q7:FQCN 是什么?为什么推荐用?
答: FQCN = Fully Qualified Collection Name(全限定集合名),格式为 namespace.collection.module。
例如:ansible.builtin.dnf(不写 dnf)
优势:
避免不同 collection 中同名模块的歧义
代码可读性好,一眼知道模块来源
在多 collection 环境中防止冲突
Q8:ansible-playbook 和 ansible-navigator 的区别?
答:
对比 | ansible-playbook | ansible-navigator |
运行环境 | 控制节点本地 Python 环境 | Execution Environment(容器化) |
依赖管理 | 手动管理 collections/roles | EE 镜像内已打包 |
界面 | 命令行文本输出 | TUI 交互界面(可切换 stdout 模式) |
适用版本 | 所有版本 | RHEL 9+ / AAP 2.x |
20. 模块选择速查表
需求 | 推荐模块(FQCN) | 关键参数 |
|---|---|---|
安装/卸载软件包 | ansible.builtin.dnf | name, state |
启动/停止服务 | ansible.builtin.service | name, state, enabled |
复制文件(固定内容) | ansible.builtin.copy | src/content, dest, mode |
渲染 Jinja2 模板 | ansible.builtin.template | src, dest |
修改文件单行配置 | ansible.builtin.lineinfile | path, regexp, line |
插入一段内容 | ansible.builtin.blockinfile | path, block, marker |
创建/删除文件目录 | ansible.builtin.file | path, state, mode |
下载远程文件 | ansible.builtin.get_url | url, dest |
执行 shell 命令 | cmd(支持管道) | |
执行简单命令 | ansible.builtin.command | cmd(不支持管道) |
管理用户 | ansible.builtin.user | name, password, groups |
管理用户组 | name, state | |
管理 Cron 任务 | ansible.builtin.cron | name, minute, job |
配置 yum/dnf 仓库 | ansible.builtin.yum_repository | name, baseurl, gpgcheck |
收集系统信息 | ansible.builtin.setup | filter(过滤 facts) |
调试输出信息 | ansible.builtin.debug | msg, var |
暂停执行 | ansible.builtin.pause | seconds, prompt |
等待条件满足 | ansible.builtin.wait_for | port, host, timeout |
管理防火墙规则 | ansible.posix.firewalld | service/port, state, permanent |
挂载文件系统 | ansible.posix.mount | path, src, fstype, state |
管理逻辑卷 | community.general.lvol | vg, lv, size |
管理卷组 | community.general.lvg | vg, pvs |
管理磁盘分区 | community.general.parted | device, number, part_end |
创建文件系统 | community.general.filesystem | fstype, dev |
附录:快速命令索引
# ── 安装与配置 ──────────────────────────────────────────────
sudo dnf install ansible-core ansible-navigator # RHEL 9
ansible-config dump --only-changed # 查看生效配置
ansible-inventory --graph # 查看清单结构
# ── Galaxy 管理 ─────────────────────────────────────────────
ansible-galaxy collection install -r requirements.yml -p mycollection/
ansible-galaxy install -r roles/requirements.yml -p roles/
ansible-galaxy collection list
ansible-galaxy role list
# ── Vault 操作 ──────────────────────────────────────────────
ansible-vault encrypt file.yml
ansible-vault decrypt file.yml
ansible-vault view file.yml
ansible-vault edit file.yml
ansible-vault rekey file.yml
# ── Playbook 执行 ───────────────────────────────────────────
ansible-playbook site.yml # 执行
ansible-playbook site.yml --syntax-check # 语法检查
ansible-playbook site.yml --check # 试运行
ansible-playbook site.yml -v / -vv / -vvv # 调试级别
ansible-playbook site.yml --tags "install" # 按标签执行
ansible-playbook site.yml --limit "servera,serverb" # 限定主机
# ── Navigator ──────────────────────────────────────────────
ansible-navigator run site.yml -m stdout
ansible-navigator collections
ansible-navigator doc ansible.builtin.dnf -m stdout
ansible-navigator inventory --list -m stdout