[TOC]

0x00 快速入门

基础概念
什么是ansible?
答:它是一个”配置管理工具”,它是一个Linux系统上的”自动化运维工具”;

ansible能做什么?
正如其他配置管理工具一样,ansible可以帮助我们完成一些批量任务,或者完成一些需要经常重复的工作。

  • 比如:同时在100台服务器上安装nginx服务,并在安装后启动它们。
  • 比如:将某个文件一次性拷贝到100台服务器上。
  • 比如:每当有新服务器加入工作环境时,你都要为新服务器部署redis服务,也就是说你需要经常重复的完成相同的工作。

ansible优秀的特性:

  • “幂等性”:可以保证我们重复的执行同一项操作时得到的结果是一样的。
    • 举个例子:你想把一个文件拷贝到目标主机的某个目录上,但是你不确定此目录中是否已经存在此文件,当你使用ansible完成这项任务时,就非常简单了,因为如果目标主机的对应目录中已经存在此文件,那么ansible则不会进行任何操作,如果目标主机的对应目录中并不存在此文件,ansible就会将文件拷贝到对应目录中;
    • ansible是”以结果为导向的”,我们指定了一个”目标状态”,ansible会自动判断,”当前状态”是否与”目标状态”一致,如果一致,则不进行任何操作,如果不一致那么就将”当前状态”变成”目标状态”
  • 剧本
  • 模板
  • 角色

其他的一些运维配置管理工具还有puppet或者saltstack而ansible相比较于他们的优点:

  • 使用puppet管理100台主机,就要在这100台主机上安装puppet对应的agent(客户端代理程序),比较繁琐;
  • 不同之处在于ansible只需要依赖ssh即可正常工作,不用在受管主机上安装agent,也就是说只要你能通过ssh连接到对应主机,你就可以通过ansible管理对应的主机。
  • 为了好分辨后面将Ansible主机就是管理主机,受管理的主机叫做受控主机;

参考文档帮助:https://docs.ansible.com/ansible/latest/index.html


1.环境安装与设置

环境设置:采用VMWARE-player模拟环境实现;

10.10.107.222  Master-Ansible管理端
10.10.107.234 Slave-Ansible 受控端(Ubuntu)
10.20.172.235 Slave-Ansible 受控端(Centos)


Ansible安装:

#Step1.我使用yum源的方式安装ansible,因为安装ansible需要epel源,所以我配置了阿里的epel源和centos7系统镜像源,
$ pwd
/etc/yum.repos.d

# cat aliBase.repo
[aliBase]
name=aliBase
baseurl=https://mirrors.aliyun.com/centos/$releasever/os/$basearch/
enabled=1
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/centos/$releasever/os/$basearch/RPM-GPG-KEY-CentOS-$releasever

# cat aliEpel.repo
[aliEpel]
name=aliEpel
baseurl=https://mirrors.aliyun.com/epel/$releasever\Server/$basearch/
enabled=1
gpgcheck=0


#Step2.yum源配置完成后,安装ansible
yum install ansible #此时yum源中对应的版本为ansible-2.8.1-1.el7.noarch.rpm


#Step3.验证安装
$ansible 127.0.0.1 -m ping #使用ansible去ping本机,
127.0.0.1 | SUCCESS => {
"changed": false,
"ping": "pong"
}

安装成功后如果想要通过ansible管理某主机,使用ansible管理必须同时满足两个最基本的条件如下

  • 条件一、ansible所在的主机可以通过ssh连接到受管主机。
  • 条件二、受管主机的IP地址等信息已经添加到ansible的”管理清单”中,如果清单中没有的主机无法通过ansible进行配置管理;

ansible提供一个默认的”清单”文件 /etc/ansible/hosts并且采用ini风格里面有默认的配置示例使用提示;

#由于ansible工作方式,需要将受管主机的IP地址、ssh端口号等信息添加到一个被称作为"清单(Inventory)"的配置文件中
# ansible_port 用于配置对应主机上的sshd服务端口号默认的22号端口,
# ansible_user 用于配置连接到对应主机时所使用的用户名称。
# ansible_ssh_pass 用于配置对应用户的连接密码。
echo "10.10.107.234 ansible_port=22 ansible_user=root ansible_ssh_pass=ubuntu" >> /etc/ansible/hosts #通过ansible主机管理234主机

