一、ansible playbook简介

playbook 是 ansible 用于配置,部署,和管理被控节点的剧本。
  通过 playbook 的详细描述,执行其中的一系列 tasks ,可以让远端主机达到预期的状态。playbook 就像 Ansible 控制器给被控节点列出的的一系列 to-do-list ,而被控节点必须要完成。
  也可以这么理解,playbook 字面意思,即剧本,现实中由演员按照剧本表演,在Ansible中,这次由计算机进行表演,由计算机安装,部署应用,提供对外服务,以及组织计算机处理各种各样的事情。

二、ansible playbook格式

playbook由YAML语言编写。 YAML( /ˈjæməl/ )参考了其他多种语言,包括:XML、C语言、Python、Perl以及电子邮件格式RFC2822,Clark Evans在2001年5月在首次发表了这种语言,另外Ingy döt Net与OrenBen-Kiki也是这语言的共同设计者。
  YMAL格式是类似于JSON的文件格式,便于人理解和阅读,同时便于书写。首先学习了解一下YMAL的格式,对我们后面书写playbook很有帮助。以下为playbook常用到的YMAL格式:
  1、文件的第一行应该以 “- - -” (三个连字符)开始,表明YMAL文件的开始。(块分割)
  2、在同一行中,#之后的内容表示注释,类似于shell,python和ruby。
  3、YMAL中的列表元素以”-”开头然后紧跟着一个空格,后面为元素内容。
  4、同一个列表中的元素应该保持相同的缩进。否则会被当做错误处理。(不支持空格)
  5、play中hosts,variables,roles,tasks等对象的表示方法都是键值中间以":“分隔表示,”:"后面还要增加一个空格。

文件名称应该以.yml结尾
host部分:使用 hosts 指示使用哪个主机或主机组来运行下面的 tasks ,每个 playbook 都必须指定 hosts ,hosts也可以使用通配符格式。主机或主机组在 inventory 清单中指定,可以使用系统默认的/etc/ansible/hosts,也可以自己编辑,在运行的时候加上-i选项,指定清单的位置即可。在运行清单文件的时候,–list-hosts选项会显示那些主机将会参与执行 task 的过程中。
remote_user:指定远端主机中的哪个用户来登录远端系统,在远端系统执行 task 的用户,可以任意指定,也可以使用 sudo,但是用户必须要有执行相应 task 的权限。
tasks:指定远端主机将要执行的一系列动作。tasks 的核心为 ansible 的模块,前面已经提到模块的用法。tasks 包含 name要执行的模块,name 是可选的,只是为了便于用户阅读,不过还是建议加上去,模块是必须的,同时也要给予模块相应的参数。

1、核心元素

Hosts:主机组;
Tasks:任务列表;
Variables:变量,设置方式有四种;
Templates:包含了模板语法的文本文件;
Handlers:由特定条件触发的任务;

2、基本组件

Hosts:运行指定任务的目标主机
remote_user:在远程主机上执行任务的用户;
sudo_user
tasks:任务列表
格式:
    tasks:
      – name: TASK_NAME
       module: arguments
       notify: HANDLER_NAME
       handlers:
      – name: HANDLER_NAME
       module: arguments
模块,模块参数
 格式:
    (1) action: module arguments
    (2) module: arguments
    注意:shell和command模块后面直接跟命令,而非key=value类的参数列表;
handlers:任务,在特定条件下触发;接收到其它任务的通知时被触发;
(1) 某任务的状态在运行后为changed时,可通过“notify”通知给相应的handlers;
(2) 任务可以通过“tags“打标签,而后可在ansible-playbook命令上使用-t指定进行调用;

(1)定义playbook
---
- hosts: web
  remote_user: root
  tasks:
    - name: install nginx
      yum: name=nginx state=present
    - name: copy nginx.conf
      copy: src=/tmp/nginx.conf dest=/etc/nginx/nginx.conf backup=yes 
      notify: reload    #当nginx.conf发生改变时,通知相应的handlers
      tags: reloadnginx  #打标签
    - name: start nginx service
      service: name=nginx state=started
      tags: startnginx  #打标签
  handlers:
    - name: reload
      service: name=nginx state=restarted
(2)、运行前三部曲
检查错误
ansible-playbook nginx.yml --syntax-check

列出所有任务
ansible-playbook nginx.yml --list-task

列出在那些机器执行
ansible-playbook nginx.yml --list-hosts

运行
ansible-playbook nginx.yml
(3)执行测试
[root@ansible ~]# ansible-playbook test.yml 

PLAY [web] *****************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************
ok: [192.168.253.135]

