Ansible - 5 - 剧本执行
Ansible 剧本(Playbook)
Ansible Playbook 基础介绍
Playbook 是 ansible 用于配置,部署,和管理被控节点的剧本。
- playbook类似Linux的shell脚本,用于实现和管理大量的、规律的、复杂的操作任务
- playbook方便代码和配置的重用、移植性好,同时也易于管理
Playbook格式
# playbook文件由YMAL语言编写,需遵循yaml格式要求
1. 第一行以 "---" 开始,表明YMAL文件的开始(非playbook强制要求,没有也能通过语法检查)
2. 列表元素以”-”开头,后面带有一个空格,然后元素内容
3. 对象的键与值以":"和一个空格来分隔
4. 相同层次内容必须保持相同的缩进和对齐,否则会报错
5. 在同一行中,“#” 之后的内容表示注释
6. 文件名应该以.yml或.yaml结尾
# 编写完成后可以通过 --syntax-check 子命令检查语法错误
ansible-playbook --syntax-check sample.yaml
Playbook组成
- 一个Playbook可以包括一个或多个Play。
- 一个Play由Host的无序集合与Task的有序列表组成。
- 每一个Task仅由一个模块构成。
# play
- 一个Playbook包括一个或多个Play
- 一个Play由Host的无序集合与Task的有序列表组成
# 目标定义部分:host
- 执行playbook的主机信息
# 变量定义部分:variable
- 执行playbook需要的变量
- 设置的ansible配置变量,例如gather_facts
# 任务定义部分:tasks & module
- 在目标主机执行的任务列表和调用的模块
- Play的主体部分,Task列表中的各任务按次序逐个在Hosts中的指定主机上执行
- 每一个Task仅由一个模块构成
- 建议为每一个Task设置一个Name,便于在运行Playbook时从输出结果辨别task信息
- Ansible的自带模块Command模块和Shell模块无需使用key=value格式,可以直接编写要执行的命令
# 触发器定义部分:handlers
- task执行完成后需要调用的任务,通常结合关键字notify
- 由通知者进行的Notify,如果没有被Notify,则Handlers不会执行,如果被Notify了,则Handlers被执行
- 不管有多少个通知者进行了Notify,等到Play中的所有Task都执行完成之后,Handlers也只会被执行一次
- 实质上也是Task的列表
Ansible Playbook 示例
[root@test01 ansible-test]# pwd
/root/ansible-test
[root@test01 ansible-test]#
[root@test01 ansible-test]# ll
total 4
drwxr-xr-x 2 root root 24 Oct 17 09:47 conf
-rw-r--r-- 1 root root 922 Oct 14 11:24 sample.yaml
[root@test01 ansible-test]#
[root@test01 ansible-test]# tree
.
├── conf
│ └── httpd.conf
└── sample.yaml
1 directory, 2 files
[root@test01 ansible-test]#
[root@test01 ansible-test]# cat conf/httpd.conf
MaxClients {{ maxClients }}
Listen {{ httpd_port }}
[root@test01 ansible-test]#
[root@test01 ansible-test]# cat sample.yaml
- hosts: ta
remote_user: vipxf
gather_facts: no
vars:
- package: vim
tasks:
- name: install vim
yum: name={{ package }} state=latest
- name: install configuration file
become: yes
become_method: su
become_flags: "-"
become_user: root
template: src=/root/ansible-test/conf/httpd.conf dest=/root/test-template.conf
notify:
- check-hostname-date
- name: copy file
copy: src=/root/ansible-test/conf/httpd.conf dest=/home/vipxf/test-copy.conf
handlers:
- name: check-hostname-date
shell: hostname && date
ignore_errors: True
- hosts: ta
remote_user: vipxf
gather_facts: no
become: yes
become_method: su
become_flags: "-"
become_user: root
vars:
- newgroup: testgroup
- newuser: testuser
tasks:
- name: create new group
group: name={{ newgroup }} system=yes
- name: create new user
user: name={{ newuser }} group=testgroup system=yes
[root@test01 ansible-test]#
[root@test01 ansible-test]# cat /etc/ansible/hosts
[ta]
172.20.8.247 ansible_ssh_port=2222 ansible_ssh_user=vipxf ansible_ssh_pass=Anliven09!
[ta:vars]
httpd_port=80 maxClients=10
[root@test01 ansible-test]#
Ansible playbook 示例说明
- hosts: ta # 指定执行指定任务的主机,可以通过一个或多个由冒号分隔主机组
remote_user: vipxf # 指定在远程主机执行任务的用户,也能用在task中
gather_facts: no
vars:
- package: vim
tasks: # 任务列表,在指定主机上按顺序执行各任务
- name: install vim # 每个任务的名称,用于输出执行结果
yum: name={{ package }} state=latest
- name: install configuration file
become: yes
become_method: su
become_flags: "-"
become_user: root
template: src=/root/ansible-test/conf/httpd.conf dest=/root/test-template.conf
notify: # 此任务状态为changed时采取的操作,在handlers中定义
- check-hostname-date
- name: copy file
copy: src=/root/ansible-test/conf/httpd.conf dest=/home/vipxf/test-copy.conf
handlers: # 触发器,在被指定任务通知和所有任务完成的情况下只执行一次,实质上也是按序执行的任务列表
- name: check-hostname-date
shell: hostname && date
ignore_errors: True # 如果命令或脚本的退出码不为零,使用ignore_errors来忽略错误信息
- hosts: ta # playbook文件可以有多个play
remote_user: vipxf
gather_facts: no
become: yes
become_method: su
become_flags: "-"
become_user: root
vars:
- newgroup: testgroup
- newuser: testuser
tasks:
- name: create new group
group: name={{ newgroup }} system=yes
- name: create new user
user: name={{ newuser }} group=testgroup system=yes
相关命令
# Ansible具有幂等性,会自动跳过没有变化的部分
# 建议使用绝对路径来执行playbook的yaml文件
# 语法检查 和 预测试 不能保证结果绝对正确,实际的运行测试是有必要的
# 特别注意yml文件的内容格式(空格、缩进)、特殊字符等,必要时重写重建
--------------------------------------------------------------------------------
## 列出主机列表、任务列表和标签列表
ansible-playbook --list-hosts --list-tasks --list-tags sample.yaml
--------------------------------------------------------------------------------
## 检查内容语法错误
ansible-playbook --syntax-check sample.yaml
## 预测试(不改变目标主机的任何设置)
ansible-playbook --check sample.yaml --ask-become-pass
ansible-playbook -C sample.yaml --ask-become-pass
--------------------------------------------------------------------------------
## 选择playbook开始执行的任务
ansible-playbook sample.yaml --start-at-task="common" # 从名称为common的任务开始执行playbook
## 详细模式下执行playbook,根据提示输入root密码
ansible-playbook -v sample.yaml --ask-become-pass
## verbose mode (-vvv for more, -vvvv to enable connection debugging)
ansible-playbook -vvv sample.yaml --ask-become-pass
返回结果
# 一般以红色、黄色、绿色来表示执行结果
- 绿色:执行成功,未更改目标主机状态
- 黄色:执行成功,对目标主机完成变更,目标主机状态有变化
- 紫色:执行结果出现警告或提示信息
- 红色:执行失败,结果出现异常
# 在playbook执行后,会明确列出任务执行状态的汇总信息
# 任务执行状态包括:ok、changed、unreachable、failed、skipped、rescued、ignored
结果示例
[root@test02 ~]# ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:50:56:a1:07:78 brd ff:ff:ff:ff:ff:ff
inet 172.20.8.247/24 brd 172.20.8.255 scope global noprefixroute ens192
valid_lft forever preferred_lft forever
inet6 fe80::250:56ff:fea1:778/64 scope link
valid_lft forever preferred_lft forever
[root@test02 ~]#
[root@test02 ~]# vim -h | head -n 1
VIM - Vi IMproved 7.4 (2013 Aug 10, compiled Jun 14 2019 10:43:10)
[root@test02 ~]#
[root@test02 ~]# pwd
/root
[root@test02 ~]# ll test-template.conf
-rw-r--r-- 1 root root 38 Oct 17 18:18 test-template.conf
[root@test02 ~]#
[root@test02 ~]# ll /home/vipxf/test-copy.conf
-rw-rw-r-- 1 vipxf vipxf 66 Oct 17 18:18 /home/vipxf/test-copy.conf
[root@test02 ~]#
[root@test02 ~]# cat /etc/group |grep test
testgroup:x:995:
testuser:x:994:
[root@test02 ~]# cat /etc/passwd |grep test
testuser:x:997:995::/home/testuser:/bin/bash
[root@test02 ~]#
Ansible Playbook 条件判断(When)
when语句可以将变量、facts或此前任务的执行结果作为指定task是否执行的前提条件,也可以判断变量是否被定义。
示例-1:when
- hosts: ta
remote_user: vipxf
gather_facts: yes
tasks:
- name: test
shell: echo "test"
when: ansible_os_family == "RedHat" and ansible_distribution_version == "7.8" # 可以使用facts、playbook 或Inventory中定义的变量
- name: test1
shell: echo "step1"
register: result # 将执行结果定义为变量
- name: test2
shell: echo "step2"
when: result.rc == 0 # 基于先前任务的结果来执行
- name: test3
shell: echo "step3"
when: result.stderr != ""
- name: test4
shell: echo "step4"
when: result.changed == "true"
示例-2:结合fail语句
test.sh
[vipxf@test02 ~]$ pwd
/home/vipxf
[vipxf@test02 ~]$
[vipxf@test02 ~]$ ll test.sh
-rw-rw-r-- 1 vipxf vipxf 127 Oct 21 00:11 test.sh
[vipxf@test02 ~]$
[vipxf@test02 ~]$ cat test.sh
#!/bin/bash
if [ "$1" = "Anliven" ];then
echo "Success"
else
echo "Failed"
fi
[vipxf@test02 ~]$
when2.yaml
[root@test01 ansible-test]# cat when2.yaml
- hosts: ta
remote_user: vipxf
gather_facts: yes
vars:
- teststring: Failed
tasks:
- name: test1
shell: sh /home/vipxf/test.sh {{ testvar }}
register: result
- name: test2
shell: echo "rc 0"
when: result.rc == 0 # 基于先前任务的结果来执行
- name: test3
shell: echo "Success"
when: '"Success" in result.stdout' # 对stdout结果进行判断
- name: test4
fail: msg="Check Failed" # 任务报错并抛出msg信息
when: result.stdout == (( teststring )) # 在when使用playbook中定义的变量
[root@test01 ansible-test]#
[root@test01 ansible-test]# ansible-playbook -v when2.yaml -e testvar=Anliven
Using /etc/ansible/ansible.cfg as config file
PLAY [ta] *********************************************************************************************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************************************************************
ok: [172.20.8.247]
TASK [test1] ******************************************************************************************************************************************************************************************
changed: [172.20.8.247] => {"changed": true, "cmd": "sh /home/vipxf/test.sh Anliven", "delta": "0:00:00.003638", "end": "2022-10-21 18:05:00.273596", "rc": 0, "start": "2022-10-21 18:05:00.269958", "stderr": "", "stderr_lines": [], "stdout": "Success", "stdout_lines": ["Success"]}
TASK [test2] ******************************************************************************************************************************************************************************************
changed: [172.20.8.247] => {"changed": true, "cmd": "echo \"rc 0\"", "delta": "0:00:00.002893", "end": "2022-10-21 18:05:00.722340", "rc": 0, "start": "2022-10-21 18:05:00.719447", "stderr": "", "stderr_lines": [], "stdout": "rc 0", "stdout_lines": ["rc 0"]}
TASK [test3] ******************************************************************************************************************************************************************************************
changed: [172.20.8.247] => {"changed": true, "cmd": "echo \"Success\"", "delta": "0:00:00.002632", "end": "2022-10-21 18:05:01.172659", "rc": 0, "start": "2022-10-21 18:05:01.170027", "stderr": "", "stderr_lines": [], "stdout": "Success", "stdout_lines": ["Success"]}
TASK [test4] ******************************************************************************************************************************************************************************************
skipping: [172.20.8.247] => {"changed": false, "skip_reason": "Conditional result was False"}
PLAY RECAP ********************************************************************************************************************************************************************************************
172.20.8.247 : ok=4 changed=3 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
[root@test01 ansible-test]#
[root@test01 ansible-test]# ansible-playbook -v when2.yaml -e testvar="test"
Using /etc/ansible/ansible.cfg as config file
PLAY [ta] *********************************************************************************************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************************************************************
ok: [172.20.8.247]
TASK [test1] ******************************************************************************************************************************************************************************************
changed: [172.20.8.247] => {"changed": true, "cmd": "sh /home/vipxf/test.sh test", "delta": "0:00:00.003612", "end": "2022-10-21 18:06:49.907767", "rc": 0, "start": "2022-10-21 18:06:49.904155", "stderr": "", "stderr_lines": [], "stdout": "Failed", "stdout_lines": ["Failed"]}
TASK [test2] ******************************************************************************************************************************************************************************************
changed: [172.20.8.247] => {"changed": true, "cmd": "echo \"rc 0\"", "delta": "0:00:00.002721", "end": "2022-10-21 18:06:50.338001", "rc": 0, "start": "2022-10-21 18:06:50.335280", "stderr": "", "stderr_lines": [], "stdout": "rc 0", "stdout_lines": ["rc 0"]}
TASK [test3] ******************************************************************************************************************************************************************************************
skipping: [172.20.8.247] => {"changed": false, "skip_reason": "Conditional result was False"}
TASK [test4] ******************************************************************************************************************************************************************************************
fatal: [172.20.8.247]: FAILED! => {"changed": false, "msg": "Check Failed"}
PLAY RECAP ********************************************************************************************************************************************************************************************
172.20.8.247 : ok=3 changed=2 unreachable=0 failed=1 skipped=1 rescued=0 ignored=0
[root@test01 ansible-test]#
changed_when 与 failed_when
# changed_when
# 在条件成立时,将对应任务的执行状态设置为changed
# 先执行task,并对task返回的值进行判断,当满足changed_when指定的条件时说明是执行成功的
# 默认情况下执行命令完成的主机状态都为changed
- name: all host run this task
shell: hostname
register: info
changed_when: '"webserver01" in info.stdout' # 输出包含某个特定字符才能将主机状态设为changed
# failed_when
# 在条件成立时,将对应任务的执行状态设置为失败
# 当执行失败后会将信息存在register的stderr中,通过判断指定的字符是否在stderr中来确定是否真的失败
# 其实是ansible的一种错误处理机制:由fail模块组合了when条件语句的效果
- name: this command prints FAILED when it fails
command: echo "FAILED"
register: command_result
failed_when: "'FAILED' in command_result.stdout"
Ansible Playbook 循环迭代(with_items)
用来在playbook中实现循环迭代的功能。
支持元素列表、文件名(with_fileglob)、复合(with_together)、步进(with_sequence)、随机(with_random_choice)、until等多种类型循环。
- name: test with_items
hosts: ta
remote_user: vipxf
gather_facts: yes
tasks:
- name: test1
shell: echo {{ item }} # 通过引用item变量来迭代
with_items:
- one
- two
- three
- name: test2
shell: echo {{ item.name }} with {{ item.value }} # 通过字典来迭代
with_items:
- { name: 'one', value: '111'}
- { name: 'two', value: '222'}
- { name: 'three', value: '333'}
- name: test3
copy: src={{ item }} dest=~/ mode=664 owner=vipxf group=vipxf # 把文件名作为变量循环
with_fileglob:
- /playbook/files/*
Ansible Playbook 角色(Roles)
- 角色(roles)用于层次性、结构化地组织playbook
- 通过规范的目录存储结构将变量、文件、任务、模板以及触发器等放置于单独目录
- 通过独立的role目录将不同的playbook任务单一化,从而实现高效的代码复用
- 在playbook中调用roles,根据层次型结构自动转载roles的变量文件、tasks以及handlers等要素信息
- 一般用于基于主机构建服务和进程的场景
- 可以在 /etc/ansible/ansible.cfg 中设置 roles_path 来设置roles文件路径
创建role
1. 创建 roles 名称的目录
2. 在 roles 目录中分别创建各角色命名的目录,如webserver等
3. 在每个角色命名的目录中分别创建defaults、files、handlers、meta、tasks、templates和vars等目录;如果为空目录则不被引用
4. 在roles的整体编排文件playbook中调用各角色
目录结构说明
site.yml # playbook文件,roles的整体编排文件
roles/ # 定义各角色的总目录
common/ # 角色role模块名,在playbook中需要调用时使用的名称
default/ # 默认变量文件目录(定义此角色的默认变量),至少包含一个main.yml文件,在main.yml文件中可以用include指令将其他yml文件包含进来
files/ # 存放由copy或script等模块调用的文件
handlers/ # 触发器文件目录,至少包含一个main.yml文件,在main.yml文件中可以用include指令将其他yml文件包含进来
meta/ # 此角色的元数据(特殊设定及其依赖关系),至少包含一个main.yml文件,在main.yml文件中可以用include指令将其他yml文件包含进来
tasks/ # 任务文件目录,至少包含一个main.yml文件,在main.yml文件中可以用include指令将其他yml文件包含进来
templates/ # 模板文件目录,template模块会自动在此目录中寻找
vars/ # 变量文件目录,至少包含一个main.yml文件,在main.yml文件中可以用include指令将其他yml文件包含进来
webserver/ # 角色 webserver 目录
default/
files/
handlers/
meta/
tasks/
templates/
vars/
调用方式
在playbook中可以通过关键字 roles 来调用角色role。
- hosts: webserver
remote_user: anliven
roles: # 使用关键字roles调用角色role
- common # 调用role
- webserver # 调研多个role
- hosts: webserver
remote_user: anliven
roles:
- common
- { role: foo_app_instance, dir:'/opt/a',port:5000} # 可以向roles传递参数
- { role: foo_app_instance, dir:'/opt/b',port:5001}
- hosts:webserver
remote_user: anliven
roles:
- { role: some_role, when: "ansible_so_family == 'RedHat" } # 指定调用roles的条件
Ansible Playbook 标签(Tags)
- 通过标签(tags)可以选择运行或跳过playbook中的指定任务
- 在playbook为任务定义一个"标签",在通过ansible-playbook命令执行此playbook时使用 --tags 或 --skip-tags选项选择运行或跳过这个任务
- 可以使用系统特殊标签,也可以自定义标签
- 可以为同一个任务设定多个标签,也可以为不同的任务设定相同的标签
语法格式
# 格式1
tags:
- testtag
- t2
# 格式2
tags: testtag,t2
# 格式3
tags: ['testtag','t2']
特殊标签
always 除非--skip-tags选项指定,否则 always 标签的task会一直执行
never 除非--tags选项指定,否则 never 标签的task都不会执行
tagged 不包括never的所有标签
untagged 所有无标签和always标签
all 包括非never标签和无标签
选项
# 如果执行 ansible-playbook 时不指定标签,则会执行所有非 never 标签的任务
--tags "tag1,tag2..." 执行指定标签和always标签的tasks
--tags always 只执行always标签的tasks
--tags all 执行所有非never标签和无标签的tasks
--tags never 执行always和never标签的tasks
--tags tagged 执行所有标签的tasks,但不包括never标签的tasks
--tags untagged 执行所有无标签和always标签的tasks
--skip-tags "tag1,tag2..." 跳过指定标签的tasks
--list-tags 查看playbook中哪些tags会被执行
tags示例
tasks:
- name: install package
yum: name={{ packagename }} state=latest
tags:
- always
- name: copy configuration file
copy: src=/root/conf/httpd.conf dest=/etc/httpd/conf/httpd.conf
tags: conf,http
命令示例
ansible-playbook nginx_tags.yaml --tags "testtag,t2"
Ansible Playbook 调试(Debug)
- Print statements during execution
- 在Ansible Playbook中常使用debug模块,可以在Playbook执行过程打印调试信息
- 结合when条件语句一起使用时,可以调试特定条件下的执行过程
注意:在setup模块中查询出来的变量,直接可以在debug中直接作为变量引用。
# msg
输出自定义信息,如果不指定或不写msg的话,默认也会输出“null”
# var
- 指定要打印的变量名,与msg参数互斥,二者只能有一个
- var参数中的变量不需要使用{{}}表达式,而msg中需要
# verbosity
- debug的调试级别,默认0是全部显示,级别调整到3是忽略内容不显示
- 在命令中使用-vvv参数,可以在设置为3情况下仍然显示debug内容
示例
tasks:
- name: Host run this task
debug: 'msg="{{ ansible_fqdn }} and {{ ansible_default_ipv4.address }}"' # 打印必要信息
when: ansible_memtotal_mb < 500 and ansible_processor_cores == 2 # 结合when使用
- name: all host run this task
shell: hostname
register: info
- name: Hostname is webserver01 Machie run this task
debug: 'msg="{{ ansible_fqdn }}"'
when: info['stdout'].startswith('Success')
- name: Show debug info
debug: var=info verbosity=1 # 打印var变量信息,调试级别为1
全文跳转链接
Ansible系列全文地址 :https://www.cnblogs.com/anliven/p/16859401.html
行动是绝望的解药!
欢迎转载和引用,但请在明显处保留原文链接和原作者信息!
本博客内容多为个人工作与学习的记录,少数内容来自于网络并略有修改,已尽力标明原文链接和转载说明。如有冒犯,即刻删除!
以所舍,求所得,有所获,方所成。