#当为主机配置别名时,主机的IP地址必须使用anible_host关键字进行指明,否则ansible将无法正确的识别对应的主机。
echo "test ansible_host=10.20.172.104 ansible_port=22 ansible_user=root ansible_ssh_pass=2019" >> /etc/ansible/hosts #设置别名的方式

#注意:上述配置参数都是ansible2.0版本以后的写法,2.0版本之前,应遵从如下写法
ansible_port #应该写成ansible_ssh_port
ansible_user #应该写成ansible_ssh_user/pass
ansible_host #应该写成ansible_ssh_host


#验证清单配置(两台机器都是OK的)
$ansible 10.10.107.234 -m ping
10.10.107.234 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}

ansible test -m ping
test | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}

ansible主控端如何采用密匙来登录受控端?

#首先,生成默认格式的密钥对,私钥与公钥。
ssh-keygen
#然后将生成的公钥加入到10.10.107.234的认证列表
ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]
#好了公钥认证的相关操作配置完成,此刻,我们已经可以通过ansible主机免密码连接到主机60中了。

因为配置了密钥认证,所以可以实现免密码创建ssh连接,既然已经能够免密码创建ssh连接,那么在配置”主机清单”时,就没有必要再提供对应主机的用户名与密码了,所以在完成了密钥认证的相关配置后,我们可以将清单中的配置精简为如下格式。

10.10.107.234 ansible_port=22 #精简模式(默认也是的22号端口,所以可以忽略)
test ansible_host=10.10.107.234 ansible_port=22 #别名模式

安装总结:

  • 在上面我们使用的是ssh账号密码登录,但是在生产环境中为了提高安全性,我们通常会基于密钥进行ssh认证甚至会禁用密码认证;
  • 在接入之前需要将受控端的公匙写入ansible的kown_hosts中;


2.清单配置详解

我们可以在ansible提供的清单配置文件中进行配置我们以该文件进行讲解

$vim /etc/ansible/hosts
# This is the default ansible 'hosts' file.
# It should live in /etc/ansible/hosts

# 示例1.清单支持"分组"功能,我们可以将某些主机分为一组,然后通过组名去管理组内的所有主机。
# 比如,主机234和主机235都属于A模块的服务器,主机221属于B模块的服务器,我们则可以在清单中进行如下配置
[A]
10.10.107.234
10.10.107.235

[B]
10.10.107.221


# 示例2.如果两台主机的IP地址是连续的我们可以使用更简洁的方法,配置A组中的受管主机,示例如下
[A]
10.10.107.[234:235]

[B]
10.10.107.221


# 示例3. 使用主机名配置受管主机的前提是ansible主机可以正确解析对应的主机名,比如,我们想要通过主机名配置两台主机,示例如下。
[A]
db01.weiyigeek.net
db02.weiyigeek.net

[B]
db0[1:2].weiyigeek.net


# 示例4.为了更加的灵活的管理受管主机,可能需要在组内嵌套组。
#比如,服务器环境从大类上可以分为"生产环境"和"测试环境",把主机分成了两组生产组和测试组,但是生产环境又包含很多业务模块,
#比如,A模块生产组、B模块生产组,同理测试环境中也会有同样的问题,比如A模块测试环境组,B模块测试组,这时我们就需要更加细化的进行分组,示例如下
[testA]
10.10.107.1

[testB]
10.10.107.2

#而master组中包含"子组",没错,"children"关键字表示当前组中存在子组就是testA组和testB组
[Master:children]
test[A:B]

验证配置结果:

#验证1.A组中包含主机60与61,B组中包含主机70,经过上述配置后,我们可以通过组名去管理组内的所有主机,示例如下。
ansible A -m ping
ansible B -m ping
ansible all -m ping #将配置文件中所有的主机进行ping操作

#验证4.如我们需要针对生产环境中的所有主机进行操作时,调用master组即可
ansible Master -m ping

WeiyiGeek.验证1



其实Ansible的清单文件/etc/ansible/hosts不仅能够识别INI的配置语法还能够识别”YAML”的配置语法。
注意,为了使缩进显得更加明显,此处每次缩进使用两个空格

$vim /etc/ansible/hosts
#使用YAML语法配置的主机清单非常简单下面就是他的配置示例

