> ansbile-playbook是一系列ansible命令的集合,利用yaml 语言编写。playbook命令根据自上而下的顺序依次执行。同时,playbook开创了很多特性,它可以允许你传输某个命令的状态到后面的指令,如你可以从一台机器的文件中抓取内容并附为变量,然后在另一台机器中使用,这使得你可以实现一些复杂的部署机制,这是ansible命令无法实现的。

playbook通过ansible-playbook命令使用,它的参数和ansible命令类似,如参数-k(–ask-pass) 和 -K (–ask-sudo) 来询问ssh密码和sudo密码,-u指定用户,这些指令也可以通过规定的单元写在playbook 。

一. ansible核心功能:

pyYAML-----用于ansible编写剧本所使用的语言格式(saltstack---python)

rsync-ini语法 sersync-xml语法 ansible-pyYAML语法

paramiko---远程连接与数据传输

Jinja2-----用于编写ansible的模板信息

二. ansible剧本编写规则说明

yml 语法简介 核心规则:有效的利用空格进行剧本的编写,剧本编写是不支持tab的

  • 大小写敏感
  • yaml使用一个固定的缩进风格表示数据层结构关系
  • 缩进时不允许使用Tab键,只允许使用空格。
  • 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
  • 每个冒号后面一定要有一个空格(以冒号结尾不需要空格,表示文件路径的模版可以不需要空格)
CMD="echo"
  yaml:
  mykey:
  • # 表示注释,从这个字符一直到行尾,都会被解析器忽略
  • YAML 还有一个小的怪癖. 所有的 YAML 文件(无论和 Ansible 有没有关系)开始行都应该是 ---. 这是 YAML 格式的一部分, 表明一个文件的开始.
  • 想要表示列表项,使用一个 "- " 作为开头(一个横杠和一个空格)。多个项使用同样的缩进级别作为同一个列表的一部分列表中的所有成员都开始于相同的缩进级别
  • 一个字典是由一个简单的 键: 值 的形式组成(这个冒号后面必须是一个空格)
  • Ansible 使用 “{{ var }}” 来引用变量,foo: "{{ variable }}"。

参考 Ansible中文权威指南 - YAML 语法 阮一峰 - YAML 语言教程

三. 剧本书写格式

下面给出一个简单的ansible-playbook示例,了解下其构成。

# cat user.yml
- name: create user
  hosts: all
  user: root
  gather_facts: false
  vars:
  - user: "test"
  tasks:
  - name: create  user
    user: name="{{ user }}"
-----------------------------------------------------------------------------------------------------------------------------------------
上面的playbook 实现的功能是新增一个用户:

 name参数对该playbook实现的功能做一个概述,后面执行过程中,会打印 name变量的值 ;

 hosts:用于指定要执行指定任务的主机其可以是一个或多个由冒号分隔主机组。
 
 remote_user :用于指定远程主机上的执行任务的用户。不过remote_user也可用于各task中。也可以通过指定其通过sudo的方式在远程主机上执行任务其可用于play全局或某任务。此外甚至可以在sudo时使用sudo_user指定sudo时切换的用户。
 
 user :指定了使用什么用户登录远程主机操作;
 
 sudo:如果设置为yes,执行该任务组的用户在执行任务的时候,获取root权限
 
 sudo_user:如果设置user为breeze,sudo为yes,sudo_user为bernie时,则breeze用户在执行任务时会获得bernie用户的权限

 connection:通过什么方式连接到远程主机,默认为ssh

 gather_facts:指定了在以下任务部分执行前,是否先执行setup模块获取主机相关信息,这在后面的task会使用到setup获取的信息时用到。默认自动执行,如果不需要setup模块传递过来的变量,则可以将该选项设置为False;

 vars参数,指定了变量,这里指字一个user变量,其值为test ,需要注意的是,变量值一定要用引号引住;

 task指定了一个任务,其下面的name参数同样是对任务的描述,在执行过程中会打印出来。user提定了调用user模块,name是user模块里的一个参数,而增加的用户名字调用了上面user变量的值。

四. playbook的构成

playbook是由一个或多个“play”组成的列表。play的主要功能在于将事先归并为一组的主机装扮成事先通过ansible中的task定义好的角色。从根本上来讲所谓task无非是调用ansible的一个module。将多个play组织在一个playbook中即可以让它们联同起来按事先编排的机制同唱一台大戏。其主要有以下四部分构成 playbooks组成