TASK [install nginx] *******************************************************************************************
ok: [192.168.253.135]

TASK [copy nginx.conf] *****************************************************************************************
ok: [192.168.253.135]

TASK [start nginx service] *************************************************************************************
changed: [192.168.253.135]

PLAY RECAP *****************************************************************************************************
192.168.253.135            : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

查看端口是否开启
root@ansible ~]# ansible web -m shell -a 'ss -luntp | grep nginx'
192.168.253.135 | CHANGED | rc=0 >>
tcp    LISTEN     0      128       *:80                    *:*                   users:(("nginx",pid=7068,fd=6),("nginx",pid=7066,fd=6))
tcp    LISTEN     0      128    [::]:80                 [::]:*                   users:(("nginx",pid=7068,fd=7),("nginx",pid=7066,fd=7))

测试标签
[root@ansible ~]# ansible web -m shell -a 'systemctl stop nginx'
192.168.253.135 | CHANGED | rc=0 >>

[root@ansible ~]# ansible-playbook test.yml -t startnginx

PLAY [web] *****************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************
ok: [192.168.253.135]

TASK [start nginx service] *************************************************************************************
changed: [192.168.253.135]

PLAY RECAP *****************************************************************************************************
192.168.253.135            : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

测试notify
触发条件是配置文件被改变,所以我们先修改配置
[root@ansible ~]# vim /tmp/nginx.conf 
 	listen       8080;
[root@ansible ~]# ansible-playbook test.yml -t reloadnginx

PLAY [web] *****************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************
ok: [192.168.253.135]

TASK [copy nginx.conf] *****************************************************************************************
changed: [192.168.253.135]

RUNNING HANDLER [reload] ***************************************************************************************
changed: [192.168.253.135]

PLAY RECAP *****************************************************************************************************
192.168.253.135            : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[root@ansible ~]# ansible web -m shell -a 'ss -luntp | grep nginx'
192.168.253.135 | CHANGED | rc=0 >>
tcp    LISTEN     0      128       *:8080                  *:*                   users:(("nginx",pid=7656,fd=6),("nginx",pid=7654,fd=6))
端口变成了我们修改的8080

三、variables部分

variables变量的四种定义方法:
1.hosts文件定义
2.剧本定义
3.系统自动定义
4.命令行定义

1、facts

setup模块是通过调用facts组件实现,variables也可以直接调用facts组件

ansible_all_ipv4_addresses:仅显示ipv4的信息 ---> [u'192.168.95.143']
ansible_eth0['ipv4']['address']:仅显示ipv4的信息 ---> eth0 的ip地址
ansible_devices:仅显示磁盘设备信息
ansible_distribution:显示是什么系统,例:centos,suse等
ansible_distribution_version:仅显示系统版本
ansible_machine:显示系统类型,例:32位,还是64位
ansible_eth0:仅显示eth0的信息
ansible_hostname:仅显示主机名
ansible_kernel:仅显示内核版本
ansible_lvm:显示lvm相关信息
ansible_memtotal_mb:显示系统总内存
ansible_memfree_mb:显示可用系统内存
ansible_memory_mb:详细显示内存情况
ansible_swaptotal_mb:显示总的swap内存
ansible_swapfree_mb:显示swap内存的可用内存
ansible_mounts:显示系统磁盘挂载情况
ansible_processor:显示cpu个数(具体显示每个cpu的型号)
ansible_processor_vcpus:显示cpu个数(只显示总的个数)
ansible_python_version:显示python版本

批量修改主机host文件

---
- hosts: web  
  vars:        
    IP: "{{ ansible_eth0['ipv4']['address'] }}"  
  tasks:        
  - name: 将原有的hosts文件备份          
    shell: mv /etc/hosts /etc/hosts_bak        
  - name: 将ansible端的hosts复制到各自机器上          
    copy: src=/root/hosts dest=/etc/ owner=root group=root mode=0644        
  - name: 在新的hosts文件后面追加各自机器内网ip和hostname          
    lineinfile: dest=/etc/hosts line="{{ IP }}  {{ ansible_hostname }} "
2、用户自定义
通过命令行传入:
ansible-playbook -e VARS

在playbook中定义变量:
vars:
	- var1: value1
	- var2: valus2
3、Host Inventory
在主机清单中定义:
#不同主机传递不同变量
IP/HOSTNAME varaiable=value var2=value2		

#向组中的主机传递相同的变量
[groupname:vars]
variable=value

ansible inventory内置参数

ansible_playbook ansibleplaybook格式为_自动化


