3.Ansible编写和运行playbook
3.Ansible编写和运行 Playbook
Playbook 介绍
如果把 Ansible 的ad-hoc命令
比作 “一次性脚本”(适合临时执行单个简单任务),那么Playbook
就是 “可重复执行的程序”(适合复杂、多步骤的管理流程)。
举个例子:
-
用 ad-hoc 创建一个用户可以这样写(临时执行一次):
# 在node1上创建用户newbie,UID为4000 [bq@controller web]$ ansible -m user -a "name=newbie uid=4000 state=present" node1
-
但如果需要在多台主机上重复这个操作,或者后续还要修改用户属性,用 Playbook 更合适。把上述操作写成 Playbook(保存为
create_user.yml
):--- - name: 统一配置用户(描述这个Play的作用)hosts: node1 # 目标主机(从清单中选)tasks:- name: 确保newbie用户存在且UID为4000(描述这个任务的作用)user: # 使用user模块name: newbie # 用户名uid: 4000 # UIDstate: present # 状态:存在(present)或删除(absent) ...
Playbook 本质是一个YAML格式的文本文件
(通常以.yaml
或.yml
为扩展名),核心作用是:
- 按顺序定义多个
Play
(每个 Play 是一组针对特定主机的操作); - 每个 Play 包含多个
任务
(按顺序执行的具体操作,如安装软件、修改配置等); - 可重复执行,结果可预测(第二次执行相同 Playbook 时,未变更的任务会显示 “OK”)。
Vim 编辑器设置
用 Vim 编辑 Playbook 时,建议配置自动缩进和 Tab 转换,避免格式错误。
在用户家目录的.vimrc
文件中添加配置:
# 方法1:全局生效(所有文件都用2个空格缩进,显示行号)
set ai ts=2 number# 方法2:仅YAML文件生效(更推荐)
autocmd FileType yaml set ai ts=2 # 打开YAML文件时,自动启用2个空格缩进
配置说明:
ai
:自动缩进(新行继承上一行的缩进);ts=2
:Tab 键自动转换为 2 个空格;number
:显示行号(方便定位错误)。
YAML 格式基础
基本格式:
--- # YAML 文件起始标识(建议添加)
- name: 第一个Play(描述该Play的作用,如"部署Web服务")hosts: web_servers # 目标主机/主机组(来自inventory)remote_user: devops # 远程执行用户become: yes # 是否提权(yes/no)become_method: sudo # 提权方式(默认sudo)become_user: root # 提权后的目标用户(通常为root)vars: # 定义变量(可在任务中引用)app_name: "myapp"app_port: 8080tasks: # 任务列表(按顺序执行)- name: 任务1描述(如"安装依赖包")yum: # 模块名称(根据操作选择,如yum/apt/copy等)name: "{{ item }}" # 引用变量或列表项state: presentloop: # 循环执行(可选)- package1- package2- name: 任务2描述(如"复制配置文件")copy:src: ./local_config.conf # 本地文件路径dest: /etc/remote_config.conf # 目标路径mode: '0644' # 文件权限notify: # 触发处理器(配置变更时执行,可选)- 重启服务handlers: # 处理器(仅在被notify触发时执行)- name: 重启服务systemd:name: "{{ app_name }}"state: restarted- name: 第二个Play(可定义多个Play,按顺序执行)hosts: db_serverstasks:- name: 数据库相关任务# 具体模块和参数...
核心结构说明:
- Play 定义:每个
- name: ...
开头的块是一个 Play,包含对特定主机的操作 - 主机与用户配置:
hosts
指定目标,remote_user
和become
控制执行权限 - 变量(vars):集中定义可复用的值,通过
{{ 变量名 }}
引用 - 任务(tasks):具体操作列表,每个任务包含
name
(描述)、模块和参数 - 处理器(handlers):用于响应任务通知的操作(如配置变更后重启服务)
编写时需注意:
-
严格使用空格缩进(推荐 2 个空格),同一层级缩进必须一致
-
注释用
#
(#
后面的内容会被忽略,建议多写注释提高可读性) -
模块参数需符合对应模块的语法要求(可通过
ansible-doc 模块名
查看文档) -
可通过
ansible-playbook --syntax-check 剧本名.yml
检查语法正确性 -
冒号右侧(
key: value
):大部分可修改,少数不可,右侧是 “值”,用于填充具体内容、配置或引用,能否修改取决于其是否为固定枚举值。-
- 可修改:动态值或自定义内容
大部分情况下,右侧是用户根据需求设置的具体值,可自由修改:
- 字符串 / 路径:如
hosts: node1
中,node1
可改为其他主机名(如node2
、all
);home: /home/joe
中,/home/joe
可改为/home/bob
。 - 变量引用:如
name: "{{ user }}"
中,{{ user }}
会动态引用变量值,变量值修改后这里也会同步变化。 - 文本内容:如
debug
模块的msg
内容,username is {{ user }}
可改为任意文本(如用户名为 {{ user }}
)。
- 不可修改:固定枚举值
部分模块参数的右侧值是 Ansible 规定的枚举值(只能从指定选项中选择),不可随意修改,否则会报错。
例如:user
模块的state
参数:只能是present
(创建)或absent
(删除),不能改为create
或delete
。service
模块的state
参数:只能是started
、stopped
、restarted
等,不可自定义其他值。
-
-
冒号(
:
)左侧的内容固定的关键字不可修改,有些是自定义名称(可修改)**。具体区分如下:-
不可修改:固定关键字
这些是 Ansible 或 YAML 语法规定的固定 “键”,必须严格按原样书写,修改后会导致语法错误或功能失效。
例如:-
Playbook 结构关键字:
name
(定义任务 / 剧本名称)、hosts
(指定目标主机)、vars
(定义变量)、tasks
(定义任务列表)等。
例:hosts: node1
中,hosts
不可修改,修改为host_list: node1
会报错。 -
模块名:如
user:
(用户管理模块)、debug:
(调试模块),是 Ansible 内置模块的唯一标识,不可修改。
例:user: name: joe
中,user
不可修改,改为add_user: name: joe
会提示 “模块不存在”。 -
模块参数:模块内置的配置项,如
user
模块的name
(用户名)、home
(家目录)、state
(状态);debug
模块的msg
(输出信息)等。
例:name: "{{ user }}"
中,name
不可修改,是user
模块规定的参数名,改为username: "{{ user }}"
会导致参数无效。 -
可修改:自定义标识 / 变量名
如果左侧是用户自定义的变量名、标签或标识符,则可以修改,只要符合 YAML 命名规范(字母、数字、下划线,不建议用特殊字符)。
例如:- 自定义变量名:
vars
下的变量,如user: joe
中,user
是自定义变量名,可改为username: joe
(后续引用需同步改为{{ username }}
)。 - 自定义标签:如果使用
tags
关键字,标签名可自定义,如tags: create_user
中,create_user
可修改为add_user
等。
- 自定义变量名:
2. 可修改:自定义标识 / 变量名
如果左侧是用户自定义的变量名、标签或标识符,则可以修改,只要符合 YAML 命名规范(字母、数字、下划线,不建议用特殊字符)。
例如:- 自定义变量名:
vars
下的变量,如user: joe
中,user
是自定义变量名,可改为username: joe
(后续引用需同步改为{{ username }}
)。 - 自定义标签:如果使用
tags
关键字,标签名可自定义,如tags: create_user
中,create_user
可修改为add_user
等。
-
-
实验环境搭建
下面通过一个 “部署 Web 服务” 的案例,实际演示 Playbook 的编写和运行。先搭建实验环境:
1:创建工作目录
# 创建web目录(用于存放Playbook相关文件),并进入该目录
[bq@controller ~]$ mkdir web && cd web
2:配置 Ansible 默认参数(ansible.cfg)
创建ansible.cfg
文件,定义远程用户、主机清单路径、提权方式等(避免每次执行命令重复输入参数):
[bq@controller web]$ cat > ansible.cfg <<'EOF'
[defaults]
remote_user = bq # 默认远程登录用户(要在目标主机存在)
inventory = ./inventory # 主机清单路径(指定为当前目录的inventory文件)[privilege_escalation]
become = True # 默认启用提权(执行需要root权限的操作)
become_user = root # 提权到root用户
become_method = sudo # 提权方式为sudo
become_ask_pass = False # 提权时不询问密码(需提前配置sudo免密)
EOF
3:定义主机清单(inventory)
创建inventory
文件,列出需要管理的主机(每行一个主机名,需确保 controller 能通过 SSH 连接这些主机):
[bq@controller web]$ cat > inventory <<'EOF'
controller # 控制节点(可选,可用于本地测试)
node1 # 被管理节点1(示例中主要操作的节点)
node2 # 被管理节点2(预留,本案例暂不使用)
node3
node4
EOF
Playbook 编写(实战案例)
我们编写一个 Playbook(web_deploy.yml
),实现以下功能:
- 在 node1 上安装并启动 httpd(Web 服务)和 firewalld(防火墙);
- 配置防火墙允许 http 访问;
- 创建一个测试网页;
- 在本地(controller)验证 node1 的 Web 服务是否可用。
Playbook 示例
playbook.yaml 内容如下:
# YAML文件起始标识(建议添加,明确文件格式)
---# 第一个Play:部署Web服务到node1
- name: 在node1上部署Web服务(描述Play的作用)hosts: node1 # 目标主机(从inventory中选node1)# 提权配置(覆盖ansible.cfg的默认设置,这里使用默认即可)# remote_user: bq# become: Truetasks: # 任务列表(按顺序执行)# 任务1:安装httpd和firewalld(确保是最新版本)- name: 安装最新版的httpd和firewalldyum: # 使用yum模块(适用于CentOS/RHEL系统)name: # 要安装的软件包列表- httpd # Web服务软件- firewalld # 防火墙软件state: latest # 状态:确保为最新版本(若已安装则升级)# 任务2:创建测试网页- name: 生成测试网页内容copy: # 使用copy模块(复制内容到目标文件)content: "Welcome to bq WebSite!\n" # 网页内容(\n是换行)dest: /var/www/html/index.html # 目标文件路径(httpd默认首页路径)mode: '0644' # 文件权限(所有者可读可写,其他用户只读)# 任务3:启动并设置firewalld开机自启- name: 确保firewalld服务启动并开机自启service: # 使用service模块(管理系统服务)name: firewalld # 服务名称enabled: true # 开机自启(true=启用,false=禁用)state: started # 服务状态(started=启动,stopped=停止,restarted=重启)# 任务4:防火墙放行http服务- name: 配置防火墙允许http访问(80端口)firewalld: # 使用firewalld模块(管理防火墙规则)service: http # 预定义服务(对应80端口)permanent: true # 规则永久生效(重启防火墙后不丢失)state: enabled # 启用规则immediate: yes # 立即生效(无需等待防火墙重启)# 任务5:启动并设置httpd开机自启- name: 确保httpd服务启动并开机自启service:name: httpdenabled: truestate: started# 第二个Play:在本地验证Web服务
- name: 验证node1的Web服务是否可用hosts: localhost # 目标主机为本地(controller自身)become: no # 不需要提权(本地访问无需root)tasks:- name: 访问node1的Web页面,检查是否正常响应uri: # 使用uri模块(发送HTTP请求并验证)url: http://node1 # 要访问的URL(node1的http服务)return_content: yes # 返回页面内容(可选,用于调试)status_code: 200 # 期望的HTTP状态码(200=成功)# YAML文件结束标识(可省略)
...
核心结构说明
- Play:每个
- name: ...
开头的块是一个 Play,用于定义对特定主机的操作(如第一个 Play 针对 node1,第二个针对localhost)。 - 任务(tasks):每个任务是具体的操作(如安装软件、修改配置),由
name
(描述)和模块(如yum
、service
)组成。 - 模块:Ansible 的功能单元,每个模块实现特定功能(如
yum
管理软件包,service
管理服务),参数需符合模块要求(可通过ansible-doc 模块名
查文档,如ansible-doc yum
)。
Playbook 运行与验证
1:检查语法
在运行前先检查 Playbook 语法是否正确:
[bq@controller web]$ ansible-playbook web_deploy.yml --syntax-check
# 成功输出:playbook: web_deploy.yml(无报错说明语法正确)
2:空运行
用-C
选项模拟运行,不实际修改,查看会执行哪些操作(适合测试):
[bq@controller web]$ ansible-playbook web_deploy.yml -C
# 输出中会显示每个任务的预期状态(如"changed"表示会修改,"ok"表示无变化)
3:实际运行
[bq@controller web]$ ansible-playbook web_deploy.yml#首次运行成功的输出(部分):
PLAY [在node1上部署Web服务] ******************************************
TASK [Gathering Facts] **********************************************
ok: [node1]TASK [安装最新版的httpd和firewalld] ************************************
changed: [node1] # 状态为changed,表示执行了安装操作...(中间省略其他任务)PLAY [验证node1的Web服务是否可用] **************************************
TASK [Gathering Facts] **********************************************
ok: [localhost]TASK [访问node1的Web页面,检查是否正常响应] ******************************
ok: [localhost] # 状态为ok,表示访问成功(状态码200)PLAY RECAP **********************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 ...
node1 : ok=5 changed=3 ...# 第二次运行时,所有任务会显示ok(因为系统状态已符合预期,无需修改),这体现了 Ansible 的 "幂等性"(重复执行结果一致)。
4:手动验证
在 controller 上直接访问 node1 的网页,确认内容正确:
[bq@controller web]$ curl http://node1
Welcome to bq WebSite! # 输出预期内容,说明部署成功
运行选项补充
-
详细输出:用-v系列选项查看更多信息(调试时常用):
ansible-playbook web_deploy.yml -v # 显示任务结果 ansible-playbook web_deploy.yml -vv # 显示任务结果+配置 ansible-playbook web_deploy.yml -vvv- #包含关于与受管主机连接的信息。 ansible-playbook web_deploy.yml -vvvv- #增加了连接插件相关的额外详细程度选项,包括受管主机上用于执行脚本的用户,以及所执行的脚本。
-
指定主机:用
-l
选项仅对部分主机执行(覆盖 Play 中的hosts配置):ansible-playbook web_deploy.yml -l node1 # 仅在node1上执行
YAML 注释
在 YAML中, 编号或井号符号(#)右侧的所有内容都是注释。如果注释的左侧有内容, 请在该编号符号的前面加一个空格。注释可用于提高可读性。
示例:
# This is YAML comment
Some data # This is also a YAML comment
注意事项与不足
本案例的潜在问题
- 环境依赖:
- 假设目标主机是 CentOS/RHEL(使用
yum
模块),若为 Ubuntu 需替换为apt
模块; - 需确保 controller 能解析
node1
的主机名(可在/etc/hosts
中配置); become_ask_pass = False
要求目标主机的sudo
已配置免密(否则会提权失败,需手动输入密码)。
- 假设目标主机是 CentOS/RHEL(使用
- 错误处理:
- 未添加错误判断(如软件安装失败、服务启动失败时,Playbook 会直接报错终止);
- 可通过
ignore_errors: yes
忽略特定错误,或failed_when
定义失败条件。
- 扩展性:
- 若需部署到多台主机,需在
inventory
中定义主机组(如[web_servers]
),并在 Play 中设置hosts: web_servers
; - 重复任务可提取为变量(
vars
)或角色(Roles),减少代码冗余。
- 若需部署到多台主机,需在
编写 Playbook 的最佳实践
- 多写注释:每个 Play 和任务都用
name
描述作用,复杂逻辑加#
注释; - 先测试再执行:用
--syntax-check
和-C
验证后再实际运行; - 利用幂等性:尽量使用支持幂等的模块(如
yum
的state: present
不会重复安装); - 拆分复杂任务:一个 Playbook 只做一件事(如部署 Web、部署数据库分开),便于维护。
Playbook 提权
在playbook中指定此关键字将覆盖/etc/ansible/ansible.cfg文件中的设置特权升级属性
-
remote_user,指定ssh用户
-
become,启用或禁用特权升级
-
become_method,启用特权升级的方法
-
become_user,特殊升级的帐户
---
- name: Enable intranet serviceshosts: node1remote_user: bqbecome: truebecome_method: sudobecome_user: roottasks:- name: latest version of httpd and firewalld installedyum:name:- httpd- firewalldstate: latest
YAML属性补充
YAML 单行字符串
YAML中的字符串通常不需要放在引号里,即使字符串中包含空格。
字符串也可以用双引号或单引号括起。
this is a string
'this is another string'
"this is yet another a string"
YAML 多行字符串
-
可以使用竖线(I)字符表示,保留字符串中的换行字符。
示例:
--- - name: test stringhosts: node1tasks:- name: test string# 用户显示变量值或者字符串debug:msg: |Example Company123 Main StreetAtlanta, GA 30303
-
也可以使用大于号(>)字符表示换行字符。执行时换行符使用空格代替,并且行内的引导空白将被删除。
示例:
--- - name: test stringhosts: node1tasks:- name: test string# 用户显示变量值或者字符串debug:msg: >This is an exampleof a long string,that will becomea single sentence once folded.
这种方法通常用于将很长的字符串在空格字符处断行,使它们跨占多行来提高可读性。
YAML 字典
一组键值对的集合,又称为映射(mapping)和哈希(hashes)。
以缩进块的形式编写键值对集合,如下方所示:user属性是字典格式,是多个键值对集合。
user:name: bq uid: 1088state: absent
字典也可以使用以花括号括起的内联块格式编写,如下方所示:
user: {name: bq, uid: 1088, state: absent}
大多数情形中应避免内联块格式,其可读性较差。不过,当playbook中包含角色列表时,使用这种语法,更加容易区分play中包含的角色和传递给角色的变量。
某些 playbook 可能使用较旧的简写(shorthand)格式,通过将模块的键值对放在与模块名称相同的行上来定义任务。
示例:
- name: shorhand formuser: name=bq uid=1088 state=absent
普通格式:
- name: shorhand formuser:name: bq uid: 1088state: absent
两者格式总结:
- 通常您应避免简写格式,而使用普通格式。
- 普通格式的行数较多,更容易操作。任务的关键字垂直堆叠,更容易区分。 阅读play时,您的眼睛直接向下扫视,左右运动较少。
- 普通格式是原生的YAML,现代文本编辑器中的语法突出显示工具可以识别,简写形式则不支持。
- 可能会在文档和他人提供的旧playbook中看到这种语法,而且这种语法仍然可以发挥作用。
YAML 列表
一组按次序排列的值,又称为序列(sequence)和数组(array)。
以缩进块的形式编写的键值对集合,如下方所示:
- name: latest version of httpd and firewalld installedyum:name:- httpd- firewalldstate: latest
- name: test html page is installedcopy:content: "Welcome to the example.com intranet!\n"dest: /var/www/html/index.html
以上有两个任务,每个任务都是多个键值对描述。其中yum模块操作的软件包是一个简单的名称列表。
内联格式:
name: [httpd, firewalld]
尽量避免内联格式。