Playbooks是一种简单的配置管理系统与多机器部署系统的基础.非常适合于复杂应用的部署。可用于声明配置,可以编排有序的执行过程,甚至于做到在多组机器间,来回有序的执行特别指定的步骤.并且可以同步或异步的发起任务。
Playbook的组成结构:
Inventory
Modules
Ad Hoc Commands
Playbooks
Tasks 任务,即调用模块完成的某操作
Variables 变量
Templates 模板
Handlers 处理器,由某事件触发执行的操作
Roles 角色
playbook 由一个或多个 ‘plays’ 组成.它的内容是一个以 ‘plays’ 为元素的列表。每个 plays 定义了一组在特定主机或主机组上执行的任务。每个任务(task)是一个独立的操作,例如安装软件包、复制文件、运行命令等。
一个Playbook的示例:
---
- hosts: webservers
vars:
http_port: 80
max_clients: 200
remote_user: root
tasks:
- name: ensure apache is at the latest version
yum: pkg=httpd state=latest
- name: write the apache config file
template: src=/srv/httpd.j2 dest=/etc/httpd.conf
notify:
- restart apache
- name: ensure apache is running
service: name=httpd state=started
handlers:
- name: restart apache
service: name=httpd state=restarted
hosts和users:Playbook中的每一个play的目的都是为了让某个或某些主机以某个指定的用户身份执行任务。hosts用于指定要执行指定任务的主机列表,其可以是一个或多个由冒号分隔的主机组;remote_user则用于指定远程主机上的执行任务的用户,如上例中的:
- hosts: websrvs
remote_user: root
不过,remote_user也可以用于各task中。也可以通过指定其通过sudo的方式在远程主机上执行任务,其可用于play全局或某任务;此外,甚至可以在sudo时使用sudo_user指定sudo时切换的用户。
- hosts: websrvs
remote_user: testuser
tasks:
- name: test connection
ping:
remote_user:testuser
sudo:yes
基本结构:
- hosts:
plays的主体部分是task list。列表中的各任务按次序逐个在hosts中指定的所有主机上执行,即在所有主机上完成第一个任务后再开始第二个任务,在自上而下运行某playbook时,如果中途发生错误,所有已执行任务都可能回滚,因此,在更正playbook后重新执行一次即可。
task的目的是使用指定的参数执行模块,而在模块参数中可以使用变量。
每个task都应该有其name,是任务的标识(相当于ID),用于playbook的执行结果输出,建议其内容尽可能清晰描述任务。
定义task可以使用:action: module option 或 module: options 的格式,一般使用后者
在众多模块中,只有command和shell模块仅需要给定一个列表而无需使用“key=value”格式,如:
tasks:
- name: disable selinux
command:/sbin/setenforce 0
如果命令或脚本的退出码不为零,可以使用如下方式替代:
tasks:
- name: run this command and ignore the result
shell:/usr/bin/somecommand || /bin/true
或者使用ignore_errors来忽略错误信息:
tasks:
- name: run this command and ignore the result
shell: /usr/bin/somecommand
ignore_errors:True
提供这个功能的目的是有些命令运行失败,不影响下面的任务执行,我们希望继续下面的任务执行,此时就强制返回True结果给ansible或告诉ansible忽略返回的错误值,这样就不会影响下面任务的执行。
实操:
myplaybook1.yml:

运行playbook,需要使用ansible-playbook命令:

根据运行的结果,可以看到,ansible-playbook自动添加了一个Gathering Facts任务,收集远程主机的信息。
注意一下运行的顺序,先执行第一折戏(PLAY [websrvs]),即在第一个主机清单上自上而下运行各个任务,全部执行完,然后执行第二折戏(PLAY [dbserver]),也就是说这个plays是以主机清单(即hosts)为分隔标志。
修改一下playbook:

再运行:

hosts的值,即给出的主机清单,必须在/etc/ansible/hosts文件中定义,否则就会报告不能匹配到主机,忽略在此主机上运行任务。
关于任务中途发生错误,即返回值不为0,测试如下
修改myplaybook.yml

/bin/aa不存在,即返回结果为出错了。运行结果如下:

可以看到,第一个任务中途出错,后一个任务也没有执行。修改myplaybook.yml,

猜想:如果command模块执行出现错误,那么返回值将使用/bin/true的返回值,即返回的结果是成功,但运行结果如下:

最终形成的cmd与想象中不一样,或运算符||成了字符串,将command模块改为shell模块:




