1

扩展ansible

ansible采用厨房水槽的方式来实现功能。写这本书的时候,ansible里边大概有300多个模块可用。此外,还有大量的回调插件、查询插件、过滤插件、以及动态库存插件。即使具有这么多功能,还是有必要添加新功能的需要。

本章会探索给ansible添加新功能的方法:

  • 开发模块
  • 开发插件
  • 开发动态库存插件
厨房水槽: Kitchen sink, 来源于everything but the kitchen sink。这是二战时期的一个俗语。美国空军战士经常用它来形容他们在敌占区上空所遇到的防空炮火,指敌人炮火猛烈(除了洗碗槽外,各式各样的炮弹齐发)。 现在这个俗语导出使用,可以指"无所不包,一应俱全"。

开发模块

模块是ansible的马达。它们只提供一种抽象,可以让剧本简单明了的启动起来。ansible核心开发团队维护的ansible核心模块大概有150多个,涵盖了云、命令、数据库、文件、网络、包、源控制、系统、工具、web设施等等。另外,还有100多个其他额外模块,主要由社区贡献者维护,它们扩展了这些类别的很多功能。真正神奇就发生在模块代码内部,它们接受传入的参数,然后努力建立预期的结果。

ansible中的模块就是传输给远程主机并执行的那点代码。它们可以使用远程主机可以执行的任何语言实现。然而,ansible提供了一些非常有用的python语言模块的快捷方式。

基本模块结构

一个模块的存在就是满足一个需求--需要在主机上完成一些工作。模块,通常但并不总是,期望输入,并返回一些类型的输出。模块同时力图是幂等的,允许一次又一次的运行模块,而不产生负面影响。在ansible中,输入是以命令行参数的形式给到模块,输出是以JSON的形式给到标准输出。输入一般使用空格分隔的key=value语法提供,它是由模块将这些结构分解成可用数据的。如果使用Python, 有一些方便的函数来管理这些,如果使用其他语言,则完全由模块代码来处理输入。

输出是JSON格式的。惯例规定,在成功的场景中,JSON输出至少要有一个key, changed,这个值是布尔类型,表示模块执行结果是否导致改变。附加数据也可以返回,这些可以定义具体改变了什么,或者给剧本返回一些重要的信息便于后续使用。

另外,主机事实可以在JSON数据中返回,自动基于模块执行结果创建主机变量。后面我们会详细看到更多这方面的内容。

自定义模块

ansible提供了一种简单的机制来利用来自ansible外部的自定义模块。我们在第一章已经了解到,ansible会查找很多位置来查找请求的模块。一个这样的路径,第一个路径就是顶级剧本所在目录下面的library/目录。我们可以将我们的自定义模块放在这个目录下面,这样我们就可以在我们的剧本中使用了。

模块还可以嵌入到角色里边,为角色添加一些可能会依赖的功能。这些模块就只能被包含它们的角色或其他在包含模块的角色之后的其他角色和任务执行。要用角色表达一个模块,就可以将模块放到角色根目录下面的library子目录下面。

简单模块

为了演示python实现模块的简单,让我们创建一个简单的模块。这个模块的目的就是从远程克哦被一个源文件为目标文件,我们可以从它构建的简单任务。要开始我们的模块,我们首先创建模块文件。为了方便访问我们的新模块,我们就在工作目录下面创建一个library目录。

建立remote_copy.py,其代码如下:

#!/usr/bin/env python
# coding=utf-8

import shutil
from ansible.module_utils.basic import *

def main():
    module = AnsibleModule(
        argument_spec = dict(
            source = dict(required=True, type="str"),
            dest = dict(required=True, type="str")
        )
    )

    shutil.copy(module.params['source'],
                module.params['dest'])
    module.exit_json(changed=True)

if __name__ == '__main__':
    main()

然后建立一个测试剧本:

---
DOCUMENTATION = '''
---
module: remote_copy
version_added: future
short_description: Copy a file on the remote host
description:
  - The remote_copy module copies a file on the remote host from a given source to a provided destination.
options:
  source:
    description:
      - Path to a file on the source file on the remote host
    required: True
  dest:
    description:
      - Path to the destination on the remote host for the copy
    required: True
author:
  - Jesse Keating
'''

这个字符串的格式完全是YAML格式的,具有一些包含哈希结构的顶级key, 例如options。

然后我们可以使用ansible-doc来查看模块的文档内容:

ansible-doc -M library/ remote_copy | cat -
注意文档描述一定要用标准的YAML格式,否则查看文档会报错的。
提供事实数据

和模块退出附带的数据返回类似,模块还可以直接创建主机事实,通过返回名字为ansible_facts的数据。直接从模块提供事实就消除了使用后续的set_fact任务注册任务返回值的操作。

facts = {'rc_source': module.params['source'],
                'rc_dest': module.params['dest']}
module.exit_json(changed=True, ansible_facts=facts)

那么在剧情中我们就可以直接在debug中使用事实中的某个数据了。

- name: show a fact
  debug:
    var: rc_dest