#示例1.所有受管理组演示
all: #管理清单中的所有主机的一个组,这里的"all:"就是这个含义
hosts: #第二行开头使用一个空格作为缩进,使用hosts关键字,表示hosts属于all的下一级,(后面的都是采用两个空格)
10.10.107.1
10.10.107.2
#上例相当于如下INI配置
10.1.1.60
10.1.1.61



#示例2.ini风格对比yaml
#先看一个INI风格的配置表示当前清单中有3台受管主机,主机61不属于任何组,主机60属于test1组,主机70属于test2组
10.1.1.61

[test1]
10.1.1.60

[test2]
10.1.1.70

#使用YAML语法进行同等效果的配置如下
#当直接在清单中创建组时,需要在all关键字内使用children关键字,而定义每个组时,有必须使用hosts关键字,指明组内的主机
all:
hosts:
10.1.1.61:
children: #分组都是children
test1:
hosts:
10.1.1.60:
test2:
hosts:
10.1.1.70:
#从上例可以看出,

组中嵌套时候:

#仍然先写出INI风格的示例以作对比,如下
[proA]
10.1.1.60

[proB]
10.1.1.70

[pro:children]
proA
proB

#对应YAML格式的配置如下pro组有两个子组,分别为proA组和proB组,而这两个组分别有自己组内的主机。
all:
children:
pro:
children:
proA:
hosts:
10.1.1.60
proB:
hosts:
10.1.1.70

当我们使用YAML语法配置清单时,无非是使用hosts、children等关键字与我们的自定义名称进行排列组合罢了。

认证管理yaml配置:

#ini格式如下:
10.1.1.6
test7 ansible_host=10.1.1.7 ansible_port=22
localhost ansible_connection=local

#yaml的配置语法
all:
hosts:
10.1.1.6
test7:
ansible_host: 10.1.1.7 #注意冒号后都有空格,注意yaml语法
ansible_port: 22
localhost:
ansible_connection: local

综合示例:

#以下是如何立即部署到创建的容器的示例
- name: create jenkins container
docker_container:
docker_host: myserver.net:4243
name: my_jenkins
image: jenkins

- name: add container to inventory
add_host:
name: my_jenkins
ansible_connection: docker
ansible_docker_extra_args: "--tlsverify --tlscacert=/path/to/ca.pem --tlscert=/path/to/client-cert.pem --tlskey=/path/to/client-key.pem -H=tcp://myserver.net:4243" #设置守护进程
ansible_user: jenkins
changed_when: false

- name: create directory for ssh keys
delegate_to: my_jenkins
file:
path: "/var/jenkins_home/.ssh/jupiter"
state: directory

参考absible的yaml语法:https://docs.ansible.com/ansible/2.4/intro_inventory.html#id7


0x01 命令详解

描述:主要是描述ansible命令与ansible-doc命令参数

语法参数:

ansible [主机] [选项] [主机连与认证]
#[option]
-a #用于传递模块所需要使用的参数 -a "src=/etc/fstab dest=/testdir/ansible/"表示为fetch模块传入了两个参数
-m #选项用于调用指定的模块,-m fetch"表示调用fetch模块;
-e #指定参数变量以供模块使用


补充命令1:

ansible-doc  #模板帮助以及模块命令作用查看

#参数
-l,--list 模块简介与全部模块
-s 模块详情


补充命令2:

ansible-playbook #运行剧本配置文件脚本

#参数
--syntax-check #语法验证
--check #模拟验证执行
--list-tags #概览一下playbook中都有哪些标签
--tags [tagname] #指定执行tagname的任务
--skip-tags [tagname] #跳过执行tagname的任务而执行其他任务;
-e,--extra-vars #指定在play中使用的变量传入多/单个变量,还可以通过json字符串形式传入;


命令示例:

#ansible-playbook进行yml配置语法检查
ansible-playbook --syntax-check test.yml

#ansible-playbook进行yml配置模拟执行
ansible-playbook --check test.yml

#只有标签对应的任务会被执行,其他任务都不会被执行
ansible-playbook --tags task1 test.yml

#指定"不执行的任务",task1标签的任务将不被执行
ansible-playbook --skip-tags task1 test.yml

