我们想听听您的意见!帮助我们洞悉 Ansible 生态系统的现状。
参加 2024 年 Ansible 项目调查

创建自定义事件驱动的 Ansible 源插件

创建自定义事件驱动的 Ansible 源插件

我们被包围了!我们的现代系统和应用程序不断地生成事件。这些事件可能是由服务请求、应用程序事件、健康检查等生成的。在围绕我们所做的一切的事件流量中,我们拥有大量信息,事件驱动型 Ansible 允许对传入事件进行自动响应。

但我们不仅完全沉浸在事件数据中,而且还被事件源包围。花点时间想想你的组织甚至你的家庭,想想有多少设备或应用程序正在生成数据,如果只能够轻松收集这些数据,它们就可以派上用场。

事件驱动型 Ansible 中的事件源插件充当 Ansible 和事件生成应用程序和服务之间的桥梁。事件驱动型 Ansible 已经拥有一系列事件插件来从各种来源获取事件。但是,如果你的源插件不在该列表中怎么办?或者,如果你是一家想要将事件驱动型 Ansible 连接到自己的解决方案的 Red Hat 合作伙伴怎么办?好消息是,为事件驱动型 Ansible 开发事件源插件可能是一项相对轻松的任务。

什么是源插件?

事件驱动型 Ansible 利用规则手册来编纂对事件的响应。规则手册结合了源、条件和操作。操作是根据来自源的一个或多个事件条件执行的。事件源插件允许规则手册从云服务、应用程序和代理等接收事件。如果没有事件源,则不会收到事件,也不会执行操作。

事件源是包含在 Ansible 内容集合中的 Python 脚本。在规则手册中,事件源按名称调用,并且包含在规则手册源配置中的参数传递给事件源插件。在事件源插件中,例程应编写为异步的,以防止阻塞,从而允许在多个事件源上尽可能有效地接收和处理事件。出于这个原因,你会注意到像 Kafka 和 webhook 这样的所有初始源插件都利用了异步 IO 范式。

源插件指南

对新的事件源插件进行范围界定应该很简单。出于这个原因,插件没有太多要求。要开始插件开发,以下是一些有关源插件的指南

  1. 源插件必须包含一个特定的入口点。
  2. 每个源必须具有嵌套键,这些键与主函数预期的参数匹配。
  3. 源插件应该用预期用途、预期参数和规则手册示例进行文档化。
  4. 事件源插件应该在集合中分发。
  5. Python 例程应该编写为非阻塞或异步的。
  6. 源插件应该包含一种在事件驱动型 Ansible 之外测试插件的方法。

为了演示其中一些指南,我将使用我创建的一个示例源插件。我的源插件称为 new_records,它监视 ServiceNow 中的表,以查找要创建的新记录(例如,新的事件、问题和变更请求)。如果你想自己测试这个源插件,你需要一个 ServiceNow 实例,你可以将其作为ServiceNow 开发人员计划的一部分进行配置。 

你开始测试我的示例插件之前,请注意,这个插件来自一个水平不足的 Python 人员,它只是一个示例,并没有得到认可或建议用于生产使用。ServiceNow 实例也对 REST 资源有速率限制规则,如果你过于频繁地轮询,可能会遇到这些规则。考虑到事件推送范式是事件驱动型 Ansible 源插件的首选,这个源插件的更好的实现可能是创建一个 ServiceNow web 服务,将事件详细信息推送到事件聚合器!在这种情况下,我们的集成应用程序(ServiceNow)将把事件详细信息推送到 JetStream 或 Kafka 之类的东西(已经有一个事件源插件!)

源插件必须包含一个特定的入口点。

源插件需要一个非常具体的入口点配置。此入口点表示 Python 脚本中的一个函数,该函数将由 ansible-rulebook 调用,ansible-rulebook 是事件驱动型 Ansible 中负责执行规则手册的组件。让我们看一下我的 ServiceNow 自定义源插件的最开始部分

import asyncio
import time
import os
from typing import Any, Dict
import aiohttp

# Entrypoint from ansible-rulebook
async def main(queue: asyncio.Queue, args: Dict[str, Any]):

在插件开始的所有导入语句之后,你可以看到入口点是一个名为 main 的异步函数,它接受两个参数。第一个参数是一个 asyncio 队列,当此源在规则手册中使用时,将由 ansible-rulebook 使用。第二个参数创建一个参数字典,我的特定源插件需要该字典才能与我的 ServiceNow 实例建立连接。该字典将包含我的 ServiceNow 实例的用户名、密码和 URL 等内容。就入口点而言,实际上这就是预期的全部内容。 

