1. playbooks介绍

如果说ansible的modules是工具,inventory配置文件是原材料,那么playbook就是一封说明书,这里会记录任务是如何如何执行的,当然如果你愿意,这里也可以定义一些变量、连接参数等等。

playbook可以由单个或者多个play组成。

单个play示例:

  1. ---
  2. - hosts: webservers
  3. vars:
  4. http_port:
  5. max_clients:
  6. remote_user: root
  7. tasks:
  8. - name: ensure apache is at the latest version
  9. yum:
  10. name: httpd
  11. state: latest
  12. - name: write the apache config file
  13. template:
  14. src: /srv/httpd.j2
  15. dest: /etc/httpd.conf
  16. notify:
  17. - restart apache
  18. - name: ensure apache is running
  19. service:
  20. name: httpd
  21. state: started

上面的示例中所有的任务作用于webservers所包含的主机,通过root用户连接到目的主机,对apache服务进行了安装、配置、启动等操作,当配置文件有更改时,会触发hanlders里的重启apache操作,vars里定义的“http_port”和 “ max_clients”将会在模版文件“/srv/httpd.j2”中遵循Jinja2语法被使用到。

playbooks是使用yaml语法格式,所以看起来比较通俗易懂。通过上面的示例可以看出一个play可以包含如下内容:

  • hosts:主机组,后面定义的task将作用于该主机组的所有主机
  • vars:变量定义,在后面的task中可以引用
  • remote-user:连接参数,例如remote-user,become,become-user等等,这些参数将会覆盖ansible.cfg配置文件里的参数
  • tasks:任务,可以看作很多modules的集合,这些modules可以使用vars定义的变量
  • handlers:触发才会执行的task,很多情况下,当其他task被执行并且状态有改变后,我们希望会触发一些任务,那些被触发的任务可以写在这里

一个playbooks也可以编写多个play,示例如下:

  1. ---
  2. - hosts: webservers
  3. remote_user: root
  4. tasks:
  5. - name: ensure apache is at the latest version
  6. yum:
  7. name: httpd
  8. state: latest
  9. - name: write the apache config file
  10. template:
  11. src: /srv/httpd.j2
  12. dest: /etc/httpd.conf
  13. - hosts: databases
  14. remote_user: root
  15. tasks:
  16. - name: ensure postgresql is at the latest version
  17. yum:
  18. name: postgresql
  19. state: latest
  20. - name: ensure that postgresql is started
  21. service:
  22. name: postgresql
  23. state: started 

上面的示例中,第一个play通过root用户连接到webservers主机组,进行了apache服务的安装和配置操作;第二个play通过root用户链接到databases主机组,进行了数据库的安装和启动操作。

2.可重复利用的playbooks

上一章节中我们说到,一个playbooks可以放置多个play,一个play里面可以有多个tasks(modules),但是,当要管理的资源越来越多时,我们发现将所有play都写在一个yml文件里会很臃肿,不好维护。

此时我们可以通过“import_playbook”方法引用其他的playbooks文件;

此时我们可以通过“import_playbook”方法引用其他的playbooks文件;使用“import_tasks”、“include_tasks”、“import_role”、“include_role”、“roles”引用其他的tasks文件。

import_playbook

比较简单,直接上示例,文件main.yml:

  1. - import_playbook: webservers.yml
  2. - 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,示例如下:

  1. ---
  2. - hosts: webservers
  3. tasks:
  4. - debug:
  5. msg: "before we run our role"
  6. - import_role:
  7. name: example
  8. - include_role:
  9. name: example
  10. - debug:
  11. msg: "after we ran our role"

从上面的示例可以看出,在tasks中使用import_role和include_role方法导入了role example,role里面的task会按顺序执行。

当然我们也可以引用的同时定义变量:

  1. ---
  2. - hosts: webservers
  3. roles:
  4. - common
  5. - role: foo_app_instance
  6. vars:
  7. dir: '/opt/a'
  8. app_port:
  9. - role: foo_app_instance
  10. vars:
  11. dir: '/opt/b'
  12. app_port:

也可以给role打tag:

  1. ---
  2. - hosts: webservers
  3. tasks:
  4. - import_role:
  5. name: foo
  6. tags:
  7. - bar
  8. - baz

使用条件语句(后面有详细写when语句用法):

  1. ---
  2. - hosts: webservers
  3. tasks:
  4. - include_role:
  5. name: some_role
  6. 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三种方法,据我所知也只有这三种方法。

2.1 动态和静态

至此,我们知道了可以使用import*和include*导入其他的playbooks,那么这两者的区别是什么呢?