而模块如果不返回facts, 那么我们就需要注册输出,并使用set_fact来为我们创建事实,就像这样:

- name: do a remote copy
  remote_copy:
    source: /tmp/foo
    dest: /tmp/bar
  register: mycopy

- name: set facts from mycopy
  set_fact:
    rc_dest: "{{ mycopy.dest }}"
检查模式(check mode)

ansible 1.1之后,支持检查模式,就是一种假装对系统做修改但是实际上并没有改变系统的一种操作模式。检查模式对于测试修改是否真正发生,或者系统状态自从上次ansible运行是否漂移都非常有用。 检查模式依赖于模块是否支持检查模式,并返回数据就像它实际上已经做了改变一样。

支持检查模式

为了只是模块支持检查模式,我们在创建模块对象的时候,需要设置一个参数support_check_mode。
注意设置这个参数可以在定义argument_spec之前,也可以在它之后。这里我们在argument_spec定义之后:

module = AnsibleModule(
  argument_spec = dict(
    source=dict(required=True, type='str'),
    dest=dict(required=True, type='str')
  ),
  supports_check_mode=True
)
处理检查模式

监测什么时候检查模式是活动状态非常容易。模块对象就包含一个check_mode属性,如果检查模式是活动状态,这个属性就是True。我们的模块中,我们希望在执行复制之前先看检查模式是否激活。我们可以简单的将复制行为放入一个if语句里边,避免检查模式是活跃的时候去拷贝了。返回可以发生没有任何改变:

if not module.check_mode:
  shutil.copy(module.params['source'],
              module.params['dest'])

那么,现在我们可以在运行剧本的时候添加一个-C参数来执行。这个参数让剧本执行进行检查模式。 执行完后,看执行的输出好像创建并复制了文件,但是真实却不存在这些文件。

开发插件

插件是另外一种扩展和修改ansible功能的方式。模块是作为任务来执行的,而插件是在其他很多地方利用的。 插件会分解为一些类型,具体依赖于它们在它们能插入ansible执行的什么地方。ansible这些范围的每个都带有一些插件,终端用户可以创建在这些特定地方的它们自己的功能扩展。

连接类型插件

ansible任何时间对主机建立连接来执行任务,是使用了一个连接插件。ansible自带了一些连接插件,包括ssh, docker, chroot, local和smart。通过创建连接插件,ansible可以利用附加的连接机制来连接远程主机,这对于连接一些新类型的系统会非常有用,就像网络交换器,或者某天可以连接你的冰箱。 创建连接插件有点超出本书的范围;然而,最简单的起步的方法就是读现有的连接插件的实现。现有插件可以在runner/connection_plugins/里边找到源代码。

shell插件

和连接插件差不多,ansible用shell插件在shell环境执行一些东西。 每个shell稍微有所不同,ansible关心按顺序执行命令、重定向输出、发现错误、以及其他交互。ansible支持几种shell, 包括sh, csh, fish以及powershell。我们可以通过实现一个新的shell插件添加更多shell。

lookup插件

lookup插件是ansible从主机系统如何访问外部数据源,以及实现语言特性,例如循环构建(with_*)。

vars插件

事实搜集插件

过滤器插件

回调插件

回调是放在ansible执行里边的,可以插入一些东西来添加功能。有一些预期的回调点可以注册来在这些点触发用户自定义行为。下面是一些可能触发功能的点:
• runner_on_failed
• runner_on_ok
• runner_on_skipped
• runner_on_unreachable • runner_on_no_hosts
• runner_on_async_poll
• runner_on_async_ok
• runner_on_async_failed
• playbook_on_start
• playbook_on_notify
• playbook_on_no_hosts_matched
• playbook_on_no_hosts_remaining • playbook_on_task_start
• playbook_on_vars_prompt
• playbook_on_setup
• playbook_on_import_for_host
• playbook_on_not_import_for_host • playbook_on_play_start
• playbook_on_stats

ansible运行到达这些装袋的每个点,任何有代码的插件都可以在这些点上被执行。这就提供了极大的能力去扩展ansible而无需修改基础代码。

回调可以用很多方式利用:

  • 修改屏幕上的显示。
  • 更新

行为插件

分布插件(distributing plugins)

开发动态库存插件

列举主机

列举主机变量

简单库存插件

优化脚本性能

总结

ansible是一个强大的工具,然而,有时候它可能没有提供某些需要的所有功能。并不是每一个功能都适合由主项目来支持,也不可能集成自定义的专有数据源。出于这些原因,ansible里边存在可以扩展功能的设施。由于共享模块的基本代码,创建和使用自定义模块变得很容易。很多不同类型的插件可以创建并和ansible一起使用,以各种方式影响操作。超出ansible支持的库存源依然能相对容易和高效的使用。

在所有情况下,都有一种机制来提供模块、插件和库存源,以及依赖于增强功能的剧本和角色,使其无缝分布。

词汇

  • everything but the kitchen sink: 无所不包、一应俱全。

导航


老将廉颇
878 声望297 粉丝