Ansible是一款自动化运维工具,基于 Python paramiko 开发,分布式,无需客户端,轻量级,配置语法使用 YAML 及 Jinja2模板语言,实现了批量系统配置、批量程序部署、批量运行命令等功能。Ansible是基于模块工作的,通过Hosts Inventory定义管理主机列表,Playbooks编排任务执行,实现自动化运维的功能。
Ansible架构图
Ansible架构图如图1所示,共有五大部分组成,分别为Ansible、Inventory、Modules、Plugins及Playbooks。
图1 ansible架构图
(1) Ansible:核心,提供一个框架
(2) Inventory:定义管理主机或主机组
默认在/etc/ansible/hosts下
[suse]
192.168.234.128 ansible_ssh_user=docker
192.168.234.129 ansible_ssh_port=2222
[centos]
192.168.234.130 ansible_connection=local
192.168.234.131 ansible_ssh_pass=123456
Inventory可以定义单独的主机,也可以定义主机组,其中suse、centos就是主机组。Inventory也可以重新定义行为参数,如ansible_ssh_user、ansible_ssh_port等。
(3) Modules:包含各个核心模块及自定义模块
Ansible提供的模块有200余个,项目应用到核心模块有 synchronize(备份),copy(恢复),shell(nas获取),cron(定时任务),user(密码修改),zypper(软件包管理之rpm),setup(获取facts);
扩展模块有UPStartItem(启动项管理),UPInstall(软件包管理),UPUpload(文件上传)
(4) Plugins:完成模块功能的补充,如日志插件、邮件插件等
(5) Playbooks: ansible的任务配置文件,将多个任务定义在剧本中,由ansible自动执行
playbooks采用YAML语法结构,通过空格来展示,序列里的项用"-"来代表,Map里的键值对用":"分隔,包含四个组成部分:
1) Target section:定义要执行playbook的远程主机或主机组
2) Variable section:定义playbook运行时需要使用的变量
3) Task section:定义要在远程主机执行的任务列表
4) Handler section:定义task执行完成之后需要调用的任务
---
- hosts: centos #Target section部分
gather_facts: 'no'
vars: #Variable section部分
haproxy_rpm: haproxy-1.5.14-3.el7.x86_64.rpm
remote_dir: /opt/
haproxy_config_path: /etc/haproxy/haproxy.cfg
tasks: #Task section部分
- name: copy haproxy.rmp to remote host
copy: src=/tmp/{{ haproxy_rpm }} dest={{ remote_dir }}
- name: install haproxy
shell: chdir={{ remote_dir }} creates=yes rpm -ivh {{haproxy_rpm}}
failed_when: False
register: result
- debug: var=result
- name: copy haproxy.cfg to remote host
copy: src=/tmp/haproxy.cfg dest={{ haproxy_config_path }}
notify: restart haproxy
handlers: #Handler section部分
- name: restart haproxy
shell: systemctl restart haproxy.service
failed_when: False忽略错误,不影响task继续执行,添加register: result,将输出保存到result变量中,再添加debug即可输出错误信息。
notify: restart haproxy,只有当前task任务发现改变时才会通知handler,执行任务,即当/tmp/haproxy.cfg配置信息有修改之后,便会重启haproxy。
Ansible流程图
(1)ansible执行流程图
Ansible执行ad-hoc命令流程如图2所示。当我们执行一条ad-hoc命令时,首先进入ansible入口(bin/ansible),调用自身类方法parse(self),读取配置ansible配置文件信息,返回解析好的(options, args);其次调用自身类方法run(self, options, args),从/etc/ansible/hosts下获取过滤后的管理主机列表,/lib/ansible/modules加载匹配模块。Ansible得到管理主机列表及需要执行任务的模块及其参数之后,执行Runner.run(),与管理主机建立连接,调用callback,最终返回执行任务结果。
图2 ansible执行流程图
(2)ansible工作原理图
图3 ansible工作原理图
上图描述的是ansible执行任务的工作原理图。Ansible执行任务前,首先与管理主机建立连接,创建$HOME/.ansible/tmp/临时文件夹/,并生成以模块名命名的python文件,调用subprocess.Popen()方法执行python脚本,返回任务执行结果,最终删除$HOME/.ansible/tmp/临时文件夹/。不同的是连接是local方式则是在ansible本端生成临时文件夹其及python脚本(如synchronize模块),而建立的连接是ssh方式则是在ansible管理端生成(如copy模块)。
Ansible API
Ansible提供了一个python直接调用的API,便于我们对其进行二次开发和整合。核心程序如下:
import ansible.playbook
from ansible import callbacks
from ansible import utils
import json
stats=callbacks.AggregateStats()
runner_cb=callbacks.PlaybookCallbacks(verbose=utils.VERBOSITY)
playbook_cb=callbacks.PlaybookRunnerCallbacks(stats,verbose=utils.VERBOSITY)
res=ansible.playbook.PlayBook(
playbook=playbook,
stats=stats,
callbacks=playbook_cb,
runner_callbacks=runner_cb,
inventory=inventory,
forks=10
)
result=res.run()
data=json.dumps(result)
其中,playbook是指定要执行的yaml格式文件(playbook);stats用来收集playbook执行期间的状态信息,并进行汇总;callbacks用来输出playbook执行最终的结果;runner_callbacks用来输出playbook执行期间的结果;inventory定义管理主机列表;forks定义任务最大并发数。
Ansible有一个callback_plugins的功能,可以对playbook执行状态做判定。默认情况下,返回执行状态及具体执行结果的python文件是放在/usr/share/ansible/plugins/callback/下,二次开发时必须以轮询方式获取结果,这种方式并不能实时地反馈结果。另一种更好的方式是自定义playbook_cb,引入rabbitmq,将执行状态及具体执行结果发送给rabbitmq,后台只需从rabbitmq中实时消费,获取实时执行结果。
模块扩展
虽然ansible本身给我们提供了200余个模块,但是实际开发中现有的模块未能满足项目开发需求,我们需要开发自定义模块应用于项目开发中。而ansible提供了AnsibleModule辅助类来简化工作,支持解析参数输入,JSON格式返回输出,调用外部程序。以下是自定义的启动项管理模块(UPStartItem)部分程序详解。
def main():
module = AnsibleModule(
#argument_spec:定义模块所需参数的数据字典
argument_spec = dict( #required=True,参数必须有;required=False,参数可不选
src = dict(required=True),
original_basename = dict(required=False), #no_log=True,此模块的任何行为都不记录日志
content = dict(required=False, no_log=True),
dest = dict(required=False), #type定义参数类型,type='bool,该参数为布尔类型
backup = dict(default=False, type='bool'), #aliases定义参数别名
force = dict(default=True, aliases=['thirsty'], type='bool'),
validate = dict(required=False, type='str'), #choices定义参数选择可选值
state = dict(default='present', choices=['absent', 'present']), #default指定参数默认值
regexp = dict(default=None),
line = dict(aliases=['value']),
oldScript = dict(required=True),
newScript = dict(required=True),
linedest=dict(required=True, aliases=['name', 'destfile']),
),#mutually_exclusive定义互斥参数
mutually_exclusive=[['insertbefore', 'insertafter']],#add_file_common_args支持file模块参数
add_file_common_args=True,#supports_check_mode模块是否支持检测模式
supports_check_mode=True
) state = module.params['state'] #解析输入
……
(rc,out,err) = module.run_command(validate % src) #调用外部程序
module.exit_json(**res_args) #JSON格式返回结果
from ansible.module_utils.basic import *
main()