使用内置变量把用户名密码写在Inventory中,也就是/etc/ansible/hosts文件里,缺点就是暴露了账号密码,不安全。如果有多个主机需要使用同样的变量,可以用组变量的形式,书写格式如下:

[web]
192.168.253.135
192.168.253.136
192.168.253.137
[web:vars]  #给名为webservers的组定义一个变量,:vars是固定格式
ansible_ssh_port=22
ansible_ssh_user='root'
ansible_ssh_pass='1234.com'
4、通过roles传递变量
roles的vars目录下的main.yml文件中定义
[root@ansible roles]# vim vars/main.yml
nginxprot: 8080

四、templates模板

模板是一个文本文件,嵌套有脚本(使用模板编程语言编写)。
  Jinja2:Jinja2是python的一种模板语言,以Django的模板语言为原本。
模板支持:

字符串:使用单引号或双引号;
  数字:整数,浮点数;
  列表:[item1, item2, ...]
  元组:(item1, item2, ...)
  字典:{key1:value1, key2:value2, ...}
  布尔型:true/false
  算术运算:
    +, -, *, /, //, %, **
  比较操作:
    ==, !=, >, >=, <, <=
  逻辑运算:
    and, or, not

通常来说,模板都是通过引用变量来运用的。

1、定义模板

我们直接把之前定义的/tmp/nginx.conf改个名,然后编辑一下,就可以定义成我们的模板文件了:

[root@ansible ~]# cd /tmp
[root@ansible tmp]# mv nginx.conf nginx.conf.j2
[root@ansible tmp]# vim nginx.conf.j2
    worker_processes  {{ ansible_processor_vcpus }};
    listen       {{ nginxport }};
2、修改剧本

我们现在需要去修改剧本来定义变量:

---
- hosts: web
  remote_user: root
  tasks:
    - rpmname: {{ rpmname }}
      nginxport: 8888
    - name: install {{ rpmname }}
      yum: name={{ rpm name}} state=present
    - name: copy {{ rpmname }}
      template: src=/tmp/{{ rpmname }}.conf.j2 dest=/etc/{{ rpmname }}/{{ rpmname }}.conf backup=yes 
      notify: reload    #当{{ rpmname }}.conf发生改变时,通知相应的handlers
      tags: reload{{ rpmname }}  #打标签
    - name: start {{ rpmname }} service
      service: name={{ rpmname }} state=started
      tags: start{{ rpmname }}  #打标签
  handlers:
    - name: reload
      service: name={{ rpmname }} state=restarted
3、运行测试
[root@ansible ~]# ansible-playbook nginx.yml -t reloadnginx

PLAY [web] *****************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************
ok: [192.168.253.135]

TASK [copy nginx.conf] *****************************************************************************************
changed: [192.168.253.135]

RUNNING HANDLER [reload] ***************************************************************************************
changed: [192.168.253.135]

PLAY RECAP *****************************************************************************************************
192.168.253.135            : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

五、条件测试

when语句:在task中使用,jinja2的语法格式。
举例如下:

tasks:
- name: install conf file to centos7
  template: src=files/nginx.conf.c7.j2
  when: ansible_distribution_major_version == "7"
- name: install conf file to centos6
  template: src=files/nginx.conf.c6.j2
  when: ansible_distribution_major_version == "6"

循环:迭代,需要重复执行的任务;
  对迭代项的引用,固定变量名为"item",而后,要在task中使用with_items给定要迭代的元素列表;
举例如下:

tasks:
- name: unstall web packages
  yum: name={{ item }} state=absent
  with_items:
  - httpd
  - php
  - php-mysql

六、字典

ansible playbook 还支持字典功能。举例如下:

- name: install some packages
  yum: name={{ item }} state=present
  with_items:
    - nginx
    - memcached
    - php-fpm
- name: add some groups
  group: name={{ item }} state=present
  with_items:
    - group11
    - group12
    - group13
- name: add some users
  user: name={{ item.name }} group={{ item.group }} state=present
  with_items:
    - { name: 'user11', group: 'group11' }
    - { name: 'user12', group: 'group12' }
    - { name: 'user13', group: 'group13' }

七、角色订制:roles

1、简介

对于以上所有的方式有个弊端就是无法实现复用假设在同时部署Web、db、ha 时或不同服务器组合不同的应用就需要写多个yml文件。很难实现灵活的调用。
  roles 用于层次性、结构化地组织playbook。roles 能够根据层次型结构自动装载变量文件、tasks以及handlers等。要使用roles只需要在playbook中使用include指令即可。简单来讲,roles就是通过分别将变量(vars)、文件(file)、任务(tasks)、模块(modules)及处理器(handlers)放置于单独的目录中,并可以便捷地include它们的一种机制。角色一般用于基于主机构建服务的场景中,但也可以是用于构建守护进程等场景中。