#指定在play中使用的变量(传入单个变量 / diphenhydramine变量)
ansible-playbook cmdvar.yml --extra-vars "pass_var=cmdline pass var"
ansible-playbook cmdvar.yml -e 'pass_var="test" pass_var1="test1"'
ansible-playbook cmdvar.yml -e '{"testvar":"test","testvar1":"test1"}'
ansible-playbook cmdvar.yml -e '{"countlist":["one","two","three","four"]}' #获取值使用如下两种语法引用变量 {{countlist[0]}} 或者 {{countlist.0}}


0x02 Ansible模块基础使用

当我们使用ansible完成实际任务时,需要依靠ansible的各个模块,比如前的ping模块

#ping模块使用
$ansible all -m ping

ansible-doc -l #获取到的模块信息比较概括(查看absible当前所有模块)
ansible-doc -s ping #ping模块的详细使用
ansible-doc -s fetch #我们需要将受管主机中的文件拉取到ansible主机时则可以使用此模块

比如:查看fetch模块的使用帮助

# ansible-doc -s fetch
- name: Fetch files from remote nodes
fetch:
dest: # (required - 必须参数) 指定存入到ansible主机上文件路径
src: # (required - 必须参数) 指定远程主机文件路径
validate_checksum: #Verify checksums
fail_on_missing: #默认yes,只有再文件不存在的时候失败
flat: #文件重写覆盖

基础示例:

#主机清单-yaml文件
all:
hosts:
local:
ansible_host: 10.10.107.222
ansible_user: root
ansible_ssh_pass: [email protected]
children:
ops:
children:
testA:
hosts:
10.10.107.221
testB:
hosts:
10.20.172.179


#示例:拉取ops组中所有主机的/etc/fstab文件拉取到本地
ansible ops -m fetch -a "src=/etc/fstab dest=/tmp/ansible/" #初次拉取
#黄色提示,并且发生改变
10.10.107.221 | CHANGED => {
"changed": true, #关键点
"checksum": "2b8323413e9c5a3067f61c27ca5ea21378bae582", #远程MD5校验
"dest": "/tmp/ansible/10.10.107.221/etc/fstab", #(会以主机名称建立文件夹,然后再存入)
"md5sum": "edf31ba4fd10068f6310f4343855af89", #本地MD5
"remote_checksum": "2b8323413e9c5a3067f61c27ca5ea21378bae582", #远程MD5校验
"remote_md5sum": null
}

WeiyiGeek.fetch

返回提示颜色来看幂等性

  • 当返回信息为绿色时,”changed”为false,表示ansible没有进行任何操作,没有”改变什么”。
  • 当返回信息为黄色时,”changed”为true,表示ansible执行了操作,”当前状态”已经被ansible改变成了”目标状态”。

比如:这时候我把10.10.107.221的fstab进行更改

echo " " >> /etc/fstab

#再次进行文件拉取查看效果
10.10.107.221 | CHANGED => { #对比点
"changed": true, #对比点 (Yellow)
"checksum": "e0719b31dd9445c5da3dd5e04f13ed44855aacd0",
"dest": "/tmp/ansible/10.10.107.221/etc/fstab",
"md5sum": "631e37410fac691ead3c90e6938494ef",
"remote_checksum": "e0719b31dd9445c5da3dd5e04f13ed44855aacd0", #比对点
"remote_md5sum": null
}
10.20.172.179 | SUCCESS => {
"changed": false, # 由于终端的问题我是白色的(Green)
"checksum": "39ffc633a100c2b2d06a3d9d0e625dff1ca7043c",
"dest": "/tmp/ansible/10.20.172.179/etc/fstab",
"file": "/etc/fstab",
"md5sum": "c63b434a7a09baea8b7b59fce8cb807b"
}

WeiyiGeek.幂等性差别

_总结_:

  • 注释中包含 “required” 字样则表示使用模块中的参数必须要设置;
  • 注意幂等性的区别点,以及yaml配置受管主机清单

0x03 PlayBook(剧本)

描述:将我们前面所学到的模块的知识点应用到工作场景,进一步理解与使用ansible

剧本yml语法

假设,我们想要在test70主机上安装nginx并启动,我们可以在ansible主机中执行如下3条命令