handlers,在发生改变时执行的操作,用于关注的资源发生变化时采取的一定操作。
module 具有”幂等”性,所以当远端系统被人改动时,可以重放 playbooks 达到恢复的目的. playbooks 本身可以识别这种改动,并且有一个基本的 event system(事件系统),可以响应这种改动。
notify:(当发生改动时)’notify’ actions 会在 playbook 的每一个 task 结束时被触发,而且即使有多个不同的 task 通知改动的发生, ‘notify’ actions 只会被触发一次。
handlertest.yml:


install configuration file任务,因为文件httpd.conf没有改变,所以不会触发handlers,修改httpd.conf中的监听端口为8080,再次运行这个playbook:

可以看到,这一次handlers起作用了,因为任务install configuration file发生了改变,即黄色了,所以触发了notify,notify调用handlers中的restart httpd任务。
handlers跟tasks一样,只要将tasks改为handlers就行,只不过是其运行的时间不同,tasks在没有运行错误的情况下,每一个任务都会按顺序执行到,而handlers中的每个任务,是通过notify触发才能运行,所谓触发,就是任务中定义了notify,并且此任务返回黄色,即状态改变了就触发。
变量:
在playbook的yml文件中定义变量,使用vars关键字:

vars定义的变量,可以在后续使用,变量的使用方法是双花括号{{ 变量名 }},其实这是jinjia2的模版语法,前面学习python时学过,{{ }}是引用变量,{% %}是执行的语句。
那么,定义的变量的作用范围是什么呢?看下面的playbook脚本:

运行后显示:

由此,可以看出,此时的变量的作用范围是play范围内的,其实,每个play可以看做一个函数,此时vars定义的变量,相当于函数的内部变量(局部变量),只能在函数内部使用,所以在下面的play中使用时,会报出没有定义变量的错误。通过playbook的yaml脚本文件结构,可以看出,在这个文件中只能定义play范围的变量,相当于只能定义局部变量。
Facts获取的信息中的变量:
通过上面的运行结果,可以看到,每个play运行定义的各个任务前,都运行了一个ansible自定义的Gathering Facts任务,这个任务应该就是ansible websrvs -m setup,其结果如下:

返回的键值对,其键名就是一个变量名,可以不在play中定义就直接使用,这是运行Gathering Facts任务生成的变量。使用其中的ansible_default_ipv4变量:



可以看到,不同的远程节点上都生成了/tmp/varstest.txt,并且其内容是各个节点对应的ipv4,这说明,对于Facts中的返回的信息,会对应每一台host生成对应的变量,并赋予各自对应的信息,即这个Facts变量是一个主机变量,在对应的主机(远程节点)运行task时,都可以使用这些变量,并且这些变量相对于主机来说是局部变量,即只能在这台主机使用,但是每台主机都会有相同的Facts局部变量。对于{{ ansible_default_ipv4 }},可以看到结果是一个字典,要访问其中的一个键时,可以使用{{ ansible_default_ipv4.macaddress }},使用点号。
Inventory变量:即在/etc/ansible/hosts中定义的变量

主机变量:这种定义的变量,也是主机变量,只不过是我们自定义的,其作用域仅限于对应的主机,类似Facts变量。如上面的hostvar,虽然每个主机都定义了,但是其内容不同,而且,本质上是不同的变量,就像hostvar1,只能用于217.130主机。

组变量:在hosts中定义:[组名:vars]


上面的playbook中只有websrvs才能使用ntp_server,下面的dbserver会出错

