目录
上篇讲了ansbile的常用模块的使用,我们执行一些简单的命令可以用ad-hoc,如果要实现比较复杂的功能那就要用到playbook 了,ad-hoc和playbook 的关系就类似shell命令和shell脚本两者之间的关系。
Ansible提供两种方式去完成任务,一是 ad-hoc 命令,一是写 Ansible playbook.前者可以解决一些简单的任务, 后者解决较复杂的任务。一般而言,在学习了 playbooks 之后,你才能体会到 Ansible 真正的强大之处在哪里。
本篇将进入ansible的重头戏 剧本playbooks,我将会花几章去讲解。
本章先讲剧本的介绍,YAML语法、Inventory清单、基础概念
一、playbooks简介
1.1 playbook介绍
Ansible中文权威指南中关于playbooks介绍如下:
Playbooks 与 adhoc 相比,是一种完全不同的运用 ansible 的方式,是非常之强大的.
简单来说,playbooks 是一种简单的配置管理系统与多机器部署系统的基础.与现有的其他系统有不同之处,且非常适合于复杂应用的部署.
Playbooks 可用于声明配置,更强大的地方在于,在 playbooks 中可以编排有序的执行过程,甚至于做到在多组机器间,来回有序的执行特别指定的步骤.并且可以同步或异步的发起任务.
我们使用 adhoc 时,主要是使用 /usr/bin/ansible 程序执行任务.而使用 playbooks 时,更多是将之放入源码控制之中,用之推送你的配置或是用于确认你的远程系统的配置是否符合配置规范.
在如右的连接中: ansible-examples repository ,有一些整套的playbooks,它们阐明了上述的这些技巧.我们建议你在另一个标签页中打开它看看,配合本章节一起看.
即便学完 playbooks 这个章节,仍有许多知识点只是入门的级别,完成本章的学习后,可回到文档索引继续学习.
ansbile-playbook是一系统ansible命令的集合,其利用yaml 语言编写,运行过程,ansbile-playbook命令根据自上而下的顺序依次执行。同时,playbook开创了很多特性,它可以允许你传输某个命令的状态到后面的指令,如你可以从一台机器的文件中抓取内容并附为变量,然后在另一台机器中使用,这使得你可以实现一些复杂的部署机制,这是ansible命令无法实现的。
1.2 playbooks组成
playbook 由一个或多个 ‘plays’ 组成.它的内容是一个以 ‘plays’ 为元素的列表。
在 ansible 中,play 的内容,被称为 tasks,即任务.在基本层次的应用中,一个任务是一个对 ansible 模块的调用
就一句话:
playbook-->一个或多个play组成-->play内容称为任务tasks-->一个tasks就是一次ansible 模块的调用
PS:为了方便记忆,可以说一个剧本(playbook)有一场或几场戏(play)组成,每场戏都有一个或多个任务tasks
将多个play组织在一个playbook中即可以让它们联同起来按事先编排的机制同唱一台大戏。其主要有以下四部分构成
playbooks组成:
Target section: 定义将要执行 playbook 的远程主机组
Variable section: 定义 playbook 运行时需要使用的变量
Task section: 定义将要在远程主机上执行的任务列表
Handler section: 定义 task 执行完成以后需要调用的任务
而其对应的目录层为五个,如下:
一般所需的目录层有:(视情况可变化)
vars 变量层
tasks 任务层
handlers 触发条件
files 文件
template 模板
Playbook的核心元素为:
Tasks:任务,由模板定义的操作列表。调用模块完成的某操作,一个任务对应一个模块的调用。
Variables:变量
Templates:模板,即使用模板语法的文件
Handlers:由特定条件触发的Tasks
Roles:角色。可以组织 playbook。基于一个已知的文件结构,去自动的加载某些 vars_files,tasks 以及 handlers
二、YAML
Playbooks 的格式是YAML(详见:YAML 语法),也可以看一下,所以这里先介绍一个YAML
2.1 YAML介绍
YAML是一个可读性高的用来表达资料序列的格式。YAML参考了其他多种语言,包括:XML、C语言、Python、Perl以及电子邮件格式RFC2822等。Clark Evans在2001年在首次发表了这种语言,另外Ingy döt Net与Oren Ben-Kiki也是这语言的共同设计者。
YAML Ain't Markup Language,即YAML不是XML。不过,在开发的这种语言时,YAML的意思其实是:"Yet Another Markup Language"(仍是一种标记语言)。其特性:
YAML的可读性好
YAML和脚本语言的交互性好
YAML使用实现语言的数据类型
YAML有一个一致的信息模型
YAML易于实现
YAML可以基于流来处理
YAML表达能力强,扩展性好
更多的内容及规范参见http://www.yaml.org。
2.2 YAML语法
YAML的语法和其他高阶语言类似,并且可以简单表达清单、散列表、标量等数据结构。其结构(Structure)通过空格来展示,序列(Sequence)里的项用"-"来代表,Map里的键值对用":"分隔。下面是一个示例。
说了一堆废话,其实主要了解几个就行了
---.表明YAML一个文件的开始
#表中的所有成员都开始于相同的缩进级别, 并且使用一个 "- " 作为开头(一个横杠和一个空格):如:
---
# 一个美味水果的列表
- Apple
- Orange
- Strawberry
- Mango
#一个字典是由一个简单的 键: 值 的形式组成(这个冒号后面必须是一个空格):如
---
# 一位职工的记录
name: Example Developer
job: Developer
skill: Elite
#字典也可以写成一行,但要用大括号{}把它括起来
---
# 一位职工的记录
{name: Example Developer, job: Developer, skill: Elite}
PS:如果看一下自己写的YAML变成json的效果,可以用一下 《在线YAML和json互转工具》
三、ansible基础元素
在讲playbook之前还需要简单了解一下ansible基础元素。
3.1 变量命名
ansible的变量名仅能由字母、数字和下划线组成,且只能以字母开头。
ps:这里的变量是 playbook 变量
3.2 facts
facts是由正在通信的远程目标主机发回的信息,这些信息被保存在ansible变量中。要获取指定的远程主机所支持的所有facts,可使用如下命令进行:
#执行会返回一堆信息,我在这里就不列出来了,你们自己可以看
ansible <主机名或主机组> -m setup
如执行“ansible hua -m setup”就会得到一些hua主机组的返回信息,这里只有一个主机vm821,在执行任务之前用这些信息做判断有助于更精确的判断,举个简单的例子,可能主机组有centos6和centos7两种系统,如果都用yum安装nginx,那是不是先做一个系统版本的判断,因为他们的yum源是不一样的,然后分别针对性地安装各自的yum源。
3.3 register
把任务的输出定义为变量,然后用于其他任务,示例如下:
tasks:
- shell: /usr/bin/foo
register: foo_result
ignore_errors: True
3.4 通过命令行传递变量
在运行playbook的时候也可以传递一些变量供playbook使用,示例如下:
ansible-playbook test.yml --extra-vars "hosts=www user=hua"
3.5 通过roles传递变量
当给一个主机应用角色的时候可以传递变量,然后在角色内使用这些变量,示例如下:
- hosts: webservers
roles:
- common
- { role: foo_app_instance, dir: '/web/htdocs/a.com', port: 8080 }
四、清单Inventory
在《hualinux 进阶 2-1.1:centos8安装ansible(手把手版 注解版)》中的 “四、机子清单Inventory介绍”已经做了例子的例子讲解,这里详细讲一下吧,也可以看一下 ansible权威指南中的 Inventory文件
ansible的主要功用在于批量主机操作,为了便捷地使用其中的部分主机,可以在inventory file中将其分组命名。默认的inventory file为/etc/ansible/hosts。
inventory file可以有多个,且也可以通过Dynamic Inventory来动态生成。
4.1 inventory文件格式
inventory文件遵循INI文件风格,中括号中的字符为组名。可以将同一个主机同时归并到多个不同的组中;此外,当如若目标主机使用了非默认的SSH端口,还可以在主机名称之后使用冒号加端口号来标明
ntp.hualinux.com
[webservers]
www1.hualinux.com:2222
www2.hualinux.com
[dbservers]
db1.hualinux.com
db2.hualinux.com
db3.hualinux.com
如果主机名称遵循相似的命名模式,还可以使用列表的方式标识各主机,例如:
[webservers]
www[01:50].example.com
[databases]
db-[a:f].example.com
4.2 主机变量
可以在inventory中定义主机时为其添加主机变量以便于在playbook中使用。例如:
[webservers]
www1.hualinux.com http_port=80 maxRequestsPerChild=8080
www2.hualinux.com http_port=8080 maxRequestsPerChild=909
适合 变量名=值,这种形式的都是,即有 = 号的都是,上面的变量名值有
http_port=80 maxRequestsPerChild=8080
www2.hualinux.com http_port=8080 maxRequestsPerChild=909
4.3 组变量
组变量是指赋予给指定组内所有主机上的在playboo中可用的变量。例如:
[webservers]
www1.hualinux.com
www2.hualinux.com
[webservers:vars]
ntp_server=ntp.hualinux.com
nfs_server=nfs.hualinux.com
适合 变更名=值 这样方式的都是,上面的变量有:
ntp_server=ntp.hualinux.com
nfs_server=nfs.hualinux.com
4.4 组嵌套
inventory中,组还可以包含其它的组,并且也可以向组中的主机指定变量。不过,这些变量只能在ansible-playbook中使用,而ansible不支持。例如:
[apache]
httpd1.hualinux.com
httpd2.hualinux.com
[nginx]
ngx1.hualinux.com
ngx2.hualinux.com
[webservers:children]
apache
nginx
[webservers:vars]
ntp_server=ntp.hualinux.com
适合 变更名=值 这样方式的都是,上面的变量有:
ntp_server=ntp.hualinux.com
4.5 inventory参数
在《ansible权威指南》中的“Inventory 参数的说明”中描述如下:
ansible_ssh_host
将要连接的远程主机名.与你想要设定的主机的别名不同的话,可通过此变量设置.
ansible_ssh_port
ssh端口号.如果不是默认的端口号,通过此变量设置.
ansible_ssh_user
默认的 ssh 用户名
ansible_ssh_pass
ssh 密码(这种方式并不安全,我们强烈建议使用 --ask-pass 或 SSH 密钥)
ansible_sudo_pass
sudo 密码(这种方式并不安全,我们强烈建议使用 --ask-sudo-pass)
ansible_sudo_exe (new in version 1.8)
sudo 命令路径(适用于1.8及以上版本)
ansible_connection
与主机的连接类型.比如:local, ssh 或者 paramiko. Ansible 1.2 以前默认使用 paramiko.1.2 以后默认使用 'smart','smart' 方式会根据是否支持 ControlPersist, 来判断'ssh' 方式是否可行.
ansible_ssh_private_key_file
ssh 使用的私钥文件.适用于有多个密钥,而你不想使用 SSH 代理的情况.
ansible_shell_type
目标系统的shell类型.默认情况下,命令的执行使用 'sh' 语法,可设置为 'csh' 或 'fish'.
ansible_python_interpreter
目标主机的 python 路径.适用于的情况: 系统中有多个 Python, 或者命令路径不是"/usr/bin/python",比如 \*BSD, 或者 /usr/bin/python
不是 2.X 版本的 Python.我们不使用 "/usr/bin/env" 机制,因为这要求远程用户的路径设置正确,且要求 "python" 可执行程序名不可为 python以外的名字(实际有可能名为python26).
与 ansible_python_interpreter 的工作方式相同,可设定如 ruby 或 perl 的路径....
PS:ansible官方文档也有一堆特定变量,等学完了,有兴趣也可以看一下,现在不用看。
五、playbook基础
参考《ansible权威指南》的playbook基础,ansible官网的 Playbook Keywords
5.1 主机与用户(Hosts和Users)
playbook中的每一个play的目的都是为了让某个或某些主机以某个指定的用户身份执行任务。hosts用于指定要执行指定任务的主机,其可以是一个或多个由冒号分隔主机组;remote_user则用于指定远程主机上的执行任务的用户。如上面示例中的
-hosts: webnodes
remote_user: root
不过,remote_user也可用于各task中。也可以通过指定其通过sudo的方式在远程主机上执行任务,其可用于play全局或某任务;此外,甚至可以在sudo时使用sudo_user指定sudo时切换的用户。
- hosts: webnodes
remote_user: hua
tasks:
- name: test connection
ping:
remote_user: hua
sudo: yes
也支持从 sudo 执行命令,你也可以登陆后,sudo 到不同的用户身份,而不是使用 root
---
- hosts: webservers
remote_user: yourname
sudo: yes
sudo_user: postgres
5.2 任务列表Tasks
play的主体部分是task list。task list中的各任务按次序逐个在hosts中指定的所有主机上执行,即在所有主机上完成第一个任务后再开始第二个。在运行自下而下某playbook时,如果中途发生错误,所有已执行任务都将回滚,因此,在更正playbook后重新执行一次即可。
task的目的是使用指定的参数执行模块,而在模块参数中可以使用变量。模块执行是幂等的,这意味着多次执行是安全的,因为其结果均一致。
每一个 task 必须有一个名称 name,这样在运行 playbook 时,从其输出的任务执行信息中可以很好的辨别出是属于哪一个 task 的. 如果没有定义 name,‘action’ 的值将会用作输出信息中标记特定的 task.
如果要声明一个 task,以前有一种格式: “action: module options” (可能在一些老的 playbooks 中还能见到).现在推荐使用更常见的格式:”module: options” ,本文档使用的就是这种格式.
如果action一行的内容过多,也中使用在行首使用几个空白字符进行换行。
5.2.1 模块使用 “key=value ” 参数格式
,service moudle 使用 key=value 格式的参数,这也是大多数 module 使用的参数格式:
tasks:
- name: make sure apache is running
service: name=httpd state=running
5.2.2 模拟不使用 “key=value” 参数格式
比较特别的两个 modudle 是 command 和 shell ,它们不使用 key=value 格式的参数,而是这样:
tasks:
- name: disable selinux
command: /sbin/setenforce 0
5.2.3 模拟不使用 “key=value” 参数格式
使用 command module 和 shell module 时,我们需要关心返回码信息,如果有一条命令,它的成功执行的返回码不是0, 你或许希望这样做:
tasks:
- name: run this command and ignore the result
shell: /usr/bin/somecommand || /bin/true
或者使用ignore_errors来忽略错误信息:
tasks:
- name: run this command and ignore the result
shell: /usr/bin/somecommand
ignore_errors: True
5.2.4 用空格或缩进隔开连续的一行
tasks:
- name: Copy ansible inventory file to client
copy: src=/etc/ansible/hosts dest=/etc/ansible/hosts
owner=root group=root mode=0644
5.3 handlers
handlers用于当关注的资源发生变化时采取一定的操作。
“notify”这个action可用于在每个play的最后被触发,这样可以避免多次有改变发生时每次都执行指定的操作,取而代之,仅在所有的变化发生完成后一次性地执行指定操作。在notify中列出的操作称为handler,也即notify中调用handler中定义的操作。
这里有一个例子,当一个文件的内容被改动时,重启两个 services:
- name: template configuration file
template: src=template.j2 dest=/etc/foo.conf
notify:
- restart memcached
- restart apache
‘notify’ 下列出的即是 handlers.
Handlers 也是一些 task 的列表,通过名字来引用,它们和一般的 task 并没有什么区别.Handlers 是由通知者进行 notify, 如果没有被 notify,handlers 不会执行.不管有多少个通知者进行了 notify,等到 play 中的所有 task 执行完成之后,handlers 也只会被执行一次.
Handlers 最佳的应用场景是用来重启服务,或者触发系统重启操作.除此以外很少用到了.
PS:handlers 会按照声明的顺序执行
六、执行一下简单的playbook
上面废话了一堆,因为“Ansible中文权威指南”翻译得还OK,也就直接复制了。
如果我们写好一个playbook,可以通过ansible-playbook命令使用,它的参数和ansible命令类似,如参数-k(–ask-pass) 和 -K (–ask-sudo) 来询问ssh密码和sudo密码,-u指定用户,这些指令也可以通过规定的单元写在playbook 。ansible-playbook的简单使用方法: ansible-playbook xxx.yml 。
6.1 例子 安装yum安装nginx并启动
现在我们就简单写一个:
用官方nginx yum源安装nginx,并启动使它能正常访问,我使用的是hua主机组,只有vm821一个主机,我把之前安装的nginx yum源及nginx都卸载掉
因为我在《hualinux 进阶 2-1.3:ansible常用模块》中的“2.5 yum模块”有安装过nginx,所以我先把它卸载了。一般推荐把rpm包下载出来推荐到目标服务器上安装,我已经放在ansible你本地机,如果没有的可以下载一下
#下载nginx rpm包
mkdri -p /disk1/tools
cd /disk1/tools
wget http://rpms.remirepo.net/enterprise/remi-release-8.rpm
我把 2.5 yum模块用playbook方式,操作如下:
#创建Yaml文件
cd /etc/ansible/
mkdir myYAML
#写入配置
cat>myYAML/install_nginx.yml<<EOF
---
- hosts: hua
remote_user: root
tasks:
- copy:
src: /disk1/tools/nginx-1.18.0-1.el8.ngx.x86_64.rpm
dest: /disk1/tools
- name: yum install nginx
yum:
name: /disk1/tools/nginx-1.18.0-1.el8.ngx.x86_64.rpm
state: present
- name: start nginx
service:
name: nginx
state: started
EOF
cat myYAML/install_nginx.yml
#运行install_nginx.yml
ansible-playbook myYAML/install_nginx.yml
#执行效果如下:
[root@vm82 ansible]# ansible-playbook myYAML/install_nginx.yml
PLAY [hua] ************************************************************************************************
TASK [Gathering Facts] ************************************************************************************
ok: [192.168.3.21]
TASK [copy] ***********************************************************************************************
changed: [192.168.3.21]
TASK [yum install nginx] **********************************************************************************
changed: [192.168.3.21]
TASK [start nginx] ****************************************************************************************
changed: [192.168.3.21]
PLAY RECAP ************************************************************************************************
192.168.3.21 : ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ok=4 ---------------表示4个TASK执行成功
changed=3--------我中途修改最后3个功能,所以它执行改变了3次
unreachable=0-----不可访问为0
failed=0------------出现错误为0
skipped=0---------跳过执行,我没有填写,所有没有跳过,ansible有这个功能
rescued=0--------- 补救0个,因我没在block配置补求rescue,所以0个 ,有兴趣的可以看一上ansible官网的 Playbook Keywords的rescue
ignored=0--------- 忽略0个 ,我也没有yaml配置忽略相关的,所以也是0个
打开浏览器输入hua主机组所在主机的IP地址,我这里是 http://192.168.3.21/,发现能正常访问,如下图:
6.2 例2 同步nginx配置并重启
在上面的例子中我们用到了play中的“主机”和“task”任务,但没有使用到Handlers,
现在添加多一个需求,在上面的基本上把一个nginx配置文件同步过去,然后建立相应的目录并重启
为了方便标准化,我建立一个专门放nginx配置文件的目录
mkdir -p /disk1/ansible_conf/nginx/18
#并把mv821主机中的nginx配置文件做了备份,然后传到/disk1/ansible_conf/nginx/18进行修改,我这里只修改一部分#修改/etc/nginx/conf.d/default.conf nginx配置文件内容 root /disk1/www/hualinux.com;
然后我在yaml中创建一个index.html文件,好让nginx有东西输出
创建一个nginx配置文件yaml,操作如下:
#写入文件,当shell改变的时候就执行 handlers中 名字为 restart nginx 的操作
cat>myYAML/config_nginx.yml<<EOF
---
- hosts: hua
remote_user: root
tasks:
- copy:
src: /disk1/ansible_conf/nginx/18/default.conf
dest: /etc/nginx/conf.d/default.conf
- name: creat nginx index.html
shell: echo 'welcome to hualinux.com' > /disk1/www/hualinux.com/index.html
notify:
- restart nginx
handlers:
- name: restart nginx
service:
name: nginx
state: restarted
EOF
cat myYAML/config_nginx.yml
#执行
ansible-playbook myYAML/config_nginx.yml
[root@vm82 ansible]# ansible-playbook myYAML/config_nginx.yml
PLAY [hua] *********************************************************************************************************
TASK [Gathering Facts] *********************************************************************************************
ok: [192.168.3.21]
TASK [copy] ********************************************************************************************************
ok: [192.168.3.21]
TASK [creat nginx index.html] **************************************************************************************
changed: [192.168.3.21]
RUNNING HANDLER [restart nginx] ************************************************************************************
changed: [192.168.3.21]
PLAY RECAP *********************************************************************************************************
192.168.3.21 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
再次用浏览器打开 http://192.168.3.21 发现内容变了
6.3 优化 例2
在写shell脚本中我们知道,为了能使用shell更加通用,一般用函数或变量的方式,这样方便维护和修改代码,play也一样,也支持变量。
变量用 {{空格 变量名 空格}}
我把config_nginx.yml 配置修改下,以变量的方式,为了方便,我把vm821网站文件删除,nginx配置文件还原
#在vm821上操作,把文件删除,和还原原配置文件
rm -f /disk1/www/hualinux.com/index.html
#default.conf.orig 是我之前备份的配置文件
cp /etc/nginx/conf.d/default.conf.orig /etc/nginx/conf.d/default.conf
#重启nginx
systemctl restart nginx
#vars就是定义变量
#注:取变更名使用 {{ 变量名 }} 变量名前右是有空格的,还有就是 如果在前面则需要加双引号
cat>myYAML/config_nginx.yml<<EOF
---
- hosts: hua
remote_user: root
vars:
- indexhtml: /disk1/www/hualinux.com/index.html
- service: nginx
tasks:
- copy:
src: /disk1/ansible_conf/nginx/18/default.conf
dest: /etc/nginx/conf.d/default.conf
- name: creat nginx index.html
shell: echo 'welcome to hualinux.com' > {{ indexhtml }}
notify:
- restart {{ service }}
handlers:
- name: restart {{ service }}
service:
name: "{{ service }}"
state: restarted
EOF
cat myYAML/config_nginx.yml
ansible-playbook myYAML/config_nginx.yml
注意:
1.变量写在 vars中
2.取变更名使用 {{ 变量名 }} 变量名前右是有空格的,还有就是 如果在前面则需要加双引号,如下面
service: name: "{{ service }}"
如果上面取变量名不加双引号会报错
#执行效果
[root@vm82 ansible]# ansible-playbook myYAML/config_nginx.yml
PLAY [hua] *********************************************************************************************************
TASK [Gathering Facts] *********************************************************************************************
ok: [192.168.3.21]
TASK [copy] ********************************************************************************************************
changed: [192.168.3.21]
TASK [creat nginx index.html] **************************************************************************************
changed: [192.168.3.21]
RUNNING HANDLER [restart nginx] ************************************************************************************
changed: [192.168.3.21]
PLAY RECAP *********************************************************************************************************
192.168.3.21 : ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
再次访问nginx和例子2效果一样