[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)
(1) 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 root@10.10.107.234
#好了公钥认证的相关操作配置完成,此刻,我们已经可以通过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) Ubuntu下Ansible安装:
安装方式:
代码语言:javascript复制# 1.官网安装方式(要有梯子)
$ sudo apt update
$ sudo apt install software-properties-common
$ sudo apt-add-repository --yes --update ppa:ansible/ansible
$ sudo apt install ansible
# 2.我们将使用默认的Ubuntu存储库安装(版本可能非最新)
sudo apt update # 首先使用以下命令刷新系统的软件包索引完成此更新后您可以使用以下方法
sudo apt install ansible # 安装Ansible软件:
# 3.安装后版本查看
ansible --version
ansible 2.9.6
config file = /etc/ansible/ansible.cfg
configured module search path = ['/home/weiyigeek/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python3/dist-packages/ansible
executable location = /usr/bin/ansible
python version = 3.8.5 (default, Jul 28 2020, 12:59:40) [GCC 9.3.0]
至此现在,您的Ansible控制节点具有管理主机所需的所有软件;
2.清单配置详解
描述:该清单文件包含有关你会Ansible管理的主机信息
- 清单文件中包括从一到数百台服务器的任何位置,并且可以将主机组织为组和子组。
- 清单文件通常还用于设置仅对特定主机或组有效的变量,以便在剧本和模板中使用。
我们可以在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]
# 示例4.分组与子组与组的变量声明
[manager]
admin ansible_host=10.10.107.202 ansible_port=20211 ansible_user=WeiyiGeeker ansible_sudo_pass='123456'
[master]
k8s-master-1 ansible_host=10.10.107.210
k8s-master-2 ansible_host=10.10.107.211
k8s-master-3 ansible_host=10.10.107.212
[node]
k8s-node-1 ansible_host=10.10.107.213
k8s-node-2 ansible_host=10.20.172.220
k8s-node-3 ansible_host=10.20.172.221
# k8s 组包含的 子组
[k8s:children]
master
node
# k8s 组内所共享的局部变量设置优先级高(全局设置在`/etc/ansible/ansible.cfg`)
[k8s:vars]
ansible_port=20211
ansible_user=WeiyiGeeker
ansible_become=true
ansible_become_method=sudo
ansible_become_pass='123456'
[all:vars]
ansible_python_interpreter=/usr/bin/python3
验证配置结果:
代码语言: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
#验证5.通过执行以下命令查看包含清单文件中定义的自己的服务器基础结构
$ansible-inventory --list -y
all:
children:
k8s:
children:
master:
hosts:
k8s-master-1:
ansible_become: 'true'
ansible_become_method: sudo
ansible_become_pass: 123456
ansible_host: 10.10.107.210
ansible_port: 20211
ansible_python_interpreter: /usr/bin/python3
ansible_user: WeiyiGeeker
k8s-master-2:
ansible_become: 'true'
ansible_become_method: sudo
ansible_become_pass: 123456
ansible_host: 10.10.107.211
ansible_port: 20211
ansible_python_interpreter: /usr/bin/python3
ansible_user: WeiyiGeeker
k8s-master-3:
ansible_become: 'true'
ansible_become_method: sudo
ansible_become_pass: 123456
ansible_host: 10.10.107.212
ansible_port: 20211
ansible_python_interpreter: /usr/bin/python3
ansible_user: WeiyiGeeker
node:
hosts:
k8s-node-1:
ansible_become: 'true'
ansible_become_method: sudo
ansible_become_pass: 123456
ansible_host: 10.10.107.213
ansible_port: 20211
ansible_python_interpreter: /usr/bin/python3
ansible_user: WeiyiGeeker
k8s-node-2:
ansible_become: 'true'
ansible_become_method: sudo
ansible_become_pass: 123456
ansible_host: 10.20.172.220
ansible_port: 20211
ansible_python_interpreter: /usr/bin/python3
ansible_user: WeiyiGeeker
k8s-node-3:
ansible_become: 'true'
ansible_become_method: sudo
ansible_become_pass: 123456
ansible_host: 10.20.172.221
ansible_port: 20211
ansible_python_interpreter: /usr/bin/python3
ansible_user: WeiyiGeeker
manager:
hosts:
admin:
ansible_host: 10.10.107.202
ansible_port: 20211
ansible_python_interpreter: /usr/bin/python3
ansible_sudo_pass: 123456
ansible_user: WeiyiGeeker
ungrouped: {}
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: root@2019
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'命令
测试运行剧本(脚本):
[root@master ~]# ansible-playbook test.yml
playbook执行后返回了一些信息,这些信息是这次剧本运行的概况:
WeiyiGeek.playbook
- ‘make directory ansible-playbook’任务返回的信息是绿色的,如果对应的目录并不存在
- ‘make directory ansible-playbook’任务返回的信息应该是
黄色的
,这是因为幂等性的缘故,比如这次local主机
我们在playbook中明明只写了两个任务,为什么最后执行时却有三个任务呢? 答:因为每个play在执行时都会先执行一个默认任务,’Gathering Facts’任务会收集当前play对应的目标主机的相关信息,收集完这些基础信息后才会执行我们指定的任务,
补充说明:
脚本语法验证
脚本模拟执行 : 我们并不能完全以’模拟’的反馈结果作为playbook是否能够正常运行的判断依据,只能通过’模拟’大概的’预估’一下而已
代码语言:javascript复制$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模块来创建文件和建立用户
代码语言: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: 表示所有任务会被执行,不用指定,默认情况下就是使用这个标签。