2、roles目录
files/:存储由copy或script等模块调用的文件;
tasks/:此目录中至少应该有一个名为main.yml的文件,用于定义各task;其它的文件需要由main.yml进行“包含”调用;
handlers/:此目录中至少应该有一个名为main.yml的文件,用于定义各handler;其它的文件需要由main.yml进行“包含”调用;
vars/:此目录中至少应该有一个名为main.yml的文件,用于定义各variable;其它的文件需要由main.yml进行“包含”调用;
templates/:存储由template模块调用的模板文本;
meta/:此目录中至少应该有一个名为main.yml的文件,定义当前角色的特殊设定及其依赖关系;其它的文件需要由main.yml进行“包含”调用;
default/:此目录中至少应该有一个名为main.yml的文件,用于设定默认变量;
3、角色定制实例
1、创建角色生成对应目录结构
[root@ansible ~]# ansible-galaxy role init nginx
- Role nginx was created successfully
[root@ansible ~]# tree nginx
nginx
├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── README.md
├── tasks
│   └── main.yml
├── templates
├── tests
│   ├── inventory
│   └── test.yml
└── vars
    └── main.yml

8 directories, 8 files
2、定义配置文件
[root@ansible ~]# wget http://nginx.org/packages/rhel/7/x86_64/RPMS/nginx-1.10.2-1.el7.ngx.x86_64.rpm
[root@ansible ~]# vim nginx/tasks/main.yml
---
# tasks file for nginx
- name: cp rpm for nginx
  copy: src=nginx-1.10.2-1.el7.ngx.x86_64.rpm dest=/tmp/nginx-1.10.2-1.el7.ngx.x86_64.rpm
- name: install
  yum: name=/tmp/nginx-1.10.2-1.el7.ngx.x86_64.rpm state=latest
- name: conf
  template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
  tags: nginxconf
  notify: new conf to reload
- name: start service
  service: name=nginx state=started enabled=true
[root@ansible ~]# cp nginx-1.10.2-1.el7.ngx.x86_64.rpm ./nginx/files/
[root@ansible ~]# cp /tmp/nginx.conf.j2 ./nginx/templates/
[root@ansible ~]# vim nginx/vars/main.yml 
nginxprot: 8848
[root@ansible ~]# vim nginx/handlers/main.yml 
- name: new conf to reload
  service: name=nginx state=restarted
[root@ansible ~]# vim roles.yml
- hosts: web
  remote_user: root
  roles:
    - nginx

[root@ansible ~]# tree ./
./
├── anaconda-ks.cfg
├── nginx
│   ├── defaults
│   │   └── main.yml
│   ├── files
│   │   └── nginx-1.10.2-1.el7.ngx.x86_64.rpm
│   ├── handlers
│   │   └── main.yml
│   ├── meta
│   │   └── main.yml
│   ├── README.md
│   ├── tasks
│   │   └── main.yml
│   ├── templates
│   │   └── nginx.conf.j2
│   ├── tests
│   │   ├── inventory
│   │   └── test.yml
│   └── vars
│       └── main.yml
├── nginx-1.10.2-1.el7.ngx.x86_64.rpm
├── nginx.yml
└── roles.yml

9 directories, 14 files
3、启动服务
[root@ansible ~]# ansible-playbook roles.yml

PLAY [web] **************************************************************************************************************************************************************************

TASK [Gathering Facts] **************************************************************************************************************************************************************
ok: [192.168.253.135]

TASK [cp rpm for nginx] *************************************************************************************************************************************************************
ok: [192.168.253.135]

TASK [nginx : install] **************************************************************************************************************************************************************
changed: [192.168.253.135]

TASK [nginx : conf] *****************************************************************************************************************************************************************
changed: [192.168.253.135]

TASK [nginx : start service] ********************************************************************************************************************************************************
changed: [192.168.253.135]

RUNNING HANDLER [nginx : new conf to reload] ****************************************************************************************************************************************
changed: [192.168.253.135]

PLAY RECAP **************************************************************************************************************************************************************************
192.168.253.135            : ok=6    changed=4    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[root@ansible ~]# ansible web -m shell -a 'ss -luntp | grep 8848'
192.168.253.135 | CHANGED | rc=0 >>
tcp    LISTEN     0      128       *:8848                  *:*                   users:(("nginx",pid=10357,fd=7),("nginx",pid=10356,fd=7))