把一个组作为另一个组的成员:[组名:children]
[atlanta]
host1
host2
[raleigh]
host2
host3
[southeast:children]
atlanta
raleigh
[southeast:vars]
some_server=foo.southeast.example.com
halon_system_timeout=30
self_destruct_countdown=60
escape_pods=2
[usa:children]
southeast
northeast
southwest
northwest
参数(参数变量):Inventory 参数,即ansible自定义的一组变量,可以控制 ansible 与远程主机的交互方式:
ansible_ssh_host
将要连接的远程主机名.与你想要设定的主机的别名不同的话,可通过此变量设.
ansible_ssh_port
ssh端口号.如果不是默认的端口号,通过此变量设置.
ansible_ssh_user
默认的 ssh 用户名
ansible_ssh_pass
ssh 密码(这种方式并不安全,我们强烈建议使用 --ask-pass 或 SSH 密钥)
ansible_sudo_pass
sudo 密码(这种方式并不安全,我们强烈建议使用 --ask-sudo-pass)
ansible_sudo_exe (new in version 1.8)
sudo 命令路径(适用于1.8及以上版本)
ansible_connection
与主机的连接类型.比如:local, ssh 或者 paramiko. Ansible 1.2 以前默认使用 paramiko.1.2 以后默认使用 'smart','smart' 方式会根据是否支持 ControlPersist, 来判断'ssh' 方式是否可行.
ansible_ssh_private_key_file
ssh 使用的私钥文件.适用于有多个密钥,而你不想使用 SSH 代理的情况.
ansible_shell_type
目标系统的shell类型.默认情况下,命令的执行使用 'sh' 语法,可设置为 'csh' 或 'fish'.
ansible_python_interpreter
目标主机的 python 路径.适用于的情况: 系统中有多个 Python, 或者命令路径不是"/usr/bin/python",比如 \*BSD, 或者 /usr/bin/python
不是 2.X 版本的 Python.我们不使用 "/usr/bin/env" 机制,因为这要求远程用户的路径设置正确,且要求 "python" 可执行程序名不可为 python以外的名字(实际有可能名为python26).
条件测试:when

当返回的Facts变量ansible_nodename为node2时,执行simulate install package任务。
迭代:使用with_items
- name: add users
user: name={{ item }} state=present groups=wheel
with_items:
- testuser1
- testuser2
user模块添加用户,会重复执行,即迭代,将with_items中的所有用户添加。
还可以使用如下格式:
- name: add users
user: name={{ item.name }} state=present groups={{ item.groups }}
with_items:
- { name:'testuser1',groups:'wheel'}
- { name:'testuser2',groups:'root'}
Template:模版元素
主要是配置文件,如多台主机运行httpd服务,而对不同主机,其httpd的配置有所不同,如监听端口不一样,可以将配置文件做成模板,如将httpd.conf中的Listen 80改成:
Listen {{ httpd_port }}
然后在hosts中,定义主机变量,如192.168.217.130 httpd_port=8080


此时再次运行ansible_playbook,在websrvs组中的各主机的/tmp下生成httpd.conf,并且其Listen的值是hosts文件中传入的主机变量httpd_port的值,各主机此值随传入值的不同而不同。
模板使用的是jinjia2模板语言,想详细了解,学习jinjia2就可以了。
Tags:标签,标记一个任务,使用场景是在只有某个或某些任务相关信息改动时,此时不需要执行全部任务,可以使用Tags指定执行特定的任务。
如:

运行如下命令:

可以看到只有tags标记的install configuration file任务执行了,同时触发handler任务,其他自定义的任务都没有运行,提高效率。
特殊tags:always,表示始终要运行,如上面的yaml,将simulate install package增加标签:
tags:
- always
则在运行ansible_playbook时,无论是否指定这个标签,其都会运行:

Roles:角色,用于层次性、结构化的组织playbook。
在Ansible中,roles 是一种组织和管理Playbooks的方式。Roles允许用户将相关的代码、文件、配置、任务、变量、模块、处理器等分组到一个单独的目录中或文件中,使得Playbooks更加模块化、可重用和易于维护。每个role通常代表一个特定的功能或任务集合,例如安装一个软件包、配置一个服务或管理一个应用。
roles能够根据层次结构自动装载变量、文件、tasks以及handlers等。要使用roles只需在playbook中使用include指令即可。基本上,使用 include 语句引用 task 文件的方法,可允许你将一个配置策略分解到更小的文件中。使用 include 语句引用 tasks 是将 tasks 从其他文件拉取过来。因为 handlers 也是 tasks,所以你也可以使用 include 语句去引用 handlers 文件,类似的也可以引用变量、标签、逻辑语句(如测试)等等。
Roles 的概念的想法就是通过 include 包含文件并将它们组合在一起,组织成一个简洁、可重用的抽象对象。这种方式可使你将注意力更多地放在大局上,只有在需要时才去深入了解细节。
Roles 基于一个已知的文件结构,去自动的加载某些 vars_files,tasks 以及 handlers。基于 roles 对内容进行分组,使得我们可以容易地与其他用户分享 roles 。
一个项目的结构如下:
site.yml
webservers.yml
fooservers.yml
roles/
common/
files/
templates/
tasks/
handlers/
vars/
defaults/
meta/
webservers/
files/
templates/
tasks/
handlers/
vars/
defaults/
meta/
创建role的步骤:
1)创建以roles命名的目录;
2)在roles目录中分别创建以各角色名称命名的目录,如webservers等
3)在每个角色命名的目录中分别创建files、templates、tasks、handlers、vars、meta、defaults子目录;用不到的目录可以创建为空目录,也可以不创建
4)在playbook文件中,调用各角色
roles中各目录中可用的文件:
- tasks目录:至少应包含一个名为main.yml的文件,其定义了此角色的任务列表;此文件可以使用include包含其他的位于此目录中的task文件;
- files目录:存放由copy或script等模块调用的文件;
- templates目录:template模块会自动在此目录中寻找jinjia2模板文件;
- handlers目录:此目录中应当包含一个main.yml文件,用于定义此角色用到的各handler;在handler中使用include包含的其他的handler文件也应该位于此目录中;
- vars目录:应当包含一个main.yml文件,用于定义此角色用到的变量;
- meta目录:应当包含一个main.yml文件,用于定义此角色的特殊设定及其依赖关系;
- defaults目录:为当前角色设定默认变量时使用此目录,应当包含一个main.yml文件;
一个 playbook 如下:
---
- hosts: webservers
roles:
- common
- webservers
这个 playbook 为一个角色 ‘x’ 指定了如下的行为:
如果 roles/x/tasks/main.yml 存在, 其中列出的 tasks 将被添加到 play 中
如果 roles/x/handlers/main.yml 存在, 其中列出的 handlers 将被添加到 play 中
如果 roles/x/vars/main.yml 存在, 其中列出的 variables 将被添加到 play 中
如果 roles/x/meta/main.yml 存在, 其中列出的 “角色依赖” 将被添加到 roles 列表中 (1.3 and later)
所有 copy tasks 可以引用 roles/x/files/ 中的文件,不需要指明文件的路径。
所有 script tasks 可以引用 roles/x/files/ 中的脚本,不需要指明文件的路径。
所有 template tasks 可以引用 roles/x/templates/ 中的文件,不需要指明文件的路径。
所有 include tasks 可以引用 roles/x/tasks/ 中的文件,不需要指明文件的路径。
实操:
1、创建目录结构:


2、对于webrole角色,提供需要拷贝的配置文件http.conf到webrole/files目录中,对于dbrole角色,提供需要拷贝的配置文件db.conf到dbrole/files目录中:

这里只是模拟
3、创建任务文件,在webrole及dbrole的子目录tasks下创建main.yml:
webrole:

这里触发器触发一个handler:restart httpd,所以在webrole/handlers下创建main.yml:

在webrole/vars下创建变量文件main.yml:

4、在myplaybook目录下(即与roles平级)创建site.yml,定义自己的playbook:

5、执行:

运行顺利完成,角色应用成功。
6、添加dbrole角色的任务,在roles/dbrole/tasks目录下创建main.yml:

在site.yml增加角色:

再次运行:

注意上面定义task时,src的值都是使用的相对目录,ansible会自动查找到正确的对应位置。
如果再定义一个plays,对其他主机要执行相同的任务时,可以直接加角色,而不需要重复写任务,实现重复使用的目的。
可以使用参数化的 roles,这种方式通过添加变量来实现,比如:
- hosts: webservers
roles:
- common
- { role: foo_app_instance, dir: '/opt/a', port: 5000 }
- { role: foo_app_instance, dir: '/opt/b', port: 5001 }
当一些事情不需要频繁去做时,你也可以为 roles 设置触发条件,像这样:
- hosts: webservers
roles:
- { role: some_role, when: "ansible_os_family == 'RedHat'" }
它的工作方式是:将条件子句应用到 role 中的每一个 task 上。
如果希望给 roles 分配指定的 tags。比如:
- hosts: webservers
roles:
- { role: foo, tags: ["bar", "baz"] }
如果 play 仍然包含有 ‘tasks’ section,这些 tasks 将在所有 roles 应用完成之后才被执行。
如果你希望定义一些 tasks,让它们在 roles 之前以及之后执行,你可以这样做:
- hosts: webservers
pre_tasks:
- shell: echo 'hello'
roles:
- { role: some_role }
tasks:
- shell: echo 'still busy'
post_tasks:
- shell: echo 'goodbye'
个人感悟:学过Python的Django,你会发现这里的ansible-playbook非常类似一个Django项目,有固定的目录格式,有一些固定的文件,ansible-playbook感觉就是对Python语言的一种扩展,使用配置文件(即yaml)来扩展实现一些功能。

2258

被折叠的 条评论
为什么被折叠?