#确定YUM源 使用yum模块安装nginx 返回再启动nginx服务
ansible test70 -m yum_repository -a 'name=aliEpel description="alibaba EPEL" baseurl=https://mirrors.aliyun.com/epel/$releasever\Server/$basearch/'
ansible test70 -m yum -a 'name=nginx disable_gpg_check=yes enablerepo=aliEpel'
ansible test70 -m service -a "name=nginx state=started"

但是在实际的工作环境中我们可能需要经常在新主机上安装nginx,难道每次有新的服务器加入工作环境,我们都要修改上述3条命令中的主机名并且重新将每一条命令执行一遍吗?

这样似乎有些麻烦,肯定有更好的办法,没错我们可以将上述命令写成脚本,每次修改一些变量然后执行脚本就行了,而ansible天生就提供了这种类似"脚本"的功能,在ansible中类似”脚本”的文件被称作”剧本”,’剧本’的英文名称为’playbook’,我们只需要将要做的事情编写成playbook,把不同的模块按照顺序编排在剧本中,ansible就会按照剧本一步一步的执行,最终达到我们的目的,虽然playbook的功能与脚本类似,但是剧本并不是简单的将ad-hoc命令按照顺序堆砌在一个可执行文件中,编写剧本需要遵循YAML语法;

一个’playbook’是由一个或多个’play’组成的,这样说可能不太容易理解,那么我们打个比方,一个'剧本'是由一个或多个'桥段'组成的,每个桥段都有不同的场景、人物、故事,所有的桥段组合在一起,组成一个完整的剧本,剧本就是playbook桥段就是play;当然’桥段’只是我自己为了方便理解给’play’起的中文名,官方名称只叫\”play\”。


剧本初识-单个play
首先,我们需要创建一个YAML格式的playbook文件,playbook文件以”.yaml”或者”.yml”作为文件名后缀,此处我们创建一个名为”test.yml”的剧本文件。

#比如:对本地主机采用ping模块,然后采用file模块再主机上创建目录;
ansible local -m ping
ansible local -m file -a "path=/testdir/test state=directory"

#yaml配置文件写法 test.yml
---
- hosts: local,testA
remote_user: root
tasks:
- name: ping The host
ping:
- name: make directory test
file:
path: /tmp/ansible-playbook
state: directory

yml配置文件解析:

  • 第一行:---表示yml文档的开始
  • 第二行:- 作为开头表示一个块序列的节点;host关键字指定要操作的主机或者组,多台主机或者组采用,分割
  • 第三行:remote_user关键字与hosts关键字对齐表示它们是平级的,使用remote_user关键字可以指定在进行远程操作时使用哪个用户进行操作
  • 第四行:使用tasks关键字指明要进行操作的任务列表之后的行都属于tasks键值对中的值;整个任务列表一共有两个任务组成,每个任务都以\”- \”开头,每个任务都有自己的名字,任务名使用name关键字进行指定
    • 第一个任务使用ping模块,使用ping模块时没有指定任何参数。
    • 第二个任务使用file模块,使用file模块时,指定了path参数与state参数的值。

采用'ansible-playbook'命令测试运行剧本(脚本):

[[email protected] ~]# ansible-playbook test.yml

playbook执行后返回了一些信息,这些信息是这次剧本运行的概况:
WeiyiGeek.playbook

  • ‘make directory ansible-playbook’任务返回的信息是绿色的,如果对应的目录并不存在
  • ‘make directory ansible-playbook’任务返回的信息应该是黄色的,这是因为幂等性的缘故,比如这次local主机

我们在playbook中明明只写了两个任务,为什么最后执行时却有三个任务呢?
答:因为每个play在执行时都会先执行一个默认任务,’Gathering Facts’任务会收集当前play对应的目标主机的相关信息,收集完这些基础信息后才会执行我们指定的任务,

补充说明

  • 脚本语法验证
  • 脚本模拟执行 : 我们并不能完全以’模拟’的反馈结果作为playbook是否能够正常运行的判断依据,只能通过’模拟’大概的’预估’一下而已
    $ansible-playbook --syntax-check test.yml #语法
    $ansible-playbook --syntax-check demo.yml #语法
    playbook: demo.yml #说明没问题

    $ansible-playbook --check test.yml #验证

WeiyiGeek.playbook--check