Target section:   定义将要执行 playbook 的远程主机组
  Variable section: 定义 playbook 运行时需要使用的变量
  Task section:     定义将要在远程主机上执行的任务列表
  Handler section:  定义 task 执行完成以后需要调用的任务

细化一点的话playbook的结构为:

Inventory:操作的主机 *
Modules:使用的模块 *
Ad Hoc Commands:在主机上指定的命令
Playbooks
  Tasks:任务,即调用模块完成的某操作 *
  Variables:变量
  Templates:模板
  Handlers:处理器,由某事件触发完成的操作
  Roles:角色

target 配置项目

- hosts:定义远程的主机组
- user:执行该任务的用户
- remote_user: 远端运行该play的用户,可以细分到每一个task
- *sudo:如果设置为yes,执行该任务组的用户在执行任务的时候,获取root权限
- *sudo_user: 如果你设置user为tom,速度设置为yes,sudo_user为jerry,则tom用户则会获取jerry用户的权限
- connection:通过什么方式连接到远程主机,默认为ssh
- become:是否提权
- become_method: 提权得方式(sudo)
- become-user=root - 以用户“root”运行以下命令(例如,使用命令使用“sudo”)。我们可以在此定义任何现有的用户
- notify:当任务执行完毕后,通知handler section 中的任务模块(出发执行)
- set_fact: 模块可以自定义facts,这些自定义的facts可以通过template或者变量的方式在playbook中使用。如果你想要获取一个进程使用的内存的百分比,则必须通过set_fact来进行计算之后得出其值,并将其值在playbook中引用。set_fact: innodb_buffer_pool_size_mb="{{ ansible_memtotal_mb / 2 }}"
- gather_facts:除非你明确说明不需要在远程主机上执行setup模块,否则会默认自动执行。如果你的确不需要setup模块所传递过来的变量,你可以启用该选项
# 可以使用setup获取到的字典中的key,来渲染对应的值
# 当值的类型为字典嵌套时,可以使用点来进行深入查找 {{ansible_eth0.ipv4.address}}
# 当值的类型为列表时,{{ansible[0].xxx}}

variable 可用的配置项目

- vars:定义一个变量。key:value,多个变量,可以使用{key:value,key2:value2}
- vars_files:定义一个变量文件。这里列出变量文件的绝对路径即可。 变量文件中的直接使用key:value定义即可。
- vars_prompt: 通过交互的方式输入的值
- name:变量的值
- prompt:提示信息
- private:是否为私有变量,yes | no, yes时输入的变量不在屏幕上显示,否则会显示在屏幕上

task 配置项目 play的主体部分是task list,task list中的各任务被按次序诸葛在hosts中 指定的所有主机上运行,即在所有主机上完成第一个任务后再开始第二个.在运行自上而下某个playbook时,如果中途发生错误,所有已执行任务都可以能回滚,因此,在更正playbook后重新执行一次即可.

task的目的是使用指定的参数运行模块,而在模块参数中可以使用变量,模块执行是幂等的,这意味着多次执行是安全的,因为其结果一致.

每个task都应该有其name,用于playbook的执行结果输出,建议其内容尽可能清晰的描述任务执行步骤。如果未提供name,则action的结果将用于输出。

定义任务的三种方式:

# 方式1(action)
- name: task的名称
  action:要做的操作
# 例如: action: yum name= httpd state=×××talled
 
# 方式2(直接写模块名)
- name: task的名称
  copy: src=files/httpd.conf dest=/etc/httpd/conf/httpd.conf
 
# 方式3(模块名时,模块的参数也可以用下列方式使用)
- name: task的名称
  service:
    name:httpd
    state:restarted

五. 剧本编写后检查/运行方法

#进行剧本配置信息语法检查
ansible-playbook --syntax-check 01.yml 

#模拟剧本执行(测试运行)
ansible-playbook -C 01.yml
–list-hosts 
–list-tasks 
–list-tags 

#查看输出的细节
ansible-playbook 01.yml --verbose

#并行执行脚本
ansible-playbook 01.yml -f 10

#输入密码
//playbook 中使用到了 become,执行playbook时可以加上--ask-become-
//pass参数:
ansible-playbook deploy.yml --ask-become-pass

#运行 
ansible-playbook 01.yml  
-t TAGS, –tags=TAGS 
–skip-tags=SKIP_TAGS 
–start-at-task=START_AT

六. 响应事件 Hanlder

handlers与tasks不同,tasks会默认的按定义顺序执行每一个task,handlers则不会,它需要在tasks中被调用,才有可能被执行。