在ansible 2.4版本中引入了dynamic和static的概念,在这之前只能使用include来导入其他的tasks文件,现在include也能用,但官方在考虑在未来版本废弃掉。

静态指所有import*的方法,动态指include*的方法。

关于动态和静态的两点区别,总结如下:

  • import_tasks(Static)方法会在playbooks解析阶段将父task变量和子task变量全部读取并加载
  • include_tasks(Dynamic)方法则是在执行play之前才会加载自己变量
  • include_tasks方法调用的文件名称可以加变量
  • import_tasks方法调用的文件名称不可以有变量

具体介绍可以参考笔者之前写的文章ansible中include_tasks和import_tasks

3.playbooks变量

3.1定义变量

ansible中可以定义变量的地方可以有很多,在这里主要写下playbooks里面的变量定义,其他部分的变量会在后续的“ansible基础-变量”详细阐述。

变量的定义通常使用YAML语法格式,示例如下:

  1. ---
  2. vars:
  3. field1: one
  4. 字典变量:
  5. ---
  6. foo:
  7. field1: one
  8. field2: two

play中定义全局变量

  1. ---
  2. - hosts: webservers
  3. vars:
  4. http_port:

上面示例中 http_port参数可以在这个play中的tasks、playbooks、roles中引用。

tasks中定义变量

当然,我们也可以在某个task中定义局部变量,这个变量只能在本task内使用,示例如下:

  1. ---
  2. - hosts: node1
  3. gather_facts: false
  4.  
  5. tasks:
  6. - name: Use var debug
  7. vars:
  8. - name: weimeng
  9. - age:
  10. debug:
  11. var: name,age

include和import中定义变量

include_role定义变量只在被引用的role中生效:

  1. - hosts: node1
  2. gather_facts: false
  3. tasks:
  4. - include_role:
  5. name: role_A
  6. vars:
  7. age:

include_tasks定义变量只在被引用的task中生效:

  1. tasks:
  2. - import_tasks: wordpress.yml
  3. vars:
  4. wp_user: timmy
  5. - import_tasks: wordpress.yml
  6. vars:
  7. wp_user: alice
  8. - import_tasks: wordpress.yml
  9. vars:
  10. wp_user: bob

roles中定义变量

playbook引用role也可以直接定义变量,示例如下:

  1. ---
  2. - hosts: webservers
  3. roles:
  4. - role: bar
  5. tags: ["foo"]
  6. # using YAML shorthand, this is equivalent to the above
  7. - { role: foo, tags: ["bar", "baz"] }

注册变量

在playbook中,我们可以将一个task的执行结果注册为一个变量,供另外一个task使用。例如:

  1. ---
  2. - hosts: webservers
  3. roles:
  4. - role: bar
  5. tags: ["foo"]
  6. # using YAML shorthand, this is equivalent to the above
  7. - { role: foo, tags: ["bar", "baz"] }

将一个task的结果注册为一个变量,然后通过这个变量判断另外一个task是否执行,这是注册变量很常用的方式。

通过命令行定义变量

在我们执行playbook时可以在命令行中指定自定义变量,例如:

  1. ansible-playbook release.yml --extra-vars "version=1.23.45 other_variable=foo”

在同一个scope内,如果与其他地方的变量冲突,命令行指定的参数优先级最高。

3.2引用变量文件

上面介绍了在playbook中如何定义变量,那么变量能否和task一样定义在单独的yml文件内,然后使用类似于include_tasks的语句引用过来呢? 答案是肯定的。

在playbook内引用变量文件使用的是vars_files:语句,示例如下:

  1. ---
  2. - hosts: all
  3. remote_user: root
  4. vars:
  5. favcolor: blue
  6. vars_files:
  7. - /vars/external_vars.yml
  8. tasks:
  9. - name: this is just a placeholder
  10. command: /bin/echo foo

在变量文件/vars/external_vars.yml中,我们只需要使用YAML语法格式进行变量定义即可。

3.3 facts

除了我们自定义的变量,ansible还支持另外一种变量,这个变量类似于puppet的facter,ansible叫做fact。

ansible的fact会根据目的主机的系统信息生成一个json格式的变量集合,我们在play中可以直接引用。例如比较常用的变量:ip地址、主机名、操作系统类型等等。

puppet的facter依赖ruby的一个安装包,通过ruby程序收集系统信息,而ansible的fact是通过python程序收集。

我们可以通过setup模块来获取目的主机的fact信息:

  1. ansible hostname -m setup

