在干货篇 | 一文带你了解Ansible(上)中,我们了解了什么是ansible、 ansible 的原理以及一些常用的 ansible 命令和 ansible 模块
但是这在一些实际场景上还是不能算是最优解决方案,例如以下场景
一天,老板让你用 ansible 给多台主机部署http服务,往往在部署httpd服务的时候我们需要下载httpd包
,还要根据每台主机的实际情况(CPU、内存等)来进行不同的配置,除此之外还要开启我们的
httpd服务等等一系列操作
按照上篇文章所讲的内容,我们是可以使用 ansible 来进行一键部署的,但是需要我们根据不同的操作(创建用户、修改配置、下载管理包等)来使用不同的 ansible 模块(user、file、yum等),这样一来,也是比较繁琐的,不算是比较好的解决方案
那么我们有什么方法将上面这些操作只需要一个命令就能都进行而不是我们一条一条的敲呢?
答案是:有!
接下来我将介绍 ansible 中的一大神器——playbook。翻译成中文就是剧本
playbook
playbook字面意思,即剧本,现实中由演员按照剧本表演,在Ansible中,这次由计算机进行表演,由计算机安装、部署应用,提供对外服务,以及组织计算机处理各种各样的事情
通俗点来讲,playbook是由一个或多个“play”组成的列表。而“play”就相当于执行的每条 ansible 命令,每条 ansible 命令组成起来成一个playbook,来实现复杂的任务
我们也可以理解为脚本,这个脚本包括 ansible 单条命令的集合
playbook执行过程
1.将以编排好的任务集(ansible 单条命令集合)写进playbook
2.通过ansible-playbook命令分拆任务集逐条执行ansible命令,按预定规则逐条执行
其次,playbook 采用 YAML 语言编写,看到这里可能有些读者不知道什么是 YAML 语言,这里我简单介绍一下
更多的内容及规范参见http://www.yaml.org
YAML语言
1.YAML是一个可读性高的用来表达资料序列的格式。YAML参考了其他多种语言,包括: XML、C语言、Python、 Perl以及电子邮件格式RFC2822等。Clark Evans在2001年在首次发表了这种语言,另外Ingy dot Net与Oren Ben-Kiki也是这语言的共同设计者YAML Ain’t Markup Language ,即YAML不是XML。不过,在开发的这种语言时, YAML的意思其实是: “Yet Another Markup Language” (仍是一种标记语言)
2.特性:
-
可读性好 -
和脚本语言的交互性好 -
使用实现语言的数据类型 -
有一致的信息模型 -
易于实现 -
可以基于流来处理 -
表达能力强,扩展性好
我们先来看一下 playbook 的基本格式
[root@ansible ansible]
---
- hosts:webservers
remote_user: root
?
tasks:
- name: hello
command: hostname
我们可以看到,palybook 由于是 YAML 语言编写的,所以后缀名是 .yml 或者 .yaml
其次,开头我们一般会用连续三个连字号(-)区分不同内容,而 hosts 是执行的远程主机列表,tasks 就是我们的任务列表,在tasks里面一个name对应一个task,command也就是 ansible 单个模块
核心要素
在playbook中,有这几大核心要素
- hosts:主机列表
- tasks:任务
- variables:变量
- templates:模板
- handlers和notity:触发器
- tags:标签
hosts
即我们要执行的远程主机列表,我们在上一篇文章中介绍过主机列表(主机清单)
playbook 中的每一个task都的目的都是为了让某个或者某些主机以指定的某个用户来执行
所以说我们在写 playbook 时首先要先告知是哪些主机来执行task(执行task的主机要先在主机清单里定义好)
我们的主机清单存储路径在 /etc/ansible/hosts 下
首先定义好我们的主机清单,添加 webserver 主机组
[root@ansible ansible]
[webserver1]
www.salted1.com
www.salted2.com
192.168.0.1
之后我们在 playbook 中指出是哪些主机要执行
[root@ansible ansible]# vim test1.yml
---
-hosts:webserver1
remote_user:root #指定在远程主机上执行task的用户为root
tasks
任务列表。在任务列表下面有许多个任务,每个任务只能对应一个模块,执行顺序为由上到下
例如我想让主机清单里的 webserver 主机组以root身份创建一个新的文件 newfile,并且添加一个系统用户user1,其shell为/sbin/nologin
[root@ansible ansible]# vim test.yml
---
- hosts: webserver
remote_user: root
# 定义任务列表
tasks:
- name: create a newfile
file: name=/data/newfile state=touch
- name: create a user
user: name=user1 shell=/sbin/nologin system=yes
之后我们运行即可
# 可以先用 -C 参数先测试
ansible-playbook -C test.yml
?
# 发现没有报错,执行
ansible-playbook test.yml
variables
为了使得我们的 playbook 更加灵活,playbook 里面引用了变量的概念
变量名规范:仅由字母,数字和下划线组成,并且只能以数字开头
变量来源:
-
远程主机上的变量(可以直接调用) -
在主机列表中定义变量 -
- 普通变量:主机组中的主机单独定义
- 公共变量:也称组变量,针对主机组里面的主机统一定义
-
在playbook中定义并通过命令行赋值 -
- ansible-playbook -e varname=value
**调用变量:**需要在变量名外面加上花括号,例:{{ varname }}
存放变量的文件
为了便于维护定义的变量,我们可以将常用的变量写进一个文件里面
[root@ansible ansible]# vim vars.yml
var1: httpd
var2: ospfd
当我们编写 palybook 需要使用文件里的变量时,可以直接调用
[root@ansible ansible]# vim test.yml
---
- hosts: webserver
remote_user: root
var_files:
- vars.yml
tasks:
- name: install httpd
yum: name={{ var1 }}
- name:install ospfd
yum:name={{ var2 }}
远程主机变量
当我们运行 playbook 时,默认都会运行一个名为 “Gathering Facts” 的任务,ansible 通过这个默认任务去搜集远程主机的相关信息(例如远程主机的ip地址、主机名、系统版本、硬件配置等信息),这些相关信息会保存在对应的变量中,当我们想要使用这些信息时,可以通过获取相应的变量来使用这些信息
如果想要查看远程主机的相关的变量,我们可以使用 setup 模块
[root@ansible ansible]# ansible 192.168.10.2 -m setup
192.168.10.2 | SUCCESS => {
"ansible_facts": {
"ansible_all_ipv4_addresses": [
"192.168.10.2",
"192.168.122.1"
],
"ansible_all_ipv6_addresses": [
"fe80::1f2f:cc8b:e62b:863b"
],
"ansible_apparmor": {
"status": "disabled"
},
"ansible_architecture": "x86_64",
"ansible_bios_date": "05/19/2017",
"ansible_bios_version": "6.00",
"ansible_cmdline": {
"BOOT_IMAGE": "/vmlinuz-3.10.0-957.27.2.el7.x86_64",
"LANG": "zh_CN.UTF-8",
"crashkernel": "auto",
"quiet": true,
"rd.lvm.lv": "centos/swap",
"rhgb": true,
"ro": true,
"root": "/dev/mapper/centos-root"
}
......................
我们可以看到,返回的信息上是json格式的字符串
我们也可以通过过滤工具 filter ,这样就不用一下子获取这么多变量,去获取我们想要的变量
例如我们想要获取远程主机的 hostname
ansible webserver -m setup -a "filter=ansible_hostname"
或者我们想要获得远程主机的主机名
ansible all -m setup -a "filter=ansible_fqdn"
playbook中定义的变量
我们还可以在 playbook 中定义我们的变量,再通过命令将我们定义好的变量进行赋值,好处就是灵活性更高
设置单个变量
[root@ansible ansible]# vim test.yml
---
- hosts: werbserver
remote_user: root
?
tasks:
- name: install package
yum: name={{ varname }}
- name: start service
service: name={{ varname }} state=started
?
# 在命令行里通过 -e 参数 给变量赋值
anisble-palybook webserver -e 'varname=httpd' test.yml
设置多个变量
[root@ansible ansible]# vim test.yml
---
- hosts: webserver
remote_user: root
?
tasks:
- name: install package
yum: name={{ varname1 }}
- name: install package
yum: name={{ varname2 }}
?
# 在命令行里通过 -e 参数 给变量赋值
anisble-palybook webserver -e 'varname1=httpd varname2=ospfd' test.yml
设置变量组
[root@ansible ansible]# vim test.yml
---
- hosts: werbserver
remote_user: root
?
vars:
- varname1: httpd
- varname2: vsftpd
?
tasks:
- name: install package
yum: name={{ varname1 }}
- name: install package
yum: name={{ varname2 }}
普通变量和公共变量
这类变量一般在主机清单里面定义
普通变量:仅针对主机组中的单个主机单独定义
公共变量:也称组变量。对主机组中的所有主机统一定义
我们先来看下普通变量
[root@ansible ansible]# vim /etc/ansible/hosts
[webserver]
192.168.244.100 http_port=81
192.168.244.101 http_port=82
编写playbook
[root@ansible ansible]# vim test.yml
---
- hosts:webserver
remote_user: root
?
tasks:
- name: set hostname
hostname: name=www{{ http_port }}.com
这里我们可以看到,不同的主机定义的 http_port 是不一样的,所以在执行 playbook 之后每台主机的 hostname 也不一样
接下来我们设置公共变量
[webserver]
192.168.244.100
192.168.244.101
[werserver:vars]
nodenmae=www
domainname=edu.com
编写 playbook
[root@ansible ansible]# vim test.yml
---
- hosts:webserver
remote_user: root
?
tasks:
- name: set hostname
hostname: name={{ nodename }}.{{ domainname }}
?
因为是公共变量,所以该主机组内的所有主机的 hostname 都是一样的
template 模板
template是一种使用 jinjia2 语言格式作为文本文件模板,我们可以通过编写 template 来替换文件中的一些变量并实现一些简单逻辑
下面我们通过两个个例子来让大家对template 模板有一个初步印象
例一
在nginx的配置文件中,产生的worker进程数默认是自动的,nginx会根据当前主机的cpu个数来产生相应的worker进程。
真实情况下我们的远程主机的硬件配置跟ansible服务器不是完全相同的,ansible服务器可能只有一个CPU,而有的主机可能有2个CPU,有的可能有4个CPU
如果我们单纯地用copy模块将ansible服务器的nginx配置文件传送到不同的主机下,往往就会导致worker进程数跟主机的CPU个数不对应
这时候我们就可以通过 template 模块来根据不同主机的CPU数量产生不同的worker进程数
首先我们创建一个 名为template的目录(建议与 playbook 目录为同一层级)
[root@ansible ansible]# mkdir templates
[root@ansible ansible]# ls
ansible.cfg templates test_template.yml
新建一个template 模板 nginx.conf.j2,注意后缀名为 .j2,并且将 nginx 配置文件复制到 template 模板下
cp /etc/nginx/nginx.conf templates/nginx.conf.j2
然后编写我们的playbook
[root@ansible ansible]# cat test_template.yml
---
- hosts: webserver
remote_user: root
?
tasks:
- name: install package
yum: name=nginx
- name: copy template
template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
notify: restart service
- name: start service
service: name=nginx state=started enabled=yes
注意:ansible服务器下的 template 模板后缀名有 .j2,传送到远程主机后应把 .j2后缀名给去掉
这样我们就能实现根据不同远程主机的 worker 进程数跟远程主机的CPU数量所对应
例二
使用template模板使得都是 nginx 服务,但不同远程主机的nginx 端口不一样
首先我们定义普通变量,使得不同远程主机的端口号不一样
[webserver]
192.168.244.141 http_port=8080
192.168.244.135 http_port=9090
将 nginx 配置文件复制到template模板下,并将监听的端口修改为在主机清单里定义的变量
[root@ansible ansible]# vim templates/nginx.conf.j2
....
server {
listen {{ http_port }} default_server;
listen [::]:{{ http_port }} default_server;
?
....
这样在192.168.244.141主机下的监听端口为8080,而在另一台主机下的监听端口为9090
handlers和notify 触发器
我们通过handlers和notify结合使用来实现触发器操作
**handlers:**在task列表中,只有当关注的资源发生变化时,才会采取相应的操作
notify:与handlers配套使用。调用handlers中的定义的操作;用于每个play的最后被触发,这样就可以避免多次发生改变时每次都会执行notify的操作。仅在所有的变化发生完成后一次性地执行指定操作
下面我们结合一个案例来进行讲解
案例
一般情况下,当修改了 httpd 的配置文件后我们需要重启httpd服务才会使得配置文件生效,这时候我们就可以通过触发器来实现一旦发现配置文件有修改就重启服务的操作
下面编写playbook
需求:在远程主机上安装httpd服务并将ansible主机上的配置文件复制过去,当被控机收到传送过来的配置文件后就重启服务使得配置文件生效
[root@ansible ~]# vim httpd.yml
---
- hosts: webserver
remote_user: root
?
tasks:
- name: install httpd package
yum: name=httpd
- name: copy config file
file: src=/etc/httpd/httpd.conf dest=/etc/httpd/ backup=yes
notify: restart service
- name: service start
service: name=httpd state=started enabled=yes
?
handlers:
- name: restart service
service: name=httpd state=restarted
我们还可以编写多个触发器
当ansible主机的配置文件传送到远程主机上后,就会触发重启服务和检查nginx进程这两个操作
[root@ansible ~]# vim httpd.yml
---
- host: webserver
remote_user: root
?
tasks:
- name: add group nginx
group: name=nginx
- name: add user nginx
user: name=nginx group=nginx
- name: install nginx
yum: name=nginx enabled=yes
- name: copy conf
file: src=/root/conf.txt dest=/etc/nginx/nginx.conf
notify:
- restart nginx
- check nginx process
handlers:
- name: restart nginx
service: name=nginx state=restarted
- name: check nginx process
shell: killall -0 nginx > /tmp/nginx.log
tags标签
使用 tag 标签可以让我们执行 playbook 里面指定的内容,无需按照从上到下的顺序依次执行
比如说我们编写一个部署 httpd 的 playbook,并且后期想跳过安装,文件配置等操作直接开启服务
[root@ansible ~]# vim tags.yml
---
- hosts: webserver
remote_user: root
?
tasks:
- name: install httpd package
yum: name=httpd
- name: copy config file
flie: src=/etc/httpd/httpd.conf dest=/etc/httpd/httpd.conf
- name: start service
service: name=httpd state=started enabled=yes
tags: httpdservice
编写后在命令行里面使用 -t 参数来实现标签操作
ansible-palybook -t httpdservice tags.yml
多个动作也可以使用同一标签
[root@ansible ~]# vim tags.yml
---
- hosts: webserver
remote_user: root
?
tasks:
- name: install httpd package
yum: name=httpd
tags: httpdservice
- name: copy config file
flie: src=/etc/httpd/httpd.conf dest=/etc/httpd/httpd.conf
- name: start service
service: name=httpd state=started enabled=yes
tags: httpdservice
命令行里指定标签执行
ansible-palybook -t httpdservice httpd.yml
以上就是 ansible playbook 的核心要素了
除此之外,我们还可以在 playbook 中使用一些编程语言上的条件控制语句来实现一些简单的逻辑
逻辑实现
when
根据某一判断条件(变量、执行结果等)来实现逻辑
[root@ansible ~]# vim when.yml
---
- hosts: webserver
remote_user: root
tasks:
- name: shutdown RedHat flavored systems
conmand: /sbin/shutdown -h now
when: ansible_ os_ family == "RedHat"
迭代
1.当有需要重复性执行的任务时,可以使用迭代机制
2.对迭代项的引用,固定变量名为{{ item }}
3.要在task中使用with_ items给定要迭代的元素列表
**利用迭代创建多个文件
**
[root@ansible ansible]# vim test_item.yml
---
- host: webserver
remote_user: root
?
tasks:
- name: create some files
file: name=/data/{{ item }} state=touch
with_items:
- file1
- file2
- file3
利用迭代安装多个包
[root@ansible ansible]# vim test_item.yml
---
- host: webserver
remote_user: root
?
tasks:
- name: install package
yum: name={{ item }}
with_items:
- nginx
- sysstat
- httpd
迭代+when判断语句
[root@ansible ansible]# vim test_item.yml
---
- host: webserver
remote_user: root
?
tasks:
- name: create some files
file: name=/data/{{ item }} state=touch
when:ansible_distribution_major_version == '7'
with_items:
- file1
- file2
- file3
迭代还可以嵌套
使用迭代来创建组和用户,并将用户添加到不同的组当中
[root@localhost ansible]# cat test_item.yml
---
- hosts: webserver
remote_user: root
?
tasks:
- name: create some group
group: name={{ item }}
with_items:
- g1
- g2
- g3
- name: create some user
user: name={{ item.name }} group={{ item.group }}
with_items:
- {name: "user1" , group: "g1"}
- {name: "user2" , group: "g2"}
- {name: "user3" , group: "g3"}
总结
今天这篇文章写的比较长,干货比较多,希望你们可以耐着性子看下去,并结合自己的思考,如果有不懂的地方可以后台私信我或留言
以下是今天这篇文章的脑图
|