剧本初识-多个play
比如我们把上面的主机或者组分别分成两个不同的场景:对于Local主机模块是不变化的,对于主机组B采用file模块以及user模块来创建文件和建立用户

demo.yml
---
- hosts: local
remote_user: root
tasks:
- name: ping The host
ping:
- name: make directory test
file:
path: /tmp/ansible-playbook
state: directory

- hosts: testB
remote_user: root
tasks:
- name: touch file
file:
path: /tmp/file.txt
state: touch
- name: create user demo
user:
name: demo
password: "$6$ygRbo7Fj.mMU2KY0$OEqihCCn5UfOsvMyzPNPBgx3bzAtwrOFyFvacgUmA374XOAEtUCrdjbW5Ip.Zqo491o3kD5I.HaC9nLhh6x741"
uid: 1024
mode: 0700

脚本(剧本执行):ansible-playbook demo.yml,执行后的结果验证:

WeiyiGeek.playbookdemo.yml

其他yml配置文件形式说明:

#形式1:使用了"等号",每个参数之间使用空格隔开这种格式也是完全正确的,0.8版本以后的ansible推荐的书写格式
tasks:
- name: make testfile
file: path=/testdir/testfile state=touch mode=0700
- name: make testfile
file: path=/testdir/testfile #即使把多个参数分行写,也需要注意缩进
state=touch mode=0700

#形式2:
#在0.8版本之前,使用action关键字调用模块,示例如下(2.8.1向下兼容):
---
- hosts: local
remote_user: root
tasks:
- name: make file
action: file path=/tmp/test.yml state=touch mode=0700

WeiyiGeek.2.8.1向下兼容

在前面示例中我们对每个任务都指定了对应的名称,即每个task都有对应的name,当我们省略name时,默认以当前任务调用的模块的名称作为任务的名称,不过建议不要省略name,因为当任务存在name时可读性比较高。

#写法一:(推荐方式)
tasks:
- name: make testfile
file: path=/testdir/testfile
state=touch
mode=0700

#写法二:
tasks:
- file: path=/testdir/testfile
state=touch
mode=0700
name: make testfile

各属性顺序虽然没有要求,但是仍然需要严格按照缩进进行对齐。


handlers 用法

描述:先来描述一个工作场景当我们修改了某些程序的配置文件以后,有可能需要重启应用程序,以便能够使新的配置生效,那么如果使用playbook来实现这个简单的功能该怎样编写playbook呢?

假设我们想要将nginx中的某个server的端口从8080改成8088,并且在修改配置以后重启nginx,那么我们可以编写如下剧本。

---
- hosts: test70
remote_user: root
tasks:
- name: Modify the configuration
lineinfile:
path=/etc/nginx/conf.d/test.conf
regexp="listen(.*) 8080 (.*)"
line="listen\1 8088 \2"
backrefs=yes
backup=yes
- name: restart nginx
service:
name=nginx
state=restarted

WeiyiGeek.restarnginx

那么问题来了

  • 第一次执行修改后重新是没有什么问题,但是在第二/n次运行时候会进行行替换匹配而不发生改变(由于幂等性),而是有一次执行了restart来重启了nginx服务;简单的说就是配置未发生任何变化却进行了服务重启;

解决问题的方法:采用 handlers 方法

  • handlers的概念:你可以把handlers理解成另一种tasks(平级),handlers是另一种’任务列表’,handlers中的任务会被tasks中的任务进行\”调用\”,但是被\”调用\”并不意味着一定会执行,只有当tasks中的任务"真正执行"以后(真正的进行实际操作,造成了实际的改变),handlers中被调用的任务才会执行,如果tasks中的任务并没有做出任何实际的操作,那么handlers中的任务即使被’调用’,也并不会执行。

handlers示例:

---
- hosts: test70
remote_user: root
tasks:
- name: Modify the configuration
lineinfile:
path=/etc/nginx/conf.d/test.conf
regexp="listen(.*) 8080(.*)"
line="listen\1 8088\2"
backrefs=yes
backup=yes
notify:
restart nginx

#我们使用notify关键字'调用'handlers中的任务,或者说通过notify关键字'通知'handlers中的任务
# 们使用handlers关键字,指明哪些任务可以被'调用',与tasks是平级的状态
handlers:
- name: restart nginx #任务的名称为"restart nginx"
service:
name=nginx
state=restarted

