一、Playbook的介绍
Playbook是Ansible的配置,部署,编排语言。他们可以被描述为一个需要希望远程主机执行命令的方案,或者一组IT程序运行的命令集合。
当执行一些简单的改动时ansible命令是非常有用的,然而它真的作用在于它的脚本能力。当对一台机器做环境初始化的时候往往需要不止做一件事情,这时使用playbook会更加适合。通过playbook你可以一次在多台机器执行多个指令。通过这种预先设计的配置保持了机器的配置统一,并很简单的执行日常任务。
Playbook还开创了很多特性,它可以允许你传输某个命令的状态到后面的指令,如你可以从一台机器的文件中抓取内容并附为变量,然后在另一台机器中使用,这使得你可以实现一些复杂的部署机制,这是ansible命令无法实现的。
二、YAML介绍
Ansible使用标准的YAML解析器,使用YAML文件语法即可书写playbook。
YAML是一个可读性高的用来表达资料序列的格式,YAML参考了其他多种语言,包括:XML、C语言、Python、Perl以及电子邮件格式RFC2822等。Clark Evans在2001首次发表了这种语言。
Playbook组成结构
Inventory #定义管理主机(清单文件)
Modules #定义模块
Ad Hot Commands
Playbooks
Variables #变量元素,可传递给Tasks/Templates使用;
Tasks #任务元素,即调用模块完成任务;
Templates #模板元素,可根据变量动态生成配置文件;
Hadlers #处理器元素,通常指在某条件满足时触发的操作;
Roles #角色元素
执行一个Playbook
使用Playbook时通过ansible-playbook命令使用,它的参数和ansible命令类似,如参数-k(–ask-pass) 和-K (–ask-sudo) 来询问ssh密码和sudo密码,-u指定用户,这些指令也可以通过规定的单元写在playbook里。ansible-playbook的简单使用方法:
ansible-playbook /etc/ansible/roles/sites.yml
也可以并行执行playbook,这里的示例是并行的运行playbook,并行的级别是10个进程:
ansible-playbook /etc/ansible/roles/sites.yml -f 10
playbook的写法例子:
分别讲解playbook语言的多个特性:
二、Playbook基础组件
主机(hosts)和用户(users)
Playbook中的每一个play的目的都是为了让某个或某些主机以某个指定的用户的身份执行任务,hosts用于指定要执行任务的主机,其可以是一个或者多个有冒号分割主机组,这和前面的ansible命令提到的hosts使用一样的语法,remote_user则用于指定远程主机上的执行任务的用户,如上面实例中:
不过,remote_user也可以用于task中:
- hosts: web
remote_user: root
tasks:
- name: test connection
ping:
remote_user: yourname
也可以通过指定其通过sudo的方式远程主机上执行任务,其可用于play全局或某task,此外,甚至可以在sudo时使用sudo_user指定sudo时切换的用户。
- hosts: web
remote_user: root
sudo:yes
sudo_user:yourname
如果需要在使用sudo时指定密码,可在运行ansible-playbook命令时加上--ask-sudo-pass(-K).如果使用sudo时,playbook疑似被挂起,可能是在sudo prompt处被卡主了,这时可执行Control-C杀死卡住的任务,重新运行一次。
Tasks列表
play的主体部分是task列表,task列表中的各任务按次序逐个在hosts中指定的主机上执行,即在所有主机上完成第一个任务后再开始第二个任务,在运行playbook时(从上到下执行),如果一个hosts执行task失败,这个host将会从整个playbook的rotation中移除,如果发生执行失败的情况,请修正playbook中的错误,然后重新执行即可。
Task的目的是使用指定的参数执行模块,而在模块参数中可以使用变量,模块执行是幂等的,这意味着多次执行是安全的,因为其结果一致。
对于command 模块和shell 模块,重复执行playbook,实际上是重复运行同样的命令。如果执行的命令类似于‘chmod’或者‘setsebool’这种命令,这没有任何问题,也可以使用一个叫做‘creates’的flag使得这两个module变得具有‘幂等’特性(不是必要的。)
每一个task必须有一个名称name,这样在运行playbook时,从其输出的任务信息中可以很好的辨别出是属于哪一个task的,如果没有定义name,‘action’的值将会用作输出信息中的标记特定的task。
定义一个task,以前有一种格式:‘action:module options’ (可能在一些老的playbook中还能见到),现在推荐使用常见的格式:“module:options”,本文档使用的就是这种格式。
- hosts: web
vars: #定义变量
http_port: 80
max_client: 200 #定义变量是使用两个花括号括起来,中间最好有空格,{{ http_port }}
remote_user: root
task:
- name: ensure apache is at the latest version
yum: name=httpd state=latest
意思是:针对web组执行一个task使用root用户执行,而这个task任务是“ensure apache is at the latest version”,然后调用yum模块进行httpd的检测,这里的模块参数的含义都跟前面说的常用模块介绍时是一样的,详情查看前面的常用模块介绍
在众多模块中,只有command和shell模块仅需要给定一个列表无需使用key-value格式,例如:
tasks:
- name: disable selinux
command: /sbin/setenforce 0
在使用command和shell模块时,我们需要关心返回码信息,如果有一条命令,它的成功执行的返回码不是0(ansible会终止运行),我们可以使用如下方式替代,强制返回成功:
tasks:
- name: run this command and ignore the result
shell: /usr/sbin/somecommand || /bin/true
或者使用ignore_error来忽略错误信息:
tasks:
- name: run this command and ignore the result
shell: /usr/bin/somecommand
ignore_errors: True
变量(variables)和模板(template)
在Playbook中可以直接定义变量(当然前面我们也在inventory中添加变量),如下所示:
- hosts: web
vars:
- package:httpd
其中vars是固定格式,而package就是变量名,httpd就是变量值。
定义变量名的时候注意一些规范,变量名可以是字母、下划线、数字,但必须是以字母开头,变量名包含中线、空格、点和以数字开头的变量名都是不规范的。
对于定义好的变量可以在task或者template中使用,但一般playbook中定义的变量都是给模板使用的,Ansible使用jijia2模板框架,这个后面讲,现在只需要知道变量可以在task和template中使用即可,如下在task中调用变量:
tasks:
- name: ensure apache is at the lastest version
yum: name={{ package }} state=latest
在task或template中引用变量都是使用双花括号,中间引入变量名即可,如:{{ VAR_NAME }}.
另外说明,在playbook中调用的变量不仅仅是在play中自定义的变量,也可以是ansible中所定义的所有变量,比如说在ansible中有一个setup模块,就是用来获取客户端所有的信息,如内存、CPU、IP、主机名、磁盘等信息,这些信息都是key-value格式的,每当我们执行playbook时首先会需要执行setup模块,也就意味着task或template中引用setup模块输出的变量。如:ansible_all_ipv4_addresses都可以在task或者template使用{{}}去引用。
引用变量的方法除了上述的方式外,还可以在hosts中定义变量,此外还可以在命令行使用ansible-playbook -e 指定变量 playbook.yml,例如:
ansible-playbook -e http_port=80 ceshi.yml
最后可以在roles中定义变量
Handlers和notify
Handlers其实挺好理解的就是发生改变时执行提前定义好的操作。
上面我们提到过,模块具有幂等性,所以当远端系统被人改动时,可以重放playbook达到恢复的目的,playbook本身可以识别这种改动,并且有一个基本的event system(系统事件),可以响应这种改动。
(当发生改动时)'notify' 动作会在playbook的每一个task结束时被触发,而且即使有多个不同的task通知改动的发生'notify' 动作只会触发一次。
这里有一个例子,当一个文件的内容被改动时,重启两个services:
- name: write the apache config file
copy: src=/srv/httpd.conf dest=/etc/httpd/conf/httpd.conf
notify:
- restart httpd
"notify"下列出的即是handlers。
handlers也是一些task的列表,通过名字来引用,它们和一般的task并没有什么区别,Handlers是由通知者进行notify,如果没有被notify,handlers不会执行,不管有多少个通知者进行了notify,等到play中的所有task执行完成之后,handlers也只会被执行一次。
这里是一个handlers的示例:
handlers:
- name: restart apache #这里和notify保持一致
service: name=httpd state=restarted
下面给一个安装服务然后使用Handlers的例子:
- hosts:web
remote_user:root
task:
- name: ensure apache is at the latest version
yum: name=httpd state=latest
- name: write the apache config file
copy: src=/srv/httpd.conf dest=/etc/httpd/conf/httpd.conf
- name: ensure apache is running
service: name=httpd state=started
这个play就做了三个task,第一是检测httpd是否是最新版,如果不是就进行安装,第二就是从服务器copy一份定义好的配置文件到目标主机;第三就是启动httpd服务器。
首先需要在ansible服务器上提供一个/srv/httpd.conf配置文件,不然执行时就会报错,然后使用ansible-playbook执行。
ansible-playbook sites.yml
如果修改了配置文件,是不是需要reload或restart后你的配置文件才能生效。但是上面的配置就算你改完 配置文件再次执行playbook,配置文件虽然过去了,但是ansible幂等性,配置文件是无法生效的,而handlers就是为了解决此类问题而生的,我们可以给copy文件的那个task定义一个handlers,一旦由配置文件改动就调用提前定义的handlers进行处理,如下:
- hosts: test
remote_user: root
tasks:
- name: ensure apache is at the latest version
yum: name=httpd state=latest
- name: write the apache config file
copy: src=/srv/httpd.conf dest=/etc/httpd/conf/httpd.conf
notify:
- restart httpd
- name: ensure apache is running
service: name=httpd state=started
handlers:
- name: restart httpd
service: name=httpd state=restarted
这个时候再次修改配置文件然后执行playbook,当执行到第二个任务后检测到定义了notify,当整个play的task都执行完毕后,就会找handlers,匹配到名称后就会执行对应的模块。
注意:notify定义的restart httpd 必须与handlers定义的name名称相同,不然会报:not found in any of the known handlers
Register
经常在playbook中,存储某个命令的结果在变量中,以备日后访问是很有用的,而‘register’就是用来决定把结果存储在哪个变量中,结果参数可以用在模板中,动作条目,或者when语句,像下面的例子:
- name: test play
hosts: all
tasks:
- shell: cat /etc/motd
register: motd_contents
- shell: echo "motd contains the word hi"
when: motd_contents.stdout.find('hi') != -1
就像上面展示的那样,这个注册后的参数的内容为字符串’stdout’是可以访问。这个注册了以后的结果,如果像上面展示的,可以转化为一个list(或者已经是一个list),就可以在任务中的”with_items”中使用,“stdout_lines”在对象中已经可以访问了,当然如果你喜欢也可以调用“home_dirs.stdout.split()” , 也可以用其它字段切割:
- name: registered variable usage as a with_items list
hosts: all
tasks:
- name: retrieve the list of home directories
command: ls /home
register: home_dirs
- name: add home dirs to the backup spooler
file: path=/mnt/bkspool/{{ item }} src=/home/{{ item }} state=link
with_items: home_dirs.stdout_lines
# same as with_items: home_dirs.stdout.split()
提示与技巧:
在playbook执行输出信息的底部,可以找到关于托管节点的信息。
其中:
ok:表示执行成功但没做过任何变动的任务;
changed:表示执行成功但做过变动的任务;
unreachable:表示无法到达的任务;
failed:表示执行失败的任务。
如果你想看到执行成功的模块输出信息,使用--verbose即可,(否则只有执行失败的才会由输出信息)
ansible-playbook paaybook.yml --verbose
如果安装了cowsay软件包,ansible playbook的输出已经进行广泛的升级,可以尝试一下!
在执行一个playbook之前,想看看这个playbook的执行会影响到哪些hosts,你可以这样做:
ansible-playbook paybook.yml --list-hosts
在执行一个playbook之前,想看看这个playbook是否有语法错误,可以这样做:
ansible-playbook -C playbook.yml 或
ansible-playbook playbook.yml --check 这样不会执行结果,只会查看playbook是否有语法错误。
三、Playbook的其它特性
条件测试
如果需要根据变量、facts(setup)或此前任务的执行结果来作为某task执行与否的前提时要用到条件测试,在Playbook中条件测试使用when子句,在task后添加when子句即可使用条件测试:when子句支持jinjia2表达式或句法,例如:
tasks:
- name: "shutdown Debian"
command: /sbin/shutdown -h now
when: ansible_os_family == "Debian"
或者多条件判断:
tasks:
- name: “shutdown Centos6 system”
command: /sbin/shutdown -t now
when:
- ansible_distribution == "Centos"
- ansible_distribution_major_version == "6"
意思就是如果对方的系统是centos并且版本是6就进行关机处理,ansible_os_family这个变量是从facts中提取的。
也可以使用组名条件判断:
tasks:
- name: "shut down CentOS 6 and Debian 7 systems"
command: /sbin/shutdown -t now
when: (ansible_distribution == "CentOS" and ansible_distribution_major_version == "6") or
(ansible_distribution == "Debian" and ansible_distribution_major_version == "7")
再如:
- host: web
remote_user: root
vars:
- http_port: 80
tasks:
- name: install package
yum: name=nginx
- name: copy template for centos7
template: src=/etc/ansible/httpd.conf dest=/etc/httpd/conf/httpd.conf #template一般在playbook.yml同级目录中创建一个templates目录,这样的话在这里就可以直接写文件template文件名不用再添加路径,当然如果没有template目录的话这里也可以写template文件所在的绝对路径。
when: ansible_distribution_major_version == "7"
notify: restart service
- name: copy template for centos6
template: src=/etc/ansible/httpd.conf6.j2 dest=/etc/httpd/conf/httpd.conf
when: ansible_distribution_major_version == "6"
notify: restart service
- name: start service
service: name=httpd state=started enabled=yes
handlers:
- name: restart service
service: name=httpd state=restarted
另外也可以自定义变量,当值为某某时执行什么动作,如下:
- hosts: all
vars:
exist: "True"
tasks:
- name: creaet file
command: touch /tmp/test.txt
when: exist | match("True")
- name: delete file
command: rm -rf /tmp/test.txt
when: exist | match("False")
意思是,当exist变量值为True时就创建文件,当值为False时就删除文件。
迭代:
当有需要重复性执行的任务时,可以使用迭代机制,其使用格式为:"将需要迭代的内容定义为item变量引用,并通过with_items语句指明迭代的元素列表即可",例如:
tasks:
- name: "Install Packages"
yum: name={{ item }} state=latest
with_items:
- httpd
- mysql-server
- php
事实上,with_items中可以使用元素还可以为hashes,例如添加用户和组:
tasks:
- name: "Add users"
user: name={{ item.name }} state=present groups={{ item.groups }}
with_items:
- { name:'test1', groups:'wheel'}
- { name:'test2', groups:'root'}
其中引用变量时前缀item变量是固定的,而item后跟的键名就是在with_items中定义的字典键名。
迭代功能在做批量处理时非常有用,比如系统初始化时一般要安装很多初始化的包就可以使用迭代了。
更多功能查看官网。
tags
在一个playbook中,我们一般会定义很多个task,如果我们只想执行某个task或多个task时就可以使用tags标签功能了,格式如下:
cat ceshi.yml
- hosts: web
vars:
http_port: 80
max_clients: 200
remote_user: root
tasks:
- name: ensure apache is at the latest version
yum: name=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
tags:
- hosts
handlers:
- name: restart apache
service: name=httpd state=restarted
为复制hosts文件定义了一个tags,tags为hosts。那么在执行此playbook时可通过ansible-playbook命令使用–tags选项能实现仅运行指定的tasks而非所有的,如下:
查看tags
事实上,不光可以为单个或多个task指定同一个tags。playbook还提供了一个特殊的tags为always。作用就是当使用always当tags的task时,无论执行哪一个tags时,定义有always的tags都会执行。
cat ceshi.yml
- hosts: web
vars:
http_port: 80
max_clients: 200
remote_user: root
tasks:
- name: ensure apache is at the latest version
yum: name=httpd state=latest
- name: write the apache config file
template: src=/srv/httpd.j2 dest=/etc/httpd.conf
tags:
- copy
notify:
- restart apache
- name: ensure apache is running
service: name=httpd state=started
tags:
- always
handlers:
- name: restart apache
service: name=httpd state=restarted
当你了解了Playbook的以上基础知识后基本可以使用playbook来编排一些自动化任务了,上面介绍了playbook的Host、user、task、variables、template等基本组成元素的使用,以及条件测试、迭代、tags等特性,下面就开始介绍playbook中非常重要的一个概念roles,另外对于playbook的template元素,不光可以简单的调用变量,并且支持各种操作符,在使用playbook时,模板也是一个不可或缺的功能。
ansible-playbook的shell模块中的管道符‘|’的使用方法
ansibl下的
- name: Run expect to wait for a successful PXE boot via out-of-band CIMC
shell: | #在另一个解释器下执行
set timeout 300
spawn ssh admin@{{ cimc_host }}
expect "password:"
send "{{ cimc_password }}\n"
expect "\n{{ cimc_name }}"
send "connect host\n"
expect "pxeboot.n12"
send "\n"
exit 0
args:
executable: /usr/bin/expect
delegate_to: localhost