fact会在playbook执行之前收集信息,默认是打开的,我们也可以通过指定gather_fact参数为false/no/False关闭fact。在没有配置fact cache的情况下,如果关闭fact,playbook的执行速度会有一个显著的提升,示例如下:

  1. ---
  2. - hosts: whatever
  3.   gather_facts: no

3.4变量的使用

前面我们介绍了下变量的定义/引用方式和fact变量,那么在playbook中我们如何使用这些变量呢?

变量通常会在模版、条件判断语句、新的变量定义等处能用到。

使用变量的方法很简单,只需要将变量写在两个大括号内并且前后都有空格即可,同时我们必须将这个大括号用双引号引起来,如果变量穿插在字符串内使用,双引号也要将字符串部分引起来。

示例如下:

  1. - hosts: app_servers
  2. vars:
  3. app_path: "{{ base_path }}/22"

如果一个变量定义比较复杂,例如列表、字典或fact(json格式),我们可以通过如下方式访问:

列表变量访问:

  1. {{ foo[] }}

字典变量访问:

  1. {{ foo[name] }} 

  1. {{ foo.name }}

json格式访问变量访问:

  1. {{ansible_eth0["ipv4"]["address"] }}

  1. {{ansible_eth0.ipv4.address }}

这里说一个小技巧,在我们排错过程中很多情况我们要debug一些变量。此时,可以使用debug模块输出变量。

debug模块有两种使用方式,vars和msg :

  1. ---
  2. - hosts: node1
  3. gather_facts: false
  4. vars:
  5. - name: weimeng
  6. - age:
  7. tasks:
  8. - name: Use var debug
  9. debug:
  10. var: name,age
  11. - name: Use msg debug
  12. debug:
  13. msg: "my name is {{ name }},and my age is {{ age }}"

输入如下:

  1. lab-ansible ansible-playbook playbooks/task_vars.yml
  2. [WARNING]: Found variable using reserved name: name
  3.  
  4. PLAY [node1] *******************************************************************
  5.  
  6. TASK [Use var debug] ***********************************************************
  7. ok: [node1] => {
  8. "name,age": "(u'weimeng', 26)"
  9. }
  10.  
  11. TASK [Use msg debug] ***********************************************************
  12. ok: [node1] => {
  13. "msg": "my name is weimeng,and my age is 26"
  14. }
  15.  
  16. PLAY RECAP *********************************************************************
  17. node1 : ok= changed= unreachable= failed=

通过对比我们可以看出,“vars”适用于直接debug变量,而“msg”可以掺杂一些字符串,我们可以根据实际情况来选择使用。

本章节主要介绍了playbook的相关变量。ansible变量的知识点还是很多的,所以我计划在后边会单独介绍ansible的变量,这里就点到为止。

4. 条件语句

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语句。

4.1 when语句

ansible的when语句用于判断是否执行这个task,例如

  1. tasks:
  2. - name: "shut down Debian flavored systems"
  3. command: /sbin/shutdown -t now
  4. when: ansible_os_family == "Debian"
  5. # note that Ansible facts and vars like ansible_os_family can be used
  6. # directly in conditionals without double curly braces

示例中如果系统的类型是“Debian”才会执行/sbin/shutdown -t now命令。

条件语句也可以使用“and”和“or”:

  1. tasks:
  2. - name: "shut down CentOS 6 and Debian 7 systems"
  3. command: /sbin/shutdown -t now
  4. when: (ansible_distribution == "CentOS" and ansible_distribution_major_version == "") or
  5. (ansible_distribution == "Debian" and ansible_distribution_major_version == "")

条件也可以写成列表的形式,这种形式和and语句起到一样的效果:

  1. tasks:
  2. - name: "shut down CentOS 6 systems"
  3. command: /sbin/shutdown -t now
  4. when:
  5. - ansible_distribution == "CentOS"
  6. - ansible_distribution_major_version == ""

register变量条件语句

通过对某个task的执行结果是否成功,决定另外一个task是否要执行:

  1. tasks:
  2. - command: /bin/false
  3. register: result
  4. ignore_errors: True
  5.  
  6. - command: /bin/something
  7. when: result is failed
  8.  
  9. # In older versions of ansible use ``success``, now both are valid but succeeded uses the correct tense.
  10. - command: /bin/something_else
  11. when: result is succeeded
  12.  
  13. - command: /bin/still/something_else
  14. when: result is skipped

变量是否被定义语句:

  1. tasks:
  2. - shell: echo "I've got '{{ foo }}' and am not afraid to use it!"
  3. when: foo is defined
  4.  
  5. - fail: msg="Bailing out. this play requires 'bar'"
  6. when: bar is undefined

