本文内容主要是为了实现《从一个简单例子开始》中老板那些大胆的想法而写的。当然作为Ansible Playbook的作成参考也不是不行。
本文持续更新中。
Ansible基本要素
Play
Playbook 的基本组成单元。一个 Playbook 可以包含一个或多个 Play,每个 Play 定义了在哪些主机上执行哪些任务,以及如何执行这些任务。不同的 Play 之间通过---
分隔。如:
---
- name: Play01
hosts: xxx
# ...
---
- name: Play02
hosts: xxx
# ...
Play是Playbook的顶层结构
以下内容只能在 Play 中定义:
- hosts
- gather_facts
- roles
- pre_tasks 和 post_tasks
- max_fail_percentage
- serial
- any_errors_fatal
- strategy
以下内容可以在 Play 或 Task 中定义,但 Play 级别的定义会影响整个 Play:
- become 和 become_*
- vars
- handlers
Inventory
动态inventory
如果需要从外部系统(如云平台)动态获取主机信息,可以使用脚本生成JSON格式的inventory。比如,例子中的inventory需要脚本能够输出(打印)以下内容:
{
"my_servers": {
"hosts": [
"SRV01",
"SRV02",
"SRV03"
],
"vars": {
"ansible_user": "my_work",
"ansible_become": "yes"
}
},
"_meta": {
"hostvars": {
"SRV01": {
"ansible_host": "192.168.0.1",
"ansible_become_password": "你的sudo密码"
},
"SRV02": {
"ansible_host": "192.168.0.2",
"ansible_become_password": "你的sudo密码"
},
"SRV03": {
"ansible_host": "192.168.0.3",
"ansible_become_password": "你的sudo密码"
}
}
}
}
逻辑结构
循环
列表遍历
使用item
变量和with_items
模块:
- name: 执行脚本
command: "{{ item }}"
with_items:
- "hostname"
- "uname -a"
- "date"
register: service_status
- name: 打印结果
# 输出结果也需要遍历哦
debug:
var: "{{ item.stdout }}"
with_items: "{{ service_status.results }}"
变量
变量的作用范围
vars
的作用域取决于定义的位置,可以是 Play、Task、Role 或全局register
的作用域通常是当前 Play 或 Block- 通过
set_fact
可以将register
的结果保存为全局变量,跨 Play 使用
使用例:
---
- name: 综合示例
hosts: all
vars:
play_var: "Play 变量"
tasks:
- name: 定义 Task 变量
debug:
msg: "{{ task_var }}"
vars:
task_var: "Task 变量"
- name: 获取当前用户
command: whoami
register: whoami_result
- name: 打印注册变量
debug:
msg: "{{ whoami_result.stdout }}"
- name: 保存到全局变量
set_fact:
global_whoami: "{{ whoami_result.stdout }}"
---
- name: 使用全局变量
hosts: all
tasks:
- name: 打印全局变量
debug:
msg: "{{ global_whoami }}"
Ansible内置变量
可以在playbook中直接使用的变量。这里列举一些常用的:
Inventory变量(参数)
变量名 | 用途 |
---|---|
ansible_host | 目标主机的 IP 地址或域名 |
ansible_user | 连接到目标主机的用户名 |
ansible_port | SSH 端口(默认是 22) |
ansible_ssh_private_key_file | SSH 私钥文件的路径 |
ansible_connection | 连接类型(例如 ssh、local、docker、winrm) |
ansible_become | 是否启用权限提升(例如 sudo) |
ansible_become_user | 权限提升时切换到的用户(例如 root) |
ansible_become_method | 权限提升的方式(例如 sudo、su) |
ansible_become_password | 权限提升时使用的密码 |
任务中使用的变量
变量名 | 用途 |
---|---|
inventory_hostname | 当前主机的host名称 |
play_hosts | 当前playbook中所有主机的列表 |
ansible_user_dir | (远程)当前用户的主目录 |
ansible_user_id | (远程)当前用户的用户名 |
Ansible内置常用模块
执行命令行
除了例子中使用的command
,以下方式也可以在远程主机执行命令行。
1. Shell
- name: 在一个任务中执行多条命令
hosts: all
tasks:
- name: 运行多条命令
shell: |
whoami &&
date &&
ls -l /tmp
register: multi_command_result
- name: 打印多条命令的结果
debug:
var: multi_command_result.stdout
其中:
- “&&”表示前一条命令成功后才执行下一条命令
- “;”表示无论前一条命令是否成功,都执行下一条命令
2. Raw
用于在不依赖 Python 的环境中执行命令(例如未安装 Python 的主机)。
- name: 使用 raw 模块执行多条命令
tasks:
- name: 运行多条命令
raw: |
whoami
date
ls -l /tmp
register: raw_command_result
- name: 打印 raw 命令的结果
debug:
var: raw_command_result.stdout
3. Script
可以使用script
在远程主机直接执行shell脚本文件。
- name: 执行本地脚本
tasks:
- name: 上传并运行脚本
script: /path/to/local/script.sh
register: script_result
- name: 打印脚本执行结果
debug:
var: script_result.stdout
4. Block
如果你希望将多个命令作为独立任务执行,可以使用block
将它们组织在一起。
- name: 使用 block 组织多个任务
tasks:
- block:
- name: 运行 whoami
command: whoami
register: whoami_result
- name: 运行 date
command: date
register: date_result
- name: 运行 ls
command: ls -l /tmp
register: ls_result
rescue:
# 处理任务失败的情况
- name: 处理错误
debug:
msg: "某个任务失败"
always:
# 无论任务是否成功都会执行
- name: 打印所有结果
debug:
msg: |
whoami: {{ whoami_result.stdout }}
date: {{ date_result.stdout }}
ls: {{ ls_result.stdout }}
文件操作
写文件
可以使用copy
模块写文件。
- name: 写入文件
copy:
content: |
这是第一行
这是第二行
# 使用“src: <path>”则可以实现文件复制
dest: /path/to/remote/file.txt
读取文件
可以使用lookup
插件读取文件文本。
- name: 读取文件
debug:
msg: "{{ lookup('file', '/path/to/file.txt') }}"
删除文件/修改权限
使用file
模块可以删除文件,或修改文件权限。
- name: 删除文件
file:
path: /path/to/remote/file.txt
state: absent
- name: 设置文件权限
file:
path: /path/to/remote/file.txt
mode: '0644'
判断文件是否存在
使用stat
模块可以判断文件是否存在。
- name: 获取文件状态
stat:
path: /path/to/remote/file.txt
register: file_stat
- name: 打印文件是否存在
debug:
msg: "文件存在"
when: file_stat.stat.exists
- name: 打印文件不存在
debug:
msg: "文件不存在"
when: not file_stat.stat.exists
条件检查
Assert
assert
模块用于在 Playbook 中执行断言检查。如果断言条件不满足,任务会失败并停止 Playbook 的执行。
注意:条件表达式不要使用{{ }}
表示变量,变量名直接写就行。如果字符串与变量重名,则对字符串用" "
。
- name: 获取Hostname
command: "hostname"
register: host_current
- name: 检查Hostname
assert:
that:
- host_current.stdout == inventory_hostname
# 可以设置多个条件
fail_msg: "Host名有问题,终止执行"
success_msg: "Host名正确,继续执行"
# 此处还可以加入assert发动的条件:
# when: xxx
其他条件?
例子中只比较了字符串是否一致。这里再整理一些常用的。
条件 | 语句 |
---|---|
数值比较 | my_var > 0 |
文件存在 | file_stat.stat.exists file_stat详见“判断文件是否存在”章节 |
逻辑或 | 条件1 or 条件2 |
字符串包含 | s in text |
正则表达式(※) | host_current.stdout is match(“^Linux %s .+$” % inventory_hostname) |
※ 这里使用了is match
,即完全匹配。也可以使用is search
进行部分匹配。
Jinja2
Jinja2 是一个现代的、设计优雅的模板引擎,广泛用于 Python 生态系统中。它是 Ansible 中默认的模板引擎,用于动态生成文本内容(如配置文件、脚本、命令等)。Jinja2 提供了强大的功能,包括变量替换、条件判断、循环、过滤器等。
模板渲染
如果要使用模板,先要制作模板,并保存在pj/templates/xxx.j2
:
CREATE DATE: {{ get_date }}
UNAME: {{ get_uname }}
在playbook里这么写:
- name: 渲染模板
vars:
# 变量名与模板文件中的一致
get_date: "{{ service_status.results.2.stdout }}"
get_uname: "{{ service_status.results.1.stdout }}"
template:
src: templates/xxx.j2
dest: "/home/manager/xxx_filled.txt"
delegate_to: localhost
# 如果不写delegate_to这行,默认将在远程设备的dest存储文件
于是你将在本地 文件/home/manager/xxx_filled.txt
看到以下内容:
CREATE DATE: 2025年 02月 25日 星期二 12:55:58 UTC
UNAME: Linux SRV01 3.10.0-1160.59.1.el7.x86_64 #1 SMP Wed Feb 23 16:47:03 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
条件判断
Jinja2 支持if
条件语句,可以根据条件生成不同的内容。
- name: 打印天气
debug:
msg: >
{% if is_raining %}
今天下雨了,记得带伞!
{% else %}
今天天气晴朗,出门享受阳光吧!
{% endif %}
当is_raining
在前文定义为真,此时将输出“今天下雨了,记得带伞!”。