剖析 | Ansible工作原理及模块扩展
分类:技术社区 发布时间:2017/3/23 0:00:00


  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()

体验创新云技术带来核心业务效率显著提升
立即预约,加速企业数字化转型进程
Copyright ⓒ 2022 苏州博纳讯动软件有限公司 国徽 苏ICP备13004761号 法律声明及隐私政策