> Handlers will only be fired for tasks which report a changed state. > 只有当 task 执行状态显示是 changed 时,handler 动作才会被触发

Tasks中的任务都是有状态的,changed或者ok。 在Ansible中,只在task的执行状态为 changed 的时候,才会执行该task调用的handler。

在所有的task列表执行之后执行,如果有多个task notify同一个handler,那么 handlers 也只执行一次。

什么情况下使用handlers呢? 如果你在tasks中修改了apache的配置文件。需要重起apache。此外还安装了apache的插件。那么还需要重起apache。像这样的应该场景中,重起apache就可以设计成一个handler.

当一个文件的内容被改动时,重启两个 services:

- name: template configuration file
  template: src=template.j2 dest=/etc/foo.conf
  notify:
     - restart memcached
     - restart apache
  • notify 下列出的即是 handlers.
  • Handlers 也是一些 task 的列表,通过名字(name)来引用。
  • Handlers 是由通知者进行 notify, 如果没有被 notify,handlers 不会执行。
  • 不管有多少个通知者进行了 notify,等到 play 中的所有 task 执行完成之后,handlers 也只会被执行一次.
handlers:
  - name: restart memcached
    service:  name=memcached state=restarted
  - name: restart apache
    service: name=apache state=restarted
  • handlers是按照在handlers中定义个顺序执行的,而不是安装notify的顺序执行的。比如,handlers 定义的顺序是1>2>3,notify 的顺序是3>2>1,实际执行顺序:1>2>3. 总结,Handlers 最佳的应用场景是用来重启服务,或者触发系统重启操作.除此以外很少用到。

参考

七. 变量

playbook 中常用的集中变量:

  1. 在 playbook 中,用户自定义的变量
  2. 无需用户定义,ansible 在执行 playbook 之前,去远程主机上搜集的关于远程主机的系统信息变量
  3. task 运行的结果「注册」为一个变量来使用,这个变量叫做「注册变量」
  4. 允许用户在执行的时候传入变量的值,这时候用到的是「额外变量」

playbook 中定义的变量 在 playbook 中,通过关键字 vars 自定义变量,用 {{}} 引用变量。

将变量放在单独的文件中 通过 vars_files 关键字指定了变量文件:

---
- hosts: centos
  vars:
    httpd_port: 80
  vars_files:
      - ./vars_servers.yml
  remote_user: root
  tasks:
  - debug:
      msg: "http_port: {{httpd_port}}"
  - debug:
      msg: "x86 passwd: {{x86.password}}"
  - debug:
      msg: "arm passwd: {{arm.password}}"
      # 也可以用 arm['password'] 表示

专门存放变量的文件:

# vars_servers.yml
x86:
    password: 123
arm:
    password: 456

远程节点的系统变量(facts) ansible 通过 module setup 收集主机的系统信息,这些收集到的系统信息叫做 facts,这些facts可以直接以变量的形式使用。

哪些 facts 变量可以引用的?通过如下命令行调用setup module 可以查看: ansible all -m setup -u root 可以看到它输出的变量信息有很多!

复杂的facts变量的使用可以用如下两种形式:

  • {{ ansible_ens3["ipv4"]["address"] }}
  • {{ ansible_ens3.ipv4.address }}

好用的一些 facts 变量

  • ansible_hostname 指定的 host 名称
  • ansible_default_ipv4.address 主机真实的 ipv4 地址,小网IP

ansible_os_family 查看系统类型的变量#

---
- hosts: all
  user: root
  tasks:
  - name: echo system
    shell: echo {{ ansible_os_family }}
  - name install ntp on Debian linux
    apt: name=git state=installed
    when: ansible_os_family == "Debian"
  - name install ntp on redhat linux
    yum: name=git state=present
    when: ansible_os_family == "RedHat"

关闭 facts 在 playbook 中,如果不收集系统信息,那么上面的变量就不能再 playbook 中使用了,但是有时候关闭会加快执行的效率:

- hosts: all
  gather_facts: no

注册变量 register 将某个 task 执行的结果「注册」为一个变量。后面的 action 就可以使用它

---
- hosts: centos
  tasks:
      - name: ls /tmp
        shell: ls -l /tmp
        register: result
        ignore_errors: True

      - name: echo result when rc==5
        shell: echo "{{result}}"
        when: result.rc == 5

      - name: debug show stdout
        debug:
          msg: "{{result.stdout}}"

「注册变量」经常和debug module一起使用,这样可以获得 action 更多的输出信息,帮助调试。