所以综上所述上例中的play表示,如果"Modify the configuration"真正的修改了配置文件(实际的操作),那么则执行"restart nginx"任务,如果"Modify the configuration"并没有进行任何实际的改动,则不执行"restart nginx"通常来说,任务执行后如果做出了实际的操作,任务执行后的状态为changed则会执行对应的handlers,

handlers是另一种任务列表并且可以有多个任务,被tasks中不同的任务notify,

  • 默认情况下所有task执行完毕后才会执行各个handler,并不是执行完某个task后,立即执行对应的handler
  • 如果你想要在执行完某些task以后立即执行对应的handler,则需要使用meta模块

示例如下:

---
- hosts: local
remote_user: root
tasks:
- name: make testfile1
file: path=/tmp/testfile1
state=directory
notify: ht2 #调用handlers中的ht2的任务
- name: make testfile2
file: path=/tmp/testfile2
state=directory
notify: ht1 #调用handlers中的ht1的任务

- meta: flush_handlers #设置在执行完前面某些task以后立即执行调用对应的handler
- name: task3
file: path=/tmp/testfile3
state=directory
notify: ht3

handlers:
- name: ht1 #设置handlers任务名称(当tasks任务执行时候才出否)
file: path=/tmp/testfile1/ht1
state=touch
- name: ht2
file: path=/tmp/testfile2/ht2
state=touch
- name: ht3
file: path=/tmp/testfile3/ht3
state=touch

#执行流程如下:
PLAY [local]
TASK [Gathering Facts]

TASK [make testfile1]
TASK [make testfile2]

RUNNING HANDLER [ht1]
RUNNING HANDLER [ht2]
#由于这里采用meta模块会把上面tasks中notify调用完成后执行,下面的tasks任务
TASK [task3]
RUNNING HANDLER [ht3]

PLAY RECAP

WeiyiGeek.meta模块与handler

在一个task中一次性notify多个handler,当多个handler的name相同时只有一个handler会被执行,所以我们并不能通过这种方式notify多个handler,

如果想要一次notify多个handler就需要设置组名来进行handlers多任务的调用,则需要借助另一个关键字它就是'listen'理解成"组名",我们可以把多个handler分成"组",当我们需要一次性notify多个handler时,只要将多个handler分为”一组”,使用相同的”组名”即可,当notify对应的值"组名"时,"组"内的所有handler都会被notify

一个notify调用多个handler中的任务:

---
- hosts: test70
remote_user: root
tasks:
- name: task1
file: path=/tmp/test
state=directory
notify: handler group1 #关键点 当task1中notify的值为handler group1时,handler1与handler2都会被notify

#andler1与handler2的listen的值都是handler group1
handlers:
- name: handler1
listen: handler group1 #关键点(监听组)
file: path=/tmp/test/ht1
state=touch
- name: handler2
listen: handler group1
file: path=/tmp/test/ht2
state=touch

监测与执行:

$ansible-playbook --syntax-check notify.yml
playbook: notify.yml

ansible-playbook notify.yml

WeiyiGeek.notify-nutil-handlers


handler总结

  • handler执行的顺序与handler在playbook中定义的顺序是相同的,与”handler被notify”的顺序无关。
  • 可以使用meta模块来执行完某些task以后立即执行对应的handler;如果想要每个task在实际操作后都立马执行对应handlers,则可以在每个任务之后都添加一个meta任务并将其值设置为flush_handlers
  • 采用tasks默认都notify只能调用一个handlers任务,如果想调用多个handlers任务就采用listen关键字来设置监听组


tags 用法

描述:
在实际使用这个剧本时你可能只是想要执行其中的一部分任务而已,或者你只想要执行其中一类任务而已,而并非想要执行整个剧本中的全部任务

这个时候我们该怎么办呢?
答:可以借助tags实现这个需求,见名知义tags可以帮助我们对任务进行'打标签'的操作,当任务存在标签以后,我们就可以在执行playbook时,借助标签指定执行哪些任务,或者指定不执行哪些任务了;

示例:play中有3个task每个task都有对应的tags,我只是简单的把tags的值写成了t1、t2、t3当然您也可以定义成为其他;