每个源必须具有嵌套键,这些键与主函数预期的参数匹配。

这是一种稍微复杂的说法,即我的自定义 ServiceNow 事件插件中需要的参数也应该是用于配置源插件的规则手册中的键。为了演示这一点,请查看规则手册中我的自定义插件的源配置,然后查看 ansible-rulebook 执行的 main 函数预期的参数

规则手册示例

- name: Watch for new records
  hosts: localhost
  sources:
    - cloin.servicenow.new_records:
            instance: https://dev-012345.service-now.com
            username: ansible
            password: ansible
            table: incident
            interval: 1

插件代码

# Entrypoint from ansible-rulebook
async def main(queue: asyncio.Queue, args: Dict[str, Any]):

    instance = args.get("instance")
    username = args.get("username")
    password = args.get("password")
    table   = args.get("table")
    query   = args.get("query", "sys_created_onONToday@javascript:gs.beginningOfToday()@javascript:gs.endOfToday()")
    interval = int(args.get("interval", 5))

需要注意的是,如果你担心分发包含凭据或其他敏感参数的规则手册,ansible-rulebook 还接受在 vars 文件中或通过环境变量使用 --vars--env-vars 设置的变量。这意味着你的规则手册源配置可能更像

- name: Watch for new records
  hosts: localhost
  sources:
    - cloin.servicenow.new_records:
        instance: {{ SN_HOST }}
        username: {{ SN_USERNAME }}
        password: {{ SN_PASSWORD }}
        table: incident
        interval: 1

源插件应该用用途、预期参数和规则手册示例进行文档化。

这是一种不言而喻的事情,即使是我这样水平不足的 Python 开发人员也能理解。事实上,这实际上是我 2023 年的新年决心之一。请查看我的源插件顶部的示例

"""
new_records.py

Description:
event-driven-ansible source plugin example
Poll ServiceNow API for new records in a table
Only retrieves records created after the script began executing
This script can be tested outside of ansible-rulebook by specifying
environment variables for SN_HOST, SN_USERNAME, SN_PASSWORD, SN_TABLE

Arguments:
  - instance: ServiceNow instance (e.g. https://dev-012345.service-now.com)
  - username: ServiceNow username
  - password: ServiceNow password
  - table:  Table to watch for new records
  - query:  (optional) Records to query. Defaults to records created today
  - interval: (optional) How often to poll for new records. Defaults to 5 seconds

Usage in a rulebook:
- name: Watch for new records
  hosts: localhost
  sources:
    - cloin.servicenow.new_records:
            instance: https://dev-012345.service-now.com
            username: ansible
            password: ansible
            table: incident
            interval: 1
  rules:
    - name: New record created
      condition: event.sys_id is defined
      action:
            debug:
"""

相当不错的指南,对吧?该文档非常清楚地说明了这是一个事件驱动型 Ansible 插件,插件的预期功能、插件接受的参数以及如何在规则手册中使用该插件。 

事件源插件应该在集合中分发。

Ansible 内容集合代表了 Ansible 内容可以轻松分发的模型。通常,这些集合包含插件、角色、剧本和文档等内容,并展示了 Ansible 的可扩展性。事件源插件和规则手册成为可以通过 Ansible 内容集合分发的附加内容类型。这在我的插件文档中说明如下

- name: Watch for new records
  hosts: localhost
  sources:
    - cloin.servicenow.new_records:
            instance: https://dev-012345.service-now.com

Python 例程应该编写为非阻塞或异步的。

异步模型表明,例如,new_records 源插件对 ServiceNow API 的请求不应该阻塞或减慢另一个源插件对另一个 API 的请求。通过在插件中使用 asyncio 以及 async 和 await,我们只需暂停该例程并等待结果,而不是阻塞其他例程执行。如果你将两个仅使用同步例程编写的源插件组合到同一个规则手册中,你可能会发现你的规则手册执行缓慢,或者很久之后才对事件做出反应。以下是我源插件中的示例

            async with session.get(f'{instance}/api/now/table/{table}?sysparm_query={query}', auth=auth) as resp:
                if resp.status == 200:

                    records = await resp.json()
                    for record in records['result']:
…
                      await queue.put(record)

注意关键字asyncawaitasync 关键字让 Python 知道这个协程将在事件循环中异步执行,同时等待来自任何“等待”的结果,即由await 关键字指定的,在本例中,是来自 ServiceNow API 调用的响应。

值得一提的另一行是上面queue.put(record) 代码段中的最后一个await。这是一行必不可少的代码,因为它允许规则手册引擎使用该记录。通过将 ServiceNow API 返回的记录放入队列,我们能够根据 API 请求返回的记录执行在规则手册中定义的操作。 

源插件应该包含一种在事件驱动型 Ansible 之外测试插件的方法。

这并不是创建源插件的硬性规则。我认为它在插件开发过程中更有帮助,可能更像最佳实践或一般提示,而不是其他任何东西。通过包含一个仅在直接通过运行调用脚本时运行的函数,例如:python new_records.py,你可以快速测试对脚本的更改,而无需先设置规则手册并启动 ansible-rulebook。对于我的示例插件,我使用以下内容

# this is only called when testing plugin directly, without ansible-rulebook
if __name__ == "__main__":
    instance = os.environ.get('SN_HOST')
    username = os.environ.get('SN_USERNAME')
    password = os.environ.get('SN_PASSWORD')
    table   = os.environ.get('SN_TABLE')

    class MockQueue:
        async def put(self, event):
            print(event)

    asyncio.run(main(MockQueue(), {"instance": instance, "username": username, "password": password, "table": table}))

如果你看一下该代码示例,你会看到一条注释,说明这实际上只是为了直接测试 Python 脚本。如果你想自己测试这段代码,你可以定义四个环境变量(例如,export SN_TABLE=incident...),然后执行该脚本。从那里,打开你的 ServiceNow 实例,在你要监视的表中创建一个新记录(对于 SN_TABLE=incident, 你需要创建一个新事件),然后你会看到脚本会打印出新创建的记录。










Ansible 的禅宗

Ansible 的禅宗

这篇博文基于我在芝加哥举行的 AnsibleFest 2022 和虚拟演讲。

最近,有人建议Tim Peters 的“Python 之禅”作为设计优质自动化内容的总体指导原则。这让我停顿了一下,因为在我看来这并不合适。虽然“Python 之禅”中确实有一些非常好的建议可以应用于 Ansible 内容,但完全采用它并不能提供 Ansible 能够实现且闻名的最佳用户体验。它作为内容设计指导原则的存在会给人错误的印象,并强化我们不想推荐的思维模式。

这让我思考,什么是 Ansible 的“禅”?

我考虑了“Python 之禅”的精神,然后我又回到了 2016 年我在 Red Hat 峰会上首次联合展示的 Ansible 最佳实践演讲。在那次演讲中,我说 Ansible 从一开始就以一种哲学为设计理念。

“Ansible 风格”是提供一种简单、强大且无代理的自动化工具。Ansible 使得即使没有特殊编码技能的用户也能在多个 IT 领域执行强大的操作。它的人类可读的自动化可以被每个 IT 团队利用和共享,这样他们可以快速提高工作效率并贡献自己的专业知识。它的无代理架构提供了跨所有 IT 基础设施领域的灵活应用能力。

Ansible simple powerful agentless

正是这种设计理念,本文中所有内容都或多或少地与之相关。

除了“Python 之禅”和我的 Ansible 最佳实践演讲之外,我还考虑了我多年来在 Ansible 生态系统中与数百位用户的交流中听到的内容。我总结了这 20 条 Ansible 格言。

Ansible zen image

  1. Ansible 不是 Python。
  2. YAML 不适合编码。
  3. 剧本不是用于编程的。
  4. Ansible 用户(很可能)不是程序员。
  5. 清晰胜于混乱。
  6. 简洁胜于冗长。
  7. 简单胜于复杂。
  8. 可读性很重要。
  9. 帮助用户完成工作是最重要的。
  10. 用户体验胜过意识形态纯粹性。
  11. “魔法”战胜手动操作。
  12. 在给用户选项时,使用约定优于配置。
  13. 声明式通常优于命令式——大多数情况下。
  14. 专注避免复杂性。
  15. 复杂性扼杀生产力。
  16. 如果实现很难解释,那它就是一个坏主意。
  17. 每个 shell 命令和 UI 交互都是一个自动化机会。
  18. 仅仅因为某件事有效,并不意味着它不能改进。
  19. 摩擦应该尽可能地消除。
  20. 自动化是一段永无止境的旅程。

您的 Ansible 自动化内容并不一定需要遵循这些指导,但它们是值得记住的好主意。这些格言是观点,可以争论,有时也会相互矛盾。重要的是,它们传达了一种从 Ansible 和您的自动化中获得最大收益的思维方式。

让我带您深入了解每个格言,并解释它们对您的自动化实践的意义。

Ansible 不是 Python。YAML 不适合编码。剧本不是用于编程的。Ansible 用户(很可能是)不是程序员。

这些格言是为什么将编程语言的指导方针应用于优质 Ansible 自动化内容看起来不对劲的核心原因。正如我所说,这会给人错误的印象,并强化我们不想推荐的思维模式——Ansible 是一种用于为您的剧本编写代码的编程语言。

这些格言都用不同的方式表达了同一件事——尤其是前三条。如果您试图在您的剧本和角色中“编写代码”,您就是在给自己制造失败的条件。Ansible 基于 YAML 的剧本从一开始就不是为了编程而设计的。

因此,当我看到 Python 风格渗透到 Ansible 用户所见所做的事情中时,我会感到困扰。如果您用 Python 编写代码,这可能是自然的,而且有道理,但大多数 Ansible 用户都不是 Python 专家。因此,当这些风格被整合进来时,可能会带来挑战和混乱,从而引入摩擦,降低他们的用户体验和 Ansible 所提供的价值。

由于 Ansible 不是一种编程语言,您组织中的所有部门都可以参与整个 IT 堆栈的自动化,而不是依赖于熟练的程序员来理解您的操作,为其编写和维护代码。

如果您是一位程序员,正在创建 Ansible 模块和插件,请假设您不是您所开发内容的目标受众,您的目标受众不会拥有您所拥有的相同技能和资源。

清晰胜于混乱。简洁胜于冗长。简单胜于复杂。可读性很重要。

这些实际上只是对“Python 之禅”中格言的解释。最后一个直接取自它,因为您无法改进完美。

在最初的 Ansible 最佳实践演讲中,我们建议用户优化可读性。这在今天更加适用。如果做得正确,您的内容可以成为您的工作流程自动化的文档。花时间使您的自动化尽可能清晰简洁。对您所创建的内容进行迭代,并始终寻找简化和澄清的机会。

这些格言不仅适用于编写剧本和创建角色的人员。如果您是模块开发人员,请考虑您的工作如何帮助用户,保持清晰简洁,以简单的方式做事,并完成工作。

帮助用户完成工作是最重要的。用户体验胜过意识形态纯粹性。

无论您是创建模块、插件和集合,还是编写剧本或设计跨域混合自动化工作流程——Ansible 都能帮助您完成工作。始终考虑并努力最大限度地提高用户体验。不要陷入对标准或意识形态纯粹性的严格解释,而将负担转嫁给最终用户。

“魔法”战胜手动操作。

阿瑟·C·克拉克写道:“任何足够先进的技术都与魔法无异。”

Ansible 中的“魔法”是它的剧本引擎和模块系统。它是 Ansible 通过抽象用户从所有复杂实现细节中摆脱出来,以直接且易于访问的方式提供强大且灵活的功能的方式。这使用户免于执行耗时且容易出错的手动操作或编写脆弱的一次性脚本和代码,使他们有时间将宝贵的专业知识应用于需要的地方。

设计出让用户惊叹的自动化,可以使困难或乏味的任务变得轻松自如,几乎毫不费力。努力提供强大的节约时间的功能,这些功能可以快速部署并利用它们来完成工作。

在给用户选项时,使用约定优于配置。

我是约定优于配置的坚定支持者,我认为它在 Ansible 社区中并没有得到足够的重视。约定优于配置是一种设计范式,它试图减少开发人员需要做出的决策数量,而不会必然失去灵活性,因此他们不必重复自己。它由 Ruby on Rails 推广。

使用您作品的剧本开发人员只需要指定他们自动化任务和工作流程中独特和非常规的方面,而不需要更多。努力减少用户需要做出的决策和实现细节的数量。花时间为他们处理最常见的用例。努力为模块、插件和角色提供尽可能多的合理默认值。优化用户快速完成工作。

声明式通常优于命令式——大多数情况下。

这条格言尤其适用于 Ansible 内容集合开发人员。Ansible 从设计之初就是一种期望状态引擎。首先以声明式的方式思考。如果确实没有办法以声明式的方式设计某件事,那么使用命令式(过程式)方法。

声明式意味着配置由一组事实保证,而不是由一组指令保证,例如,“应该有 10 台 RHEL 服务器”,而不是“根据运行的 RHEL 服务器数量,启动/停止服务器,直到有 10 台,并告诉我是否成功”。

这条格言是“用户体验胜过意识形态纯粹性”格言在实践中的一个例子。Ansible 并没有严格遵循声明式自动化方法,而是结合了声明式和命令式方法。这种组合为您提供了灵活性,您可以专注于需要做的事情,而不是严格遵循一种范式。

专注避免复杂性。复杂性扼杀生产力。

请记住,复杂性会扼杀生产力。Red Hat 的 Ansible 团队非常认真地对待这一点,并相信这一点。这不仅仅是一个营销口号。自动化可以消除复杂性,并为您提供您永远无法获得足够多的东西——时间。

遵循Linux 原则,即做好一件事,而且只做好一件事。让角色和剧本专注于特定目的。多个简单的角色和剧本比一个包含大量条件语句和“编程”的大型单一剧本更好,而 Ansible 不适合进行这种编程。

我们努力简化 Ansible 的设计方式,并鼓励您也这样做。努力简化您所自动化的内容。

如果实现很难解释,那它就是一个坏主意。

这条格言与“可读性很重要”一样,也是直接取自“Python 之禅”,因为您无法改进完美。

在关于文学编程的论文中,唐纳德·克努特写道:“不要想象我们的主要任务是指示计算机该做什么,而是让我们专注于向人类解释我们想要计算机做什么。”因此,如果您无法轻松地解释或记录您的实现,那么它就是一个需要重新考虑或废弃的坏主意。如果很难解释,那么其他人如何理解、使用和调试它呢?Kernighan 定律说:“调试比编写代码困难两倍。因此,如果您尽可能巧妙地编写代码,那么,根据定义,您不够聪明,无法调试它。”

Ansible 旨在适应人们的真实思维和工作方式。回想一下我之前说过的 Ansible 剧本是人类可读的自动化,不需要特殊的编码技能。利用这一点。然后,如果您难以解释您要做什么,请暂停并重新考虑您的实现以及您要自动化的流程。如何使它更容易解释?我的流程可以改进或简化吗?如何简化和澄清?我能否将其分解为更小、更集中的部分并对其进行迭代?

这将帮助您更早地识别出坏主意,并避免会随着时间的推移而减慢您和您的组织速度的摩擦。

每个 shell 命令和 UI 交互都是一个自动化机会。

这条格言来自我多年来谈论 Ansible 和自动化的个人经验。有时我被问到应该自动化什么。有时,我会受到挑战,认为像 Ansible 这样的自动化工具是不必要的或不适用于他们正在做的事情。无论我们是在讨论 RHEL、Windows、网络基础设施、安全、边缘设备还是云服务,我的回应多年来基本上都是一样的。我重复了它很多次,以至于我开玩笑地将这个观点变成了我自己的自动化定理。所以,如果您愿意,可以称之为“Appnel 关于自动化的定理”。

如果您想知道应该自动化什么,请查看任何人在 Linux shell 中输入的内容以及在用户界面中单击的内容。然后问问自己“这可以自动化吗?”然后问“自动化这件事有什么价值?”大多数 Ansible 模块包装了命令行工具或使用 UI 背后的相同 API。

在确定了足够多的自动化内容后,从那些造成最多痛苦以及那些您可以快速完成的内容开始。请记住,您想要创造一个良性循环,在整个组织中释放可靠性、反馈和建立信任。快速展示进展和商业价值将有助于实现这一点。

仅仅因为某些东西有效,并不意味着它不能被改进。摩擦应该尽可能消除。

这第一句格言恰好来自电影《黑豹》,它优雅地表达了在 Ansible 自动化中的一些重要智慧。

始终迭代并根据来自运营的真实世界反馈进行调整。优化可读性。继续寻找方法来简化和减少您组织及其流程中的摩擦。随着时间的推移,在您的环境和 IT 策略中引入更改将产生新的摩擦和痛点。它们还将创造新的机会,让您应用自动化实践来消除它们。

自动化是一段永无止境的旅程。

希腊哲学家赫拉克利特说:“变化是生活中唯一的常数。除了变化,没有什么是持久的。”

任何在 IT 行业工作过一段时间的人都知道,变化是无处不在的。这就是为什么敏捷性和准备快速可靠地应对持续变化、创新和业务需求至关重要。

自动化不是目的地。它是一种实践。它是一种文化、一种思维方式和一种态度。自动化是一个持续的反馈和学习过程,不断适应变化并改进之前所做的事情。

自动化创造机遇,我们在红帽看到了无处不在的自动化机遇。

因此,我向您提出的问题是:您的自动化之旅将引领您走向何方?

进一步阅读

如果您想更深入地了解 Ansible 禅宗及其起源的应用,我推荐这些资源。

Ansible 实践社区 (CoP) 已汇集了 一个关于 Ansible 内容开发的“最佳实践”的综合资源库Ansible Lint 工具 现已添加到 Red Hat Ansible 自动化平台,并将这些实践中的许多内容编纂为规则和配置文件,以帮助您快速识别并强制执行一致的应用到您的工作中。

如果您有兴趣更多地了解“Python 之禅”,我建议从 Al Sweigart 对这些格言的解释 开始。