参考

  • 注册Ansible变量属性 介绍到了一个set_facts 的模块
  • 朱双印-ansible 变量5
  • Ansible系列(五):各种变量定义方式和变量引用

命令行传递变量 --extra-vars#

---
- hosts: "{{hosts}}"
  remote_user: "{{user}}""

  tasks:
    - debug: msg="{{hosts}}""

命令输入变量:

ansible-playbook extra_learn.yml --extra-vars "{'hosts':'x86','user':‘’michael'}"

# or
ansible-playbook extra_learn.yml --extra-vars "hosts=x86 user=michael"

playbook 中的逻辑控制语句

  • when:条件判断,类似编程语言中的 if
  • loop:循环,类似编程语言中的 while
  • block:将几个 task 组成一块代码,便于针对一组操作进行异常处理等

条件语句 when 例如,在某个特定版本的系统上装包,或者只在磁盘空间满了的文件系统上执行清理操作。这些操作在Playbook中用when语句实现。

主机为Debian Linux立刻关机

tasks:
  - name: "shutdown Debian flavored systems"
    command: /sbin/shutdown -t now
    when: ansible_os_family == "Debian"

根据action的执行结果,来决定接下来执行的action。

tasks:
  - command: /bin/false
    register: result
    ignore_errors: True
  - command: /bin/something
    when: result|failed
  - command: /bin/something_else
    when: result|success
  - command: /bin/still/something_else
    when: result|skipped

远程中的系统变量facts变量作为when的条件,用“|int”还可以转换返回值的类型:

---
- hosts: web
  tasks:
    - debug: msg='only on Red Hat 7, derivatives, and later'
      when: ansible_os_family == 'RedHat' and ansible_lsb.major_release|int >= 6

循环语句 loop

  • 标准循环 with_items

为了保持简洁,重复的任务可以用以下简写的方式:

---
- name: add several users
  user: name="{{ item }}" state=present groups=wheel
  with_items:
     - michael
     - qq

如果你在变量文件中或者 ‘vars’ 区域定义了一组YAML列表,你也可以这样做:

vars:
  userlist: ["micahel", "qq"]
tasks:
  -name: add several user
   user:
     name: "{{ item }}"
     state: present
     groups: wheel
   with_items: "{{userlist}}"

使用 with_items 用于迭代的条目类型不仅仅支持简单的字符串列表.如果你有一个哈希列表,那么你可以用以下方式来引用子项:

- name: add several users
  user: name="{{ item.name }}" state=present groups="{{ item.groups }}"
  with_items:
    - { name: 'michael', groups: 'wheel' }
    - { name: 'qq', groups: 'root' }
  • 对哈希表使用循环 with_dict

这个例子不仅演示了 with_dict 用法,还使用循环安装了 RPM 包

---
- hosts: centos
  vars:
    users:
      michael:
        name: michael xiang
        phone: 123
      qq:
        name: qq huang
        phone: 456

    rpms:
        - httpd
        - lrzsz
        - vim
        - git

  tasks:
    - name: print phone records
      debug: msg="User {{item.key }} is {{ item.value.name }} {{item.value.phone}}"
      with_dict: "{{ users }}"

    - name: install rpms
      yum: name="{{item}}" state=installed
      with_items: "{{rpms}}"
  • 对文件列表使用循环 with_filegloab

with_fileglob 可以以非递归的方式来模式匹配单个目录中的文件.如下面所示:

tasks:

    # first ensure our target directory exists
    - file: dest=/etc/fooapp state=directory

    # copy each file over that matches the given pattern
    - copy: src=\{\{ item \}\} dest=/etc/fooapp/ owner=root mode=600
      with_fileglob:
        - /playbooks/files/fooapp/*

参考

多个action组装成块,可以根据不同条件执行一段语句 :

tasks:
     - block:
         - yum: name=\{\{ item \}\} state=installed
           with_items:
             - httpd
             - memcached

         - template: src=templates/src.j2 dest=/etc/foo.conf

         - service: name=bar state=started enabled=True

       when: ansible_distribution == 'CentOS'
       become: true
       become_user: root

实例

1、场景一

- hosts: nginx
  remote_user: root
  gather_facts: false
  tasks:
    - name: push nginx file 
      synchronize:
        src: /data/nginx-config/
        dest: /etc/nginx/
        archive: no
        mode: push
        delete: yes
        recursive: yes
        rsync_timeout: 300 
        rsync_opts:
          - "--exclude=.git"
      tags:
        - push_files
    - name: Configuring directory permissions
      file:
        path: /etc/nginx
        owner: nginx
        group: nginx 
        mode: 0644
        recurse: yes
      tags:
        - config_permission
    - name: nginx service reload
      shell: /opt/openresty/nginx/sbin/nginx -s reload
      tags:
        - nginx_reload

2、场景二

ansible-playbook -i $nginx_bench --extra-vars "CTIME=2019-06-11-11-11-11"    update_nginx_bench.yml
- hosts: all
  gather_facts: no
  gather_facts: F
  remote_user: root
  vars: {down_url: "http://10.10.10.1:88/bench/NGINX_{{ CTIME }}.tar.gz", down_file: "NGINX_{{ CTIME }}.tar.gz"}
  tasks:
    - shell: mkdir /data/deploy/nginx -p ; cd /data/deploy/nginx && rm -rf NGINX_*.tar.gz ; wget -q  {{down_url}}
    - shell: echo "NGINX_"$(stat --printf="%y"  /data/deploy/nginx/{{down_file}} |sed "s/ /-/" |sed "s/:/-/g" |cut -b 1-19)
      register: dirname
    - shell: ls {{dirname.stdout}} >/dev/null 2>&1  && echo "true" || echo "false"  executable=/bin/bash chdir=/data/deploy/nginx/
      register: already_update
    - fail: msg="当前service链接版本和最新git包版本一致,无需更新! 请检查有无打包,或登录ECS检查!"
      when: already_update.stdout == "true"
    - shell: source $HOME/.bashrc && source $HOME/.bash_profile  && mkdir {{dirname.stdout}} && cd {{dirname.stdout}} && wget -q  {{down_url}} && tar zxvf {{down_file}} executable=/bin/bash chdir=/data/deploy/nginx
    - file : path=/etc/nginx  state=link src=/data/deploy/nginx/{{dirname.stdout}}
    - shell: ls -l nginx   executable=/bin/bash chdir=/etc
      register: ls
    - shell: nginx -t && echo "true" || echo "false"
      register: nginx_check
    - fail: msg="Nginx 配置文件有误,请检查!!!"
      when: nginx_check.stdout == "false"
    - name: check_nginx
      debug: var=nginx_check.stdout verbosity=0
    - pause: seconds=10   prompt="10秒后重新加载配置:"
    - shell: systemctl reload nginx
      when: nginx_check.stdout == "true"
    - shell: systemctl status nginx
      register: nginx_status
    - name: show_status
      debug: var=nginx_status.stdout verbosity=0
    - shell: find . -type d -name "NGINX_*" -mtime +30 -exec rm {} \; executable=/bin/bash chdir=/data/deploy/nginx/

标签

如果你有一个大型的 playbook,那能够只运行其中特定部分的配置而无需运行整个 playbook 将会很有用.

plays 和 tasks 都因这个理由而支持 “tags:” 例:

tasks:

    - yum: name={{ item }} state=installed
      with_items:
         - httpd
         - memcached
      tags:
         - packages

    - template: src=templates/src.j2 dest=/etc/foo.conf
      tags:
         - configuration

如果你只想运行一个非常大的 playbook 中的 “configuration” 和 “packages”,你可以这样做: ansible-playbook example.yml --tags "configuration,packages"

另一方面,如果你只想执行 playbook 中某个特定任务 之外 的所有任务,你可以这样做: ansible-playbook example.yml --skip-tags "notification"

你同样也可以对 roles 应用 tags:

roles:
  - { role: webserver, port: 5000, tags: [ 'web', 'foo' ] }

你同样也可以对基本的 include 语句使用 tag:

# vim main.yml

---
- include: install_tomcat7.yml
  tags: tomcat7
- include: install_tomcat8.yml
  tags: tomcat8

以上这样也有对每个 include 语句中的单个任务进行标签的功能.


从指定任务开始运行palybook以及分步运行playbook

以下列出了几种方式来运行playbook.这对于测试或调试新的playbook很有帮助. Start-at-task 如果你想从指定的任务开始执行playbook,可以使用–start-at选项:

ansible-playbook playbook.yml --start-at="install packages" 以上命令就会在名为”install packages”的任务开始执行你的playbook.

分步运行playbook

我们也可以通过–step选项来交互式的执行playbook:

ansible-playbook playbook.yml --step 这样ansible在每个任务前会自动停止,并询问是否应该执行该任务.

比如你有个名为configure ssh的任务,playbook执行到这里会停止并询问:

Perform task: configure ssh (y/n/c): “y”回答会执行该任务,”n”回答会跳过该任务,而”c”回答则会继续执行剩余的所有任务而不再询问你.