---
- hosts: local
remote_user: root
tasks:
- name: task1
file:
path: /tmp/t1
state: touch
tags: t1 #标签1 - 执行task1任务
- name: task2
file: path=/tmp/t2
state=touch
tags: t2 #标签2 - 执行task2任务
- name: task3
file: path=/tmp/t3
state=touch
tags: t3 #标签3 - 执行task3任务

下面利用ansible-playbook中--tags选项以及--skip-tags选项来执行指定的task任务以及跳过任务执行:

#示例0.在调用标签之前,如果你想要概览一下playbook中都有哪些标签
ansible-playbook --list-tags testhttpd.yml

#示例1.只执行标签为t2的task2任务,只有标签对应的任务会被执行,其他任务都不会被执行,
ansible-playbook --tags=t2 testtag.yml


#示例2.选项指定"不执行的任务",比如下面的命令task1和task3会执行跳过了task2不会执行,
ansible-playbook --skip-tags='t2' testtag.yml

除了使用上例中的语法指定标签,我们可以为每个任务添加多个标签三种语法添加多个标签的示例:

#语法一:
tags:
- testtag
- t1

#语法二:
tags: tag1,tag2

#语法三:
tags: ['tagtest','t2']


将上面所学知识的进行综合演示,实现标签的常规用法,当拥有共同的标签时候将主tags标签提取出来与tasks同级,还可以在tasks里面建立子标签:

---
- hosts: local
remote_user: root
tags: command #关键点-形式0
tasks:
- name: show ip address
tags: ['ip'] #关键点-形式1
shell: ifconfig > /tmp/ipaddr.txt #值得注意(shell模块在play中的使用)
- name: mkdir directory
tags:
- createdir #关键点-形式2
file:
path: /tmp/createdir
state: directory

- hosts: testA
remote_user: root
tags: nginx
tasks:
- name: install nginx package
tags: ['package']
yum: name=httpd state=latest
- name: start up nginx server
tags:
- startservices
service:
name: nginx
state: started

当tags写在play中而非task中时,play中的所有task会继承当前play中的tags,而上例中两个任务都会继承httpd标签,同时还有拥有自己的标签

#示例1.概览一下playbook的yml脚本中都有哪些标签
ansible-playbook --list-tags tag.yml
playbook: tag.yml
play #1 (local): local TAGS: [command]
TASK TAGS: [command, createdir, ip]

play #2 (testA): testA TAGS: [nginx]
TASK TAGS: [nginx, package, startservices]


#示例2.在调用标签时,也可以一次性指定多个标签来调用多个标签需要用逗号隔开
#play执行顺序默认是从下到下执行
ansible-playbook --tags package,startservices testhttpd.yml
ansible-playbook --tags command,nginx tag.yml

WeiyiGeek.tags-demo


ansible特殊预置5个tag:

#标签示例
tags: t3,always,nginx

#--------------------分割线--------------------
* always : 把任务的tags的值指定为always时任务就总是会被执行,除非你使用'--skip-tags'选项明确指定不执行对应的任务
ansible-playbook --skip-tags always testtag.yml #只有这样才能跳过执行,如果play中有多个任务都有always标签将都不会被执行;

ansible-playbook --skip-tags t3 testtag.yml #另外一种情况;只跳过task3其他带有always标签的任务不会跳过,前提是task3有除了always以外的自定义标签比如这里的t3。


#--------------------分割线--------------------
* never(2.5版本中新加入的特殊tag): 从字面上理解never的作用应该与always正好相反
ansible-playbook --tags never testtag.yml #指定才会被执行


#--------------------分割线--------------------
#剩下的三个特殊标签并非像always一样always作为标签值存在,而这三个特殊标签则是在调用标签时使用
* tagged
ansible-playbook --tags tagged testtag.yml #只执行有标签的任务,没有任何标签的任务不会被执行
ansible-playbook --skip-tags tagged testtag.yml #表示跳过包含标签的任务,即使对应的任务包含always标签,也会被跳过。

* untagged
ansible-playbook --tags untagged testtag.yml #只执行没有标签的任务,但是如果某些任务包含always标签,那么这些任务也会被执行。
ansible-playbook --skip-tags untagged testtag.yml #表示跳过没有标签的任务。

* all: 表示所有任务会被执行,不用指定,默认情况下就是使用这个标签。