[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模拟环境实现;
代码语言:javascript复制10.10.107.222 Master-Ansible管理端
10.10.107.234 Slave-Ansible 受控端(Ubuntu)
10.20.172.235 Slave-Ansible 受控端(Centos)
Ansible安装:
代码语言:javascript复制#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/$releaseverServer/$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风格里面有默认的配置示例使用提示;
代码语言:javascript复制#由于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主控端如何采用密匙来登录受控端?
代码语言:javascript复制#首先,生成默认格式的密钥对,私钥与公钥。
ssh-keygen
#然后将生成的公钥加入到10.10.107.234的认证列表
ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]
#好了公钥认证的相关操作配置完成,此刻,我们已经可以通过ansible主机免密码连接到主机60中了。
因为配置了密钥认证,所以可以实现免密码创建ssh连接,既然已经能够免密码创建ssh连接,那么在配置”主机清单”时,就没有必要再提供对应主机的用户名与密码了,所以在完成了密钥认证的相关配置后,我们可以将清单中的配置精简为如下格式。
代码语言:javascript复制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提供的清单配置文件中进行配置我们以该文件进行讲解
代码语言:javascript复制$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]
验证配置结果:
代码语言:javascript复制#验证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”的配置语法。 注意,为了使缩进显得更加明显,此处每次缩进使用两个空格
代码语言:javascript复制$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:
#从上例可以看出,
组中嵌套时候:
代码语言:javascript复制#仍然先写出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配置:
代码语言:javascript复制#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
综合示例:
代码语言:javascript复制#以下是如何立即部署到创建的容器的示例
- 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命令参数
语法参数:
代码语言:javascript复制ansible [主机] [选项] [主机连与认证]
#[option]
-a #用于传递模块所需要使用的参数 -a "src=/etc/fstab dest=/testdir/ansible/"表示为fetch模块传入了两个参数
-m #选项用于调用指定的模块,-m fetch"表示调用fetch模块;
-e #指定参数变量以供模块使用
补充命令1:
代码语言:javascript复制ansible-doc #模板帮助以及模块命令作用查看
#参数
-l,--list 模块简介与全部模块
-s 模块详情
补充命令2:
代码语言:javascript复制ansible-playbook #运行剧本配置文件脚本
#参数
--syntax-check #语法验证
--check #模拟验证执行
--list-tags #概览一下playbook中都有哪些标签
--tags [tagname] #指定执行tagname的任务
--skip-tags [tagname] #跳过执行tagname的任务而执行其他任务;
-e,--extra-vars #指定在play中使用的变量传入多/单个变量,还可以通过json字符串形式传入;
命令示例:
代码语言:javascript复制#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模块
代码语言:javascript复制#ping模块使用
$ansible all -m ping
ansible-doc -l #获取到的模块信息比较概括(查看absible当前所有模块)
ansible-doc -s ping #ping模块的详细使用
ansible-doc -s fetch #我们需要将受管主机中的文件拉取到ansible主机时则可以使用此模块
比如:查看fetch模块的使用帮助
代码语言:javascript复制# 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: #文件重写覆盖
基础示例:
代码语言:javascript复制#主机清单-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进行更改
代码语言:javascript复制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条命令
代码语言:javascript复制#确定YUM源 使用yum模块安装nginx 返回再启动nginx服务
ansible test70 -m yum_repository -a 'name=aliEpel description="alibaba EPEL" baseurl=https://mirrors.aliyun.com/epel/$releaseverServer/$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”的剧本文件。
代码语言:javascript复制#比如:对本地主机采用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是否能够正常运行的判断依据,只能通过’模拟’大概的’预估’一下而已12345ansible-playbook --syntax-check test.yml #语法ansible-playbook --syntax-check demo.yml #语法playbook: demo.yml #说明没问题
剧本初识-多个play 比如我们把上面的主机或者组分别分成两个不同的场景:对于Local主机模块是不变化的,对于主机组B采用file模块以及user模块来创建文件和建立用户
代码语言:javascript复制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配置文件形式说明:
代码语言:javascript复制#形式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,那么我们可以编写如下剧本。
代码语言:javascript复制---
- hosts: test70
remote_user: root
tasks:
- name: Modify the configuration
lineinfile:
path=/etc/nginx/conf.d/test.conf
regexp="listen(.*) 8080 (.*)"
line="listen1 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示例:
代码语言:javascript复制---
- hosts: test70
remote_user: root
tasks:
- name: Modify the configuration
lineinfile:
path=/etc/nginx/conf.d/test.conf
regexp="listen(.*) 8080(.*)"
line="listen1 80882"
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模块
示例如下:
代码语言:javascript复制---
- 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中的任务:
代码语言:javascript复制---
- 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
监测与执行:
代码语言:javascript复制$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当然您也可以定义成为其他;
代码语言:javascript复制---
- 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
除了使用上例中的语法指定标签,我们可以为每个任务添加多个标签三种语法添加多个标签的示例:
代码语言:javascript复制#语法一:
tags:
- testtag
- t1
#语法二:
tags: tag1,tag2
#语法三:
tags: ['tagtest','t2']
将上面所学知识的进行综合演示,实现标签的常规用法,当拥有共同的标签时候将主tags标签提取出来与tasks同级,还可以在tasks里面建立子标签:
代码语言:javascript复制---
- 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:
代码语言:javascript复制#标签示例
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: 表示所有任务会被执行,不用指定,默认情况下就是使用这个标签。