4.2 循环语句

上面说到loop类似于旧版本的with_list语句,也就是说loop会将列表的元素逐个传递给上面的module,从而达到重复执行的目的。

最简单的形式:

  1. ---
  2. tasks:
  3. - command: echo { item }
  4. loop: [ , , , , , ]

loop与when结合使用:

  1. ---
  2. tasks:
  3. - command: echo { item }
  4. loop: [ , , , , , ]
  5. when: item >

通常loop语句会结合各式各样的filter去使用,例如“  loop: “{ { [\'alice\', \'bob\'] |product([\'clientdb\', \'employeedb\', \'providerdb\'])|list }}””,这个例子和with_nested语句起到一样的效果。也就是说旧版本的with_ + lookup() 所能实现的,新版本的loop+filter同样能实现。

5. 执行顺序

一般playbook里的task执行顺序和python一样,由上至下,定义的顺序即执行的顺序。同样的,使用include*和import*导入playbook或tasks也会安照导入顺序执行。

5.1 per_tasks和post_tasks

当playbook中有使用roles导入task和自定义tasks时,我们会发现ansible总会先执行roles导入的task,然后执行自定义的tasks,例如:

  1. - hosts: localhost
  2. gather_facts: no
  3. vars:
  4. - ff:
  5. - gg:
  6. tasks:
  7. - debug:
  8. var: ff
  9. roles:
  10. - role: role_B 

输出结果:

  1. lab-ansible ansible-playbook playbooks/roles_vars.yml
  2.  
  3. PLAY [localhost] ***************************************************************
  4.  
  5. TASK [role_B : debug] **********************************************************
  6. ok: [localhost] => {
  7. "a":
  8. }
  9.  
  10. TASK [debug] *******************************************************************
  11. ok: [localhost] => {
  12. "ff":
  13. }
  14.  
  15. PLAY RECAP *********************************************************************
  16. localhost : ok= changed= unreachable= failed=

从上面示例发现,虽然我们将tasks定义在了前面,但是tasks任务还是在roles任务之后执行。此时我们可以使用pre_task和post_task来强制指定执行顺序,例如:

  1. ---
  2. - hosts: localhost
  3. gather_facts: no
  4. vars:
  5. - ff:
  6. - gg:
  7. pre_tasks:
  8. - import_role:
  9. name: role_A
  10. vars:
  11. age:
  12. roles:
  13. - role: role_B
  14. tasks:
  15. - debug:
  16. var: ff
  17. post_tasks:
  18. - debug:
  19. var: gg

总结下playbook里任务的执行顺序:

  • 使用“pre_tasks:”定义的任务

  • 使用“roles:”引用的任务

  • 使用“tasks:”自定义的任务

  • 使用“post_tasks”定义的任务

5.2 handlers

在部署应用时,通常的步骤是安装软件包==>更改配置文件==>初始化数据库==>启动(重启)服务;升级的步骤一般是:升级软件包==>更改配置文件==>初始化数据库==>重启服务。我们发现不管是新部署还是升级,最后一步都是要重新加载程序的,也就是说当我们升级了软件或者更改了配置文件都需要重启一下应用。

为了实现触发服务重启,ansible使用handlers方法定义重启的动作,handlers并不是每次执行playbook都会触发,而是某些指定资源状态改变时才会触发指定的handlers(这里使用“资源”一词借鉴于puppet)。

示例如下:

  1. - name: template configuration file
  2. template:
  3. src: template.j2
  4. dest: /etc/foo.conf
  5. notify:
  6. - restart memcached
  7. - 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指定的顺序执行。

6.参考链接

  • https://docs.ansible.com/ansible/latest/user_guide/playbooks_intro.html#
  • https://docs.ansible.com/ansible/2.6/user_guide/playbooks_variables.html
  • https://docs.ansible.com/ansible/2.6/user_guide/playbooks_reuse.html
  • https://docs.ansible.com/ansible/2.6/user_guide/playbooks_templating.html
  • https://docs.ansible.com/ansible/2.6/user_guide/playbooks_conditionals.html
  • https://docs.ansible.com/ansible/2.6/user_guide/playbooks_loops.html

欢迎大家关注我的公众号:

ansible基础-playbooks的更多相关文章

  1. ansible基础-理解篇

    1. 介绍 要说现在的部署工具,ansible可以说家喻户晓了. ansible是一个开源软件,用于软件供应.配置管理.应用部署.ansible可以通过SSH.remote PowerShell.其他 ...

  2. ansible基础-安装与配置

    一 安装 1.1 ansible架构 ansible是一个非常简单的自动化部署项目,由python编写并且开源.用于提供自动化云配置.配置文件管理.应用部署.服务编排和很多其他的IT自动化需求. an ...

  3. ansible基础-roles

    一 简介 注:本文demo使用ansible2.7稳定版 在我看来,role是task文件.变量文件.handlers文件的集合体,这个集合体的显著特点是:可移植性和可重复执行性. 实践中,通常我们以 ...

  4. Ansible--01 ansible基础 Ansible-ad- hoc

    目录 自动化运维工具-Ansible基础 自动化运维的含义 Ansible 基础及安装 Ansible的架构 Ansible的执行流程 ansible配置文件 ansible Inventory(主机 ...

  5. ansible基础-优化

    简介 当管理集群达到一定规模时,ansible达到性能瓶颈是难以避免的,此时我们可以通过一定手段提高ansible的执行效率和性能. 笔者虽未管理过超大规模服务器,但也通过查找资料和咨询大神了解了一些 ...

  6. ansible基础-Jinja2模版 | 过滤器

    Jinja2模版介绍 注:本文demo使用ansible2.7稳定版 在ansible基础-变量的「8.2 模版使用变量」章节中关于模版与变量也有所提及,有兴趣的同学可以去回顾一下. ansible通 ...

  7. ansible基础-ansible角色的使用

    ansible基础-ansible角色的使用 作者:尹正杰  版权声明:原创作品,谢绝转载!否则将追究法律责任. 我们建议把多个节点都会用到的功能将其定义模块,然后谁要用到该模块就直接调用即可!而在a ...

  8. ansible基础-playbook剧本的使用

    ansible基础-playbook剧本的使用 作者:尹正杰  版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.YAML概述 1>.YAML的诞生 YAML是一个可读性高,用来表达数据序 ...

  9. 003.Ansible基础使用

    一 Ansible命令用法 Ansible命令行执行方式有:Ad-Hoc.Ansible-playbook两种,Web方式其官方提供付费产品Tower.Ad-Hoc主要用于临时命令的执行,Ansibl ...

随机推荐

  1. Java 多线程系列 CountDownLatch

    CountDownLatch 一个或多个线程等待其他线程完成操作后在在执行 CountDownLatch通过一个计数器来实现,await方法阻塞直到 countDown() 调用计数器归零之后释放所有 ...

  2. 星环大数据安全组件Guardian与hadoop自带的安全组件区别

    在进行讲解之前,先带大家学习下hadoop关于hdfs自己的安全如何实现的--------------------------- 名词: ACL-访问控制列表(Access Control List, ...

  3. c++ 开源库介绍和安装

    1 BLAS库 BLAS(Basic Linear Algebra Subprograms)是一组线性代数计算中通用的基本运算操作函数集合.BLAS Technical (BLAST) Forum负责 ...

  4. python第七天(字符编码,字符与字节,文件操作)

    一.字符编码: 定义:将人识别的字符转换成计算机能识别的0和1,转换的规则就是字符编码表. 常见编码表:ascii.unicode.GBK 编码表: 1.采用的都是unicode编码表 2.unico ...

  5. unity Tab键实现切换输入框功能

    using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; ...

  6. 迁移hive,不同集群。

    step1: 设置默认需要导出的hive数据库为defaultDatabase 在原集群中的任意节点上,新建“.hiverc”文件,加入如下内容: vi ~/.hiverc use defaultDa ...

  7. 做IT,必备的安全知识!

    以前的认知 以前刚接触IT行业,而我身为运维,我以为我所需要做的安全就是修改服务器密码为复杂的,ssh端口改为非22,还有就是不让人登录服务器就可以保证我维护的东西安全. 现在的认知 工作也好几年了, ...

  8. MongoDB与Spring整合(支持事务)——SpringDataMongoDB

    1.将MongoDB设置为复制集模式 a.修改 mongod.cfg 文件,添加replSetName复制集名称 #replication: replication: replSetName: &qu ...

  9. 第三方npm包安装失败

    最近升级一些第三方库,老是出现无法正常安装npm的现象. 一.问题现象 1.webpack4安装失败 // 报错信息 webpack Maximum call stack size exceeded ...

  10. kali渗透-基础篇

    渗透之meterpreter 模拟场景:小明是我室友,整天游戏人生,浑浑噩噩,前途迷茫,每次上课交作业都要看我的,于是我开启了apche服务器,给他下载作业(别问我为什么不用QQ传,因为要装逼!),他 ...