若是说ansible的modules是工具,inventory配置文件是原材料,那么playbook就是一封说明书,这里会记录任务是如何如何执行的,固然若是你愿意,这里也能够定义一些变量、链接参数等等。html
playbook能够由单个或者多个play组成。node
单个play示例:python
---
- hosts: webservers 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
上面的示例中全部的任务做用于webservers所包含的主机,经过root用户链接到目的主机,对apache服务进行了安装、配置、启动等操做,当配置文件有更改时,会触发hanlders里的重启apache操做,vars里定义的“http_port”和 “ max_clients”将会在模版文件“/srv/httpd.j2”中遵循Jinja2语法被使用到。web
playbooks是使用yaml语法格式,因此看起来比较通俗易懂。经过上面的示例能够看出一个play能够包含以下内容:sql
一个playbooks也能够编写多个play,示例以下:shell
---
- hosts: webservers 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 - hosts: databases remote_user: root tasks: - name: ensure postgresql is at the latest version yum: name: postgresql state: latest - name: ensure that postgresql is started service: name: postgresql state: started
上面的示例中,第一个play经过root用户链接到webservers主机组,进行了apache服务的安装和配置操做;第二个play经过root用户连接到databases主机组,进行了数据库的安装和启动操做。数据库
上一章节中咱们说到,一个playbooks能够放置多个play,一个play里面能够有多个tasks(modules),可是,当要管理的资源愈来愈多时,咱们发现将全部play都写在一个yml文件里会很臃肿,很差维护。apache
此时咱们能够经过“import_playbook”方法引用其余的playbooks文件;json
此时咱们能够经过“import_playbook”方法引用其余的playbooks文件;使用“import_tasks”、“include_tasks”、“import_role”、“include_role”、“roles”引用其余的tasks文件。ruby
import_playbook
比较简单,直接上示例,文件main.yml:
- import_playbook: webservers.yml - import_playbook: databases.yml
上述示例中使用import_playbook将webservers.yml和databases.yml文件里的play引用到main.yml,和直接将两个文件里的内容直接粘过来是同样的效果,执行顺序天然也会按照play定义的顺序执行。
import_tasks和include_tasks
能够参考笔者以前写的文章 ansible中include_tasks和import_tasks
import_role和include_role
ansible2.3引入了include_role,ansible 2.4版本后,新增了import_role,经过这两个方法能够在tasks里面导入role,示例以下:
---
- hosts: webservers tasks: - debug: msg: "before we run our role"
- import_role: name: example - include_role: name: example - debug: msg: "after we ran our role"
从上面的示例能够看出,在tasks中使用import_role和include_role方法导入了role example,role里面的task会按顺序执行。
固然咱们也能够引用的同时定义变量:
---
- hosts: webservers roles: - common - role: foo_app_instance vars: dir: '/opt/a' app_port: 5000
- role: foo_app_instance vars: dir: '/opt/b' app_port: 5001
也能够给role打tag:
---
- hosts: webservers tasks: - import_role: name: foo tags: - bar - baz
使用条件语句(后面有详细写when语句用法):
---
- hosts: webservers tasks: - include_role: name: some_role when: "ansible_os_family == 'RedHat'"
roles
除了使用import_role和include_role导入role,咱们也能够直接使用roles方法来导入,示例以下:
--- - hosts: webservers roles: - common - webservers ###OR - hosts: webservers roles: - role: '/path/to/my/roles/common'
和import和include方法相比,roles方法是仅仅能够导入role类型的playbook,而上述两个方法能够在其余tasks中穿插一些role类型的playbook。
在生产中,roles是比较经常使用的因此后面的章节会有对roles的单独讲解,这里就不在展开了。
经过以上的总结,咱们能够看出,在ansible里,若是咱们想复用其余文件的playbooks,可使用include、import、roles三种方法,据我所知也只有这三种方法。
至此,咱们知道了可使用import*和include*导入其余的playbooks,那么这二者的区别是什么呢?
在ansible 2.4版本中引入了dynamic和static的概念,在这以前只能使用include来导入其余的tasks文件,如今include也能用,但官方在考虑在将来版本废弃掉。
静态指全部import*的方法,动态指include*的方法。
关于动态和静态的两点区别,总结以下:
具体介绍能够参考笔者以前写的文章ansible中include_tasks和import_tasks
ansible中能够定义变量的地方能够有不少,在这里主要写下playbooks里面的变量定义,其余部分的变量会在后续的“ansible基础-变量”详细阐述。
变量的定义一般使用YAML语法格式,示例以下:
--- vars: field1: one 字典变量: --- foo: field1: one field2: two
play中定义全局变量
---
- hosts: webservers vars: http_port: 80
上面示例中 http_port参数能够在这个play中的tasks、playbooks、roles中引用。
tasks中定义变量
固然,咱们也能够在某个task中定义局部变量,这个变量只能在本task内使用,示例以下:
---
- hosts: node1 gather_facts: false tasks: - name: Use var debug vars: - name: weimeng - age: 26 debug: var: name,age
include和import中定义变量
include_role定义变量只在被引用的role中生效:
- hosts: node1 gather_facts: false tasks: - include_role: name: role_A vars: age: 24
include_tasks定义变量只在被引用的task中生效:
tasks: - import_tasks: wordpress.yml vars: wp_user: timmy - import_tasks: wordpress.yml vars: wp_user: alice - import_tasks: wordpress.yml vars: wp_user: bob
roles中定义变量
playbook引用role也能够直接定义变量,示例以下:
---
- hosts: webservers roles: - role: bar tags: ["foo"] # using YAML shorthand, this is equivalent to the above - { role: foo, tags: ["bar", "baz"] }
注册变量
在playbook中,咱们能够将一个task的执行结果注册为一个变量,供另一个task使用。例如:
---
- hosts: webservers roles: - role: bar tags: ["foo"] # using YAML shorthand, this is equivalent to the above - { role: foo, tags: ["bar", "baz"] }
将一个task的结果注册为一个变量,而后经过这个变量判断另一个task是否执行,这是注册变量很经常使用的方式。
经过命令行定义变量
在咱们执行playbook时能够在命令行中指定自定义变量,例如:
ansible-playbook release.yml --extra-vars "version=1.23.45 other_variable=foo”
在同一个scope内,若是与其余地方的变量冲突,命令行指定的参数优先级最高。
上面介绍了在playbook中如何定义变量,那么变量可否和task同样定义在单独的yml文件内,而后使用相似于include_tasks的语句引用过来呢? 答案是确定的。
在playbook内引用变量文件使用的是vars_files:语句,示例以下:
---
- hosts: all remote_user: root vars: favcolor: blue vars_files: - /vars/external_vars.yml tasks: - name: this is just a placeholder command: /bin/echo foo
在变量文件/vars/external_vars.yml中,咱们只须要使用YAML语法格式进行变量定义便可。
除了咱们自定义的变量,ansible还支持另一种变量,这个变量相似于puppet的facter,ansible叫作fact。
ansible的fact会根据目的主机的系统信息生成一个json格式的变量集合,咱们在play中能够直接引用。例如比较经常使用的变量:ip地址、主机名、操做系统类型等等。
puppet的facter依赖ruby的一个安装包,经过ruby程序收集系统信息,而ansible的fact是经过python程序收集。
咱们能够经过setup模块来获取目的主机的fact信息:
ansible hostname -m setup
fact会在playbook执行以前收集信息,默认是打开的,咱们也能够经过指定gather_fact参数为false/no/False关闭fact。在没有配置fact cache的状况下,若是关闭fact,playbook的执行速度会有一个显著的提高,示例以下:
---
- hosts: whatever gather_facts: no
前面咱们介绍了下变量的定义/引用方式和fact变量,那么在playbook中咱们如何使用这些变量呢?
变量一般会在模版、条件判断语句、新的变量定义等处能用到。
使用变量的方法很简单,只须要将变量写在两个大括号内而且先后都有空格便可,同时咱们必须将这个大括号用双引号引发来,若是变量穿插在字符串内使用,双引号也要将字符串部分引发来。
示例以下:
- hosts: app_servers vars: app_path: "{{ base_path }}/22"
若是一个变量定义比较复杂,例如列表、字典或fact(json格式),咱们能够经过以下方式访问:
列表变量访问:
{{ foo[0] }}
字典变量访问:
{{ foo[name] }}
或
{{ foo.name }}
json格式访问变量访问:
{{ansible_eth0["ipv4"]["address"] }}
或
{{ansible_eth0.ipv4.address }}
这里说一个小技巧,在咱们排错过程当中不少状况咱们要debug一些变量。此时,可使用debug模块输出变量。
debug模块有两种使用方式,vars和msg :
---
- hosts: node1 gather_facts: false vars: - name: weimeng - age: 26 tasks: - name: Use var debug debug: var: name,age - name: Use msg debug debug: msg: "my name is {{ name }},and my age is {{ age }}"
输入以下:
➜ lab-ansible ansible-playbook playbooks/task_vars.yml [WARNING]: Found variable using reserved name: name PLAY [node1] ******************************************************************* TASK [Use var debug] *********************************************************** ok: [node1] => { "name,age": "(u'weimeng', 26)" } TASK [Use msg debug] *********************************************************** ok: [node1] => { "msg": "my name is weimeng,and my age is 26" } PLAY RECAP ********************************************************************* node1 : ok=2 changed=0 unreachable=0 failed=0
经过对比咱们能够看出,“vars”适用于直接debug变量,而“msg”能够掺杂一些字符串,咱们能够根据实际状况来选择使用。
本章节主要介绍了playbook的相关变量。ansible变量的知识点仍是不少的,因此我计划在后边会单独介绍ansible的变量,这里就点到为止。
ansible条件语句不是不少,比较经常使用的就是when语句和循环语句。
当知足必定的条件时,咱们想要跳过某个task,这时候when语句出场了。当when语句的参数为true时,才会执行这个task,不然反之。
yum模块的name能够以列表的形式指定多个安装包,可是不少其余模块是不支持列表的,例如file的path,copy的src,等等;或者说咱们想迭代的将一个列表元素传递给某个模块处理,若是有多少个元素写多个task就很麻烦。此时咱们可使用ansible的循环语句loop(ansible 2.5之后),在2.5版本以前可使用with_,loop相似于旧版本的with_list语句。
ansible的when语句用于判断是否执行这个task,例如
tasks: - name: "shut down Debian flavored systems" command: /sbin/shutdown -t now when: ansible_os_family == "Debian" # note that Ansible facts and vars like ansible_os_family can be used # directly in conditionals without double curly braces
示例中若是系统的类型是“Debian”才会执行/sbin/shutdown -t now命令。
条件语句也可使用“and”和“or”:
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")
条件也能够写成列表的形式,这种形式和and语句起到同样的效果:
tasks: - name: "shut down CentOS 6 systems" command: /sbin/shutdown -t now when: - ansible_distribution == "CentOS"
- ansible_distribution_major_version == "6"
register变量条件语句
经过对某个task的执行结果是否成功,决定另一个task是否要执行:
tasks: - command: /bin/false register: result ignore_errors: True - command: /bin/something when: result is failed # In older versions of ansible use ``success``, now both are valid but succeeded uses the correct tense. - command: /bin/something_else when: result is succeeded - command: /bin/still/something_else when: result is skipped
变量是否被定义语句:
tasks: - shell: echo "I've got '{{ foo }}' and am not afraid to use it!" when: foo is defined - fail: msg="Bailing out. this play requires 'bar'" when: bar is undefined
上面说到loop相似于旧版本的with_list语句,也就是说loop会将列表的元素逐个传递给上面的module,从而达到重复执行的目的。
最简单的形式:
--- tasks: - command: echo { item } loop: [ 0, 2, 4, 6, 8, 10 ]
loop与when结合使用:
--- tasks: - command: echo { item } loop: [ 0, 2, 4, 6, 8, 10 ] when: item > 5
一般loop语句会结合各式各样的filter去使用,例如“ loop: “{ { [\'alice\', \'bob\'] |product([\'clientdb\', \'employeedb\', \'providerdb\'])|list }}””,这个例子和with_nested语句起到同样的效果。也就是说旧版本的with_ + lookup() 所能实现的,新版本的loop+filter一样能实现。
通常playbook里的task执行顺序和python同样,由上至下,定义的顺序即执行的顺序。一样的,使用include*和import*导入playbook或tasks也会安照导入顺序执行。
当playbook中有使用roles导入task和自定义tasks时,咱们会发现ansible总会先执行roles导入的task,而后执行自定义的tasks,例如:
- hosts: localhost gather_facts: no vars: - ff: 1 - gg: 2 tasks: - debug: var: ff roles: - role: role_B
输出结果:
➜ lab-ansible ansible-playbook playbooks/roles_vars.yml PLAY [localhost] *************************************************************** TASK [role_B : debug] ********************************************************** ok: [localhost] => { "a": 2 } TASK [debug] ******************************************************************* ok: [localhost] => { "ff": 1 } PLAY RECAP ********************************************************************* localhost : ok=2 changed=0 unreachable=0 failed=0
从上面示例发现,虽然咱们将tasks定义在了前面,可是tasks任务仍是在roles任务以后执行。此时咱们可使用pre_task和post_task来强制指定执行顺序,例如:
---
- hosts: localhost gather_facts: no vars: - ff: 1
- gg: 2 pre_tasks: - import_role: name: role_A vars: age: 23 roles: - role: role_B tasks: - debug: var: ff post_tasks: - debug: var: gg
总结下playbook里任务的执行顺序:
使用“pre_tasks:”定义的任务
使用“roles:”引用的任务
使用“tasks:”自定义的任务
使用“post_tasks”定义的任务
在部署应用时,一般的步骤是安装软件包==>更改配置文件==>初始化数据库==>启动(重启)服务;升级的步骤通常是:升级软件包==>更改配置文件==>初始化数据库==>重启服务。咱们发现不论是新部署仍是升级,最后一步都是要从新加载程序的,也就是说当咱们升级了软件或者更改了配置文件都须要重启一下应用。
为了实现触发服务重启,ansible使用handlers方法定义重启的动做,handlers并非每次执行playbook都会触发,而是某些指定资源状态改变时才会触发指定的handlers(这里使用“资源”一词借鉴于puppet)。
示例以下:
- name: template configuration file template: src: template.j2 dest: /etc/foo.conf notify: - restart memcached - restart apache
上面的示例中,当/etc/foo.conf文件内容有改动时(返回changed),会触发重启memcached和apache服务,若是文件内容没有变化时(返回ok),则不会触发handlers。
ansible执行过程当中并不会当即触发handlers动做,而是以play为单位,一个play执行完后最后才会触发handlers。
这样设计也是很合理的,试想在一个play内,若是触发一次就执行一次handlers,那么除了最后一次的重启,前面触发的重启都是无用功。
另外须要注意的一点是,handlers触发的执行顺序是按照定义顺序执行,而不是按照notify指定的顺序执行。
固然若是咱们想要当即触发,也是能够的,在play定义“- meta: flush_handlers”便可。
另外须要注意的一点是,handlers触发的执行顺序是按照定义顺序执行,而不是按照notify指定的顺序执行。
欢迎你们关注个人公众号: