TOC

Playbook简介

    从本质上来讲,ansible命令是一个命令行工具,我们可以借助ansible命令来执行各种任务,而playbook可以看作脚本,它可以将原本应该使用ansible命令行工具实现的各种任务全部组合起来,形成一个剧本,然后批量的在远端主机执行,在playbook中,任务是一个一个的task构成的,而每个task可以看做原来ansible命令执行的单个任务,所以也就是说,只需要将原来ansible命令行要执行的任务编写成playbook,并把不同的模块按照顺序编排在剧本中,ansible就会按照剧本一步一步的执行,从而达到最终的目的;
    Playbook采用YAML的语法格式来组织或者说来编排Ansible当中的Playbook任务清单,然后使用ansible-palybook命令去指挥着被控主机完成Playbook配置清单内的任务;

YAML语言

    YAML( YAML Ain't Markup Language),它时一个可读性高的用来表达资料序列格式的语言,YAML语言在设计之初参考了多种语言,如XML、C、Python、Perl以及电子邮件格式RFC2822等语言,它使用严格的锁进,来进行分层的,特别适合用来表达或编辑数据结构、各种配置文件或文件大纲,通常,YAML的配置文件后缀约定为.yml或者.yaml,其官网为https://yaml.org;
YAML语法要求
    YAML格式的语法要求,在YAML文件中,首行必须使用三个连续的"-"开始,还可以选择性的选择三个连续的"."结束,在YAML文件中注释使用"#"符号表示,同时,YAML格式的文件内,锁进必须统一,不能用空格和TAB混用,同样的锁进代表着同样的级别,程序判别配置的级别就主要是通过锁进来实现;
    YAML格式的文件内容区分大小写,同时,K/V键值对,Value可以是一个字符串也可以是一个列表,使用"[]"特殊符号将列表中的元素包括起来;
YAML数据类型
    在YAML文件中,对于Ansible来讲,支持三种数据类型,第一标量,标量表示单个不可分割的值,可以是字符串、数字、浮点数、Null、时间或日期,第二字典,支持以键值对组成的字典数据类型,第三数组,和Python语言中的列表一样,使用"[]"符号包裹元素的容器;
    这三种数据类型,都有自己独特的语法,编写方式常见的也有两种,一种为单行模式,一种为多行模式,唯一注意的是数组,数组在多行模式下,数组内的元素,均需使用"-"符号开头,后接一个空格,如下示例;
# 单行模式表示标量
name: "cce"
# 多行模式表示标量(建议两个空格)
name:
  "cce"

# 单行模式表示对象
user_info: { name : cce ,  age : 26}
# 多行模式表示对象(建议两个空格)
user_info:
  name: cce
  age: 26

# 单行模式表示字典
languages: [ java , python , go ]
# 多行模式表示字典(建议两个空格)
languages:
  - java
  - python
  - go
YAML语法示例
    对于Ansible的Playbook来讲,我们只需要知道上述三种数据类型即可,那么同时,如果希望将YAML格式的数据内容直接转换成JSON格式,在互联网上有很多的工具,如http://www.json2yaml.com,如下示例;
name: John Smith
age: 26
gender: Male
spouse:
- name: Female # 此处是一个对象,表示spouse数组内的第一个对象
  age: 17
  gender: Male
- { name: Jenny Smith , age: 13 , gender: Female }
- { name: hao Smith , age: 20 , gender: Male }

  • 注意:这里唯一需要注意一点,就是在一个数组内,如果元素为对象,那么在多行模式下,只需要在对象的第一个属性前面加入"-"符号即可,如上标红处;

Palybook语法格式

    Palybook是使用YAML语言来组织任务的清单的,但是由于Ansible工具是由Python语言所研发,所以在使用YAML语法格式来开发Ansible当中的Palybook时,首行必须是以"-"符号开头,然后Ansible会将这个Palybook清单文件转为List数据类型,供ansible-palybook命令使用;

ansible-playbook常用参数

    ansible-playbook命令的常用参数如下;
-k:指定连接远端主机的密码;
-e:定义传递给Palybook的变量;
-T:指定连接超时时长;
-u:指定ansible连接用户;
-t:执行指定tag的任务;
-i:指定主机配置清单文件路径;
--syntax-check:检查当前palybook清单配置语法是否正确;
--list-hosts:列出当前playbook会执行的后端主机;
--list-tags:列出当前playbook当中所有的tag;
--list-tasks:列出当前playbook当中所有的任务;
--start-at-task:指定从哪个任务开始,值为任务的name值;

Palybook核心组件

    想要使用Palybook来编排我们的Ansible任务,需要了解Palybook当中的七个核心组件,即hosts、tasks、variables、templates、handlers、notify、tags,每个组件都有自己独特的功能实现;
hosts: 指定需要执行Palybook任务的远端主机,可以是在主机清单文件里面定义的分组也可以是具体的IP或hostname;
tasks: 任务集,在task任务集当中,可以有多个任务,每个任务都使用字典类型的数据来表示,在这个字典里面最少包含两个属性,即name和task任务,task任务使用具体的模块来实现;
Hosts组件
    Palybook的主要任务就是为了让远端特定的主机执行各种ansible任务,那么Palybook中的hosts组件,最主要就是用来指定执行任务的具体的远端主机,它可以是一个组名,同时也支持组的与或非关系,当然,hosts组件可以指定一个具体的IP地址或者hostname,如下示例;
[cce@doorta /usr/local/Project/linux]# cat /etc/ansible/hosts
[centos]
172.16.1.1
172.16.1.2
[cce@doorta /usr/local/Project/linux]# cat palybook.yaml
---
- hosts: centos
[cce@doorta /usr/local/Project/linux]# ansible-playbook palybook.yaml --list-host
    pattern: ['centos']
    hosts (2):
      172.16.1.1
      172.16.1.2
    可以看到,当我们指定hosts组件的值为centos时,ansible-playbook会自动去主机配置清单中匹配这个组下面所有的主机,这就表明,当前的playbook清单所需要执行任务的主机为centos组下所有的主机;
Tasks组件
    Tasks组件最主要的功能就是指定具体需要实现的任务,Tasks组件为一个列表,列表内可以有多个任务,每个任务以字典的形式表示,每个字典内,最少有name和task两个属性,name表示指明当前任务的名称,task并非一个属性名,而是一个模块名,它的值为一个对象,对象内的属性,就是该模块支持的所有参数,如下示例;
# 使用palybook在分组为centos的主机上创建一个用户,且用户名为cce,UID为1000,然后将该用户的附加组设置为root
[cce@doorta /usr/local/Project/linux]# cat palybook.yaml             
- hosts: centos
  tasks:
    - name: add user
      user:
        name: cce
        uid: 1000
    - name: edit user
      user:
        name: cce
        groups: root
        shell: /bin/sh
# 执行任务
[cce@doorta /usr/local/Project/linux]# ansible-playbook /usr/local/Project/linux/palybook.yaml
# 验证结果
[cce@doorta /usr/local/Project/linux]# ansible centos -m shell -a 'id cce' -o
172.16.1.2 | CHANGED | rc=0 | (stdout) uid=1000(cce) gid=1000(cce) groups=1000(cce),0(root)
172.16.1.1 | CHANGED | rc=0 | (stdout) uid=1000(cce) gid=1000(cce) groups=1000(cce),0(root)
remove_user组件
    remove_user组件,见名知义,它主要的作用就是用来指定远端主机执行任务的用户的,该组件有点特殊,它可以直接全局定义,表示所有主机执行所有的task都以remove_user组件指定的用户来执行;
    当然我们也可以对某个task单独来设置remove_user组件,表示所有主机在执行这个task时使用remove_user组件指定用户来执行,但是这样操作,会使得ansible重新使用remove_user组件指定的用户重新连接所有主机,所以很少这样使用,如下示例;
- hosts: centos
  remote_user: root # 所有主机都默认使用root用户来执行任务
  tasks:
    - name: add user
      user:  
        name: cce
        state: present
    - name: touch file
      remote_user: cce  # 所有主机在执行touch file任务时,使用cce用户来执行,task当中都remove_user优先级更高,但是这样会使得ansible使用该用户重新与远端主机建立连接,所以不推荐使用这种方式来实现
      command: touch /tmp/cce
语法扩展
    在Ansible的Palybook当中,数据类型为字典的单行形式,有一种特殊的写法,它除了可以使用"{}"符号包裹起来,也可以直接以字符串的形式编写,参数和值之间使用"="符号连接,多个参数使用空格隔开,如下示例;
- hosts: centos
  remote_user: root
  tasks:
    - name: add user
      user: name=cce state=present  # ansible-playbook支持的字典特殊写法
gather_facts组件
    Ansible在正式只是playbook之前,都会使用setup模块来收集对端主机的信息,所以会导致每次playbook执行的过程都非常漫长,所以在一个playbook中,想要提升执行的效率,我们一般都会禁止收集对端主机的这个操作,那么禁止这个操作就需要,需要用到一个gather_facts组件,如下示例;
- hosts: centos
  gather_facts: no # 禁止收集主机信息
  remote_user: root
  tasks:
    - name: add user
      user: name=cce state=present
基础实践一
    到此,已经可以实现基础的Playbook清单配置了,那么接下来,我们也就可以通过Playbook来实现基本的自动化任务了,如下,通过Playbook来实现Nginx的部署与源码文件的分发;
[cce@doorta /usr/local/Project/linux]# tree
├── files
│   └── index.html
└── palybook.yaml
[cce@doorta /usr/local/Project/linux]# cat files/index.html 
<h1>Welcome To Nginx</h1>
[cce@doorta /usr/local/Project/linux]# cat palybook.yaml
- hosts: 172.16.1.2
  gather_facts: no
  remote_user: root
  tasks:
    - name: install nginx
      yum: name=nginx state=present
    - name: copy file
      copy: src=files/index.html dest=/usr/share/nginx/html/ owner=nginx group=nginx mode=644
    - name: start nginx
      systemd: name=nginx state=started enabled=yes
[cce@doorta /usr/local/Project/linux]# ansible-playbook palybook.yaml
[cce@doorta /usr/local/Project/linux]# curl 172.16.1.2
<h1>Welcome To Nginx</h1>
Handlers和Notify组件
    Handlers和Notify组件是配对使用的,实际它们主要就是实现触发器的一个功能,因为Ansible有一个特性,如果执行的操作已经执行过一边那么就不会重新执行,甚至有的没法对比是否已经执行过的模块,每次执行Palybook会重复执行,比如systemd模块;
    那么我们如果希望一个ansible任务状态为changge时,才去做一些操作,比如当Nginx的配置文件发生改变时,才利用systemd模块执行restarted操作,但是systemd模块没办法检测到上一次执行palybook是否已经执行过,所以如果将systemd模块放在tasks组件中,每次执行palybook都会执行,这样就得不偿失了,所以在这种场景下,我们可以使用Handlers结合Notify组件来实现;
    对于Handlers和Notify组件的使用也非常简单,Handlers是类似tasks一样的组件,可以在Handlers组件下定义和Tasks组件下一样的任务,但是这些任务只有在配合Notify才能使用;
    而Notify需要写在tasks任务列表的某一个任务当中,它的值是Handlers内部Handler的name名称,表示,该任务发生changge事件时,会调用Handlers内指定name的任务,如下示例;
[cce@doorta /usr/local/Project/linux]# tree
├── files
│   └── nginx.conf
└── palybook.yaml
[cce@doorta /usr/local/Project/linux]# cat palybook.yaml
- hosts: 172.16.1.2
  gather_facts: no
  remote_user: root
  tasks:
    - name: install nginx
      yum: name=nginx state=present
    - name: copy config
      copy: src=files/nginx.conf dest=/etc/nginx/nginx.conf owner=nginx group=nginx force=yes # 虽然强制覆盖,但是如果md5一样,则不会覆盖
      notify: reload nginx # 如果该任务发生changged事件,则调用name为reload nginx的handler
    - name: start nginx
      systemd: name=nginx state=started enabled=yes
  handlers: # 定义handler
    - name: reload nginx
      systemd: name=nginx state=reloaded
# 当执行一次之后修改配置文件再次执行结果如下
[cce@doorta /usr/local/Project/linux]# ansible-playbook palybook.yaml
PLAY [172.16.1.2] 
*****************************************************************************
TASK [install nginx] 
ok: [172.16.1.2]
*****************************************************************************
TASK [copy config] 
changed: [172.16.1.2]
*****************************************************************************
TASK [start nginx] 
ok: [172.16.1.2]
*****************************************************************************
RUNNING HANDLER [reload nginx] 
changed: [172.16.1.2]
*****************************************************************************
PLAY RECAP 
172.16.1.2 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# 测试结果
[cce@doorta /usr/local/Project/linux]# curl 172.16.1.2:81
<h1>Welcome To Nginx</h1>
  • 注意:主要注意一点,就是notify的值,必须是handlers组件内某个任务的name值,名称必须一致,那么同时,一个Handlers组件内,同样可以定义多个任务;
多任务触发
    notify的值除了是一个字符串之外,也可以是一个列表,也就是说一个任务可以触发多个handler的执行,如下示例;
 - hosts: 172.16.1.3
  gather_facts: no
  remote_user: root
  tasks:
    - name: install nginx
      yum: name=nginx state=present
    - name: copy config
      copy: src=files/nginx.conf dest=/etc/nginx/nginx.conf owner=nginx group=nginx force=yes
      notify:  # 多任务触发
      - reload nginx
      - check nginx process
    - name: start nginx
      systemd: name=nginx state=started enabled=yes
  handlers: # 定义handler
    - name: reload nginx
      systemd: name=nginx state=reloaded
    - name: check nginx process
      shell: killall -0 nginx # 检测进程是否存在
Tags组件
    Tags组件的主要功能就是将每个任务创建一个标识符,这样我们在使用ansible-playbook命令行时,就可以选择性的去执行某个标识符代指的任务,而不是palybook清单当中所有的任务;
[cce@doorta /usr/local/Project/linux]# cat palybook.yaml
- hosts: 172.16.1.1
  gather_facts: no
  remote_user: root
  tasks:
    - name: add user
      user: name=cce uid=1000 groups=root state=present
      tags: add
    - name: del user
      user: name=cce state=absent remove=yes
      tags: del
# 添加用户
[cce@doorta /usr/local/Project/linux]# ansible-playbook palybook.yaml -t add
PLAY [172.16.1.1]
*****************************************************************************
TASK [add user] 
*****************************************************************************
changed: [172.16.1.1]
PLAY RECAP
*****************************************************************************
172.16.1.1 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# 删除用户
[cce@doorta /usr/local/Project/linux]# ansible-playbook palybook.yaml -t del
PLAY [172.16.1.1] 
*****************************************************************************
TASK [del user]
*****************************************************************************
changed: [172.16.1.1]
PLAY RECAP
*****************************************************************************
172.16.1.1                 : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
Vars组件
    在Palybook配置清单当中,也支持变量的定义与引用,实现灵活的任务处理,在一个Palybook当中,定义变量主要使用Vars组件,Vars组件是一个对象,对象内的属性就是所定义的变量名和变量值,变量名的定义仅能由字母、数组、下划线组成,且只能以字母开头;
    然后再Palybook当中想要引用这个变量需要使用Mustache语法来引用,即"{{ var_name }}",如下示例;
- hosts: 172.16.1.1
  gather_facts: no
  remote_user: root
  vars:
    user_name: cfj  # 定义变量
  tasks:
    - name: add user
      user: name={{user_name}} uid=1000 groups=root  # 引用变量
      tags: add
命令行传递变量
    变量的定义除了在Playbook清单的Vars组件中来定义之外,还可以直接在命令行进行传递,使用ansible-palybook的-e参数来指定,如下示例;
[cce@doorta /usr/local/Project/linux]# cat palybook.yaml 
- hosts: 172.16.1.1
  gather_facts: no
  remote_user: root
  tasks:
    - name: add user
      user: name={{user_name}} uid=1000 groups=root
      tags: add
# 执行playbook剧本,并给playbook剧本传递变量
[cce@doorta /usr/local/Project/linux]# ansible-playbook -e user_name=csw palybook.yaml 
引用Setup模块变量
    在Palybook当中,也可以直接引用setup模块收集到的数据,直接引用即可,无需定义,但是在这种使用场景下,不能禁setup模块,即gather_facts属性,如下示例;
- hosts: 172.16.1.1
  gather_facts: yes
  remote_user: root
  tasks:
    - name: touch file
      shell: touch /tmp/{{ ansible_fqdn }}.log
引用文件变量
    我们也可以直接将变量存放于一个专门配置变量的YAML文件中,然后在Palybook中引用,引用这个文件里面的变量,我们需要用到一个组件,即vars_files组件,见名知义,可以直接引用多个文件内的变量,如下示例;
[cce@doorta /usr/local/Project/linux]# cat palybook.yaml 
- hosts: 172.16.1.1
  remote_user: root
  vars_files:
    - vars/var1.yaml
    - vars/var2.yaml
  tasks:
    - name: install nginx
      yum: name={{ package_name }}
    - name: start nginx
      systemd: name={{ service_name }}
[cce@doorta /usr/local/Project/linux]# cat vars/var1.yaml 
package_name: nginx
[cce@doorta /usr/local/Project/linux]# cat vars/var2.yaml 
service_name: nginx
[cce@doorta /usr/local/Project/linux]# ansible-playbook palybook.yaml
引用主机清单变量
    在Playbook当中,也可以直接引用主机配置清单文件中的变量,当然,主机变量和组变量都可以在playbook当中直接使用,如下示例;
[cce@doorta ~]# cat /etc/ansible/hosts
[centos]
172.16.1.1 package_name=nginx
172.16.1.2

[centos:vars]
service_name=nginx
[cce@doorta /usr/local/Project/linux]# cat palybook.yaml 
- hosts: 172.16.1.1
  remote_user: root
  tasks:
    - name: install nginx
      yum: name={{ package_name }}
    - name: start nginx
      systemd: name={{ service_name }}
  • 注意:主机变量优先级更高,分组变量的优先级较低,如果多个主机存在同一个变量,那么每个主机只会使用属于自己的变量;
when组件
    在每一个task任务当中,我们可以加入一个when属性,when属性是一个条件判断的属性,它的值是一个表达式,当表达式成立时,该task才会在目标主机执行,如下,通过when来判断setup收集的主机信息里面的主机名来判断,如果主机名为node1,那么就执行该task;
[cce@doorta /usr/local/Project/linux]# cat palybook.yaml
- hosts: all
  remote_user: root
  tasks:
    - name: add user
      user: name=cce state=present
      when: ansible_hostname == "node1"
[cce@doorta /usr/local/Project/linux]# ansible-playbook palybook.yaml
PLAY [all]
*****************************************************************************
TASK [Gathering Facts] 
*****************************************************************************
ok: [172.16.1.2]
ok: [172.16.1.1]
TASK [add user] 
*****************************************************************************
skipping: [172.16.1.2]
changed: [172.16.1.1]
PLAY RECAP 
*****************************************************************************
172.16.1.1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0   
172.16.1.2 : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0   
with_items组件
    with_items组件的主要功能就是实现循环,当同一个task需要执行多次时,我们可以with_items组件,with_items组件是一个列表,这个列表内的元素可以是Ansible支持的任务数据类型;
    任务循环的次数,主要看with_items列表内有多少个元素,每次循环,都会将不同的元素交给task,然后,在task中可以直接使用Mustache语法来引用这个迭代出来的元素,引用的变量名为"item",如下示例;
[cce@doorta /usr/local/Project/linux]# cat palybook.yaml
- hosts: 172.16.1.1
  remote_user: root
  tasks:
    - name: add user
      user: name={{ item.name }} uid={{ item.uid }} state=present
      with_items:
        - name: cce
          uid: 1000
        - name: cfj
          uid: 2000
[cce@doorta /usr/local/Project/linux]# ansible-playbook palybook.yaml
PLAY [172.16.1.1]
*****************************************************************************
TASK [Gathering Facts]
*****************************************************************************
ok: [172.16.1.1]
TASK [add user]
*****************************************************************************
changed: [172.16.1.1] => (item={'name': 'cce', 'uid': 1000})
changed: [172.16.1.1] => (item={'name': 'cfj', 'uid': 2000})
PLAY RECAP
*****************************************************************************
172.16.1.1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# 查看结果
[root@node1 ~]# tail -2 /etc/passwd
cce:x:1000:1000::/home/cce:/bin/bash
cfj:x:2000:2000::/home/cfj:/bin/bash

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注