FreeBSD 中文社区 2025 第二季度问卷调查
FreeBSD 中文社区(CFC)
VitePress 镜像站QQ 群 787969044视频教程Ⅰ视频教程Ⅱ
  • FreeBSD 从入门到追忆
  • 中文期刊
  • 状态报告
  • 发行说明
  • 手册
  • 网络文章集锦
  • 笔记本支持报告
  • Port 开发者手册
  • 架构手册
  • 开发者手册
  • 中文 man 手册
  • 文章
  • 书籍
  • FreeBSD 中文期刊
  • 编辑日志
  • 2025-123 下游项目
    • FreeBSD 发布工程:新主管上任
    • GhostBSD:从易用到挣扎与重生
    • BSD Now 与将来
    • 字符设备驱动教程(第三部分)
    • 学会走路——连接 GPIO 系统
    • FreeBSD 中对 SYN 段的处理
    • FreeBSD 2024 年秋季峰会
  • 2024-1112 虚拟化
    • 字符设备驱动程序教程(第二部分)
    • 面向 Linux 和 Windows 用户的 bhyve
    • Xen 与 FreeBSD
    • Wifibox:一种嵌入式虚拟化无线路由器
    • 嵌入式 FreeBSD:Fabric——起步阶段
    • DGP:一种新的数据包控制方法
    • 会议报告:我在都柏林的 EuroBSDCon 体验
  • 2024-0910 内核开发
    • 字符设备驱动程序教程
    • VPP 移植到了 FreeBSD:基础用法
    • 利用 Kyua 的 Jail 功能提升 FreeBSD 测试套件的并行效率
    • FreeBSD 上的 Valgrind
    • 嵌入式 FreeBSD:探索 bhyve
    • TCP/IP 历险记:FreeBSD TCP 协议栈中的 Pacing
    • 实用软件:实现无纸化(Paperless)
  • 2024-0708 存储与文件系统
    • FreeBSD 中的 NVMe-oF
    • FreeBSD iSCSI 入门
    • 使用 ZFS 原生加密保护数据
    • 嵌入式 FreeBSD:打造自己的镜像
    • TCP LRO 简介
    • 基于 Samba 的时间机器备份
  • 2024-0506 配置管理对决
    • 基本系统中的 mfsBSD
    • rdist
    • Hashicorp Vault
    • 在 GitHub 上向 FreeBSD 提交 PR
    • 悼念 Mike Karels
    • 2024 年 5-6 月来信
    • 嵌入式 FreeBSD 面包板
    • TCP/IP 历险记:TCP BBLog
    • 实用软件:开发定制 Ansible 模块
  • 2024-0304 开发工作流与集成
    • FreeBSD 内核开发工作流程
    • FreeBSD 与 KDE 持续集成(CI)
    • 更现代的内核调试工具
    • 从零开始的 ZFS 镜像及 makefs -t zfs
    • 提升 Git 使用体验
  • 2024-0102 网络(十周年)
    • FreeBSD 中的 RACK 栈和替代 TCP 栈
    • FreeBSD 14 中有关 TCP 的更新
    • if_ovpn 还是 OpenVPN
    • SR-IOV 已成为 FreeBSD 的重要功能
    • FreeBSD 接口 API(IfAPI)
    • BATMAN:更优的可移动热点网络方式
    • 配置自己的 VPN——基于 FreeBSD、Wireguard、IPv6 和广告拦截
    • 实用软件:使用 Zabbix 监控主机
  • 2023-1112 FreeBSD 14.0
    • LinuxBoot:从 Linux 启动 FreeBSD
    • FreeBSD 容器镜像
    • 现在用 Webhook 触发我
    • 新的 Ports 提交者:oel Bodenmann (jbo@freebsd.org)
  • 2023-0910 Port 与软件包
    • 回忆录:与 Warner Losh(@imp)的访谈
    • 在你自己的仓库中定制 Poudriere 源
    • Wazuh 和 MITRE Caldera 在 FreeBSD Jail 中的使用
    • PEP 517
    • CCCamp 2023 旅行报告
  • 2023-0708 容器与云
    • 在 Firecracker 上的 FreeBSD
    • 使用 pot 和 nomad 管理 Jail
    • 会议报告:C 与 BSD 正如拉丁语与我们——一位神学家的旅程
    • 抒怀之旅:与 Doug Rabson 的访谈
    • 基于 Jail 的广告拦截教程
    • 我们收到的来信
  • 2023-0506 FreeBSD 三十周年纪念特刊
    • CheriBSD 近十多年的历程
    • AArch64:成为 FreeBSD 新的一级架构
    • 岁月如梭:我个人的时间线
    • 安装 FreeBSD 1.0:回顾 30 年前
    • ZFS 是如何进入 FreeBSD 的呢?
    • 我不是来自约克郡的,我保证!
    • 回忆录:采访 David Greenman Lawrence
    • FreeBSD 和早期的 Unix 社区
    • 早期的 FreeBSD 移植
    • FreeBSD 30 周年:成功的秘诀
    • FreeBSD 在日本:回忆之旅与今日之实
  • 2023-0304 嵌入式
    • CheriBSD port 和软件包
    • 让我们来试试 ChatGPT
    • GPU 直通
  • 2023-0102 构建 FreEBSD Web 服务器
    • ZFS 的原子 I/O 与 PostgreSQL
    • 虚拟实验室——BSD 编程研讨会
    • ZFS 简介
    • 会议报告:落基山庆祝女性计算机科学家
    • 进行中的工作/征求反馈:数据包批处理
    • 基金会与 FreeBSD 桌面
  • 2022-1112 可观测性和衡量标准
    • 在 FreeBSD 的 DDB 内核调试器中编写自定义命令
    • DTrace:老式跟踪系统的新扩展
    • 基于证书的 Icinga 监控
    • 活动监控脚本(activitymonitor.sh)
    • 实用 IPv6(第四部分)
    • EuroBSDCon 会议报道
    • 实用 Port:Prometheus 的安装与配置
    • 书评:《用火解决问题:管理老化的计算机系统(并为现代系统保驾护航)》Kill It with Fire: Manage Aging Computer Systems (and Future Proof Modern Ones)
  • 2022-0910 安全性
    • CARP 简介
    • 重构内核加密服务框架
    • PAM 小窍门
    • SSH 小窍门
    • 实用 IPv6(第三部分)
    • 书评:Understanding Software Dynamics(深入理解软件性能——一种动态视角)—— Richard L. Sites 著
    • 访谈:保障 FreeBSD 安全性
    • MCH 2022 会议报告
  • 2022-0708 科研、系统与 FreeBSD
    • 在 FreeBSD 上构建 Loom 框架
    • 教授本科生 Unix 课程
    • FreeBSD 入门研讨会
    • 实用 IPv6(第二部分)
    • 在 2022 年及以后推广 FreeBSD
    • 进行中的工作/征求反馈:Socket 缓冲区
    • FreeBSD 开发者峰会报告
    • 支持 Electromagnetic Field 2022
  • 2022-0506 灾难恢复
    • 使用 FreeBSD 构建高弹性的私有云
    • LLDB 14 —— FreeBSD 新调试器
    • 实用 IPv6(第一部分)
    • 利用 netdump(4) 进行事后内核调试
    • 进行中的工作/征求反馈:FreeBSD 启动性能
    • 实用 Port:在 OpenZFS 上设置 NFSv4 文件服务器
  • 2022-0304 ARM64 是一级架构
    • FreeBSD/ARM64 上的数据科学
    • Pinebook Pro 上的 FreeBSD
    • 嵌入式控制器的 ACPI 支持
    • 进行中的工作/征求反馈:Lumina 桌面征集开发人员
    • 实用 Port:如何设置 Apple 时间机器
  • 2022-0102 软件与系统管理
    • 为 FreeBSD Ports 做贡献
    • 使用 Git 贡献到 FreeBSD Ports
    • CBSD:第一部分——生产环境
    • 将 OpenBSD 的 pf syncookie 代码移植到 FreeBSD 的 pf
    • 进行中的工作/征求反馈:mkjail
    • 《编程智慧:编程鬼才的经验和思考》(The Kollected Kode Vicious)书评
    • 会议报告:EuroBSDCon 2021 我的第一次 EuroBSDCon:一位新组织者的视角
  • 2021-1112 存储
    • 开放通道 SSD
    • 构建 FreeBSD 社区
    • 与完美操作系统同行 27 年
    • 进行中的工作/征求反馈:OccamBSD
    • 通过 iSCSI 导入 ZFS ZIL——不要在工作中这样做——就像我做的那样
  • 2021-0910 FreeBSD 开发
    • FreeBSD 代码审查与 git-arc
    • 如何为 FreeBSD 实现简单的 USB 驱动程序
    • 内核开发技巧
    • 程序员编程杂谈
  • 2021-0708 桌面/无线网
    • 通往 FreeBSD 桌面的直线路径
    • FreeBSD 13 中的人机接口设备 (HID) 支持
    • Panfrost 驱动程序
    • 用 Git 更新 FreeBSD
    • FreeBSD 的新面孔
    • 想给你的桌面加点佐料?
  • 2021-0506 安全
    • 七种提升新安装 FreeBSD 安全性的方法
    • copyinout 框架
    • 使用 TLS 改善 NFS 安全性
    • Capsicum 案例研究:Got
    • 对 Jail 进行安全扫描
  • 2021-0304 FreeBSD 13.0
    • 展望未来
    • FreeBSD 13.0 工具链
    • FreeBSD 13.0 中有新加载器吗?
    • TCP Cubic 准备起飞
    • OpenZFS 中的 Zstandard 压缩
    • 会议报告:FreeBSD 供应商峰会
    • Git 不够吗?
  • 2021-0102 案例研究
    • Tarsnap 的 FreeBSD 集群
    • BALLY WULFF
    • Netflix Open Connect
    • FreeBSD 的新面孔
    • 写作学者的 FreeBSD
    • 在世界之巅
  • 2020-1112 工作流/持续集成(CI)
    • FreeBSD Git 快速入门
    • 使用 syzkaller 进行内核 Fuzzing
    • Mastering Vim Quickly 书评
    • 线上会议实用技巧
    • 在控制台上进行网络监控
  • 2020-0910 贡献与入门
    • 采访:Warner Losh,第 2 部分
    • 代码审查
    • 撰写良好的提交消息
    • 如何在不是程序员的情况下做出贡献——成为 FreeBSD 译者
    • 如何成为文档提交者
    • 谷歌编程之夏
    • 为 FreeBSD 期刊撰写文章
    • 你为什么使用 FreeBSD
    • FreeBSD 的新面孔
  • 2020-0708 基准测试/调优
    • FreeBSD Friday
    • 采访:Warner Losh,第 1 部分
    • 构建和运行开源社区
    • 在 FreeBSD 上轻松搭建我的世界(Minecraft)服务器
    • FreeBSD 的新面孔
  • 2020-0506 网络性能
    • 内核中的 TLS 卸载
    • 访谈:Michael W Lucas
    • FreeBSD 桌面发行版
    • 使用 Poudriere 进行 Port 批量管理
    • FreeBSD 的新面孔
由 GitBook 提供支持
LogoLogo

FreeBSD 中文社区(CFC) 2025

在本页
  • 使用 Shell 编程的示例模块
  • 运行示例模块
  • 使用 Python 编写自定义模块
  • Ansiballz 框架
  • 创建 Python 模块
  • 从 Playbook 执行 Python 模块
  • 定义模块参数
  • 模块的 Playbook
  • 运行 calc 模块
  • 结论
  • 参考文献
在GitHub上编辑
导出为 PDF
  1. 2024-0506 配置管理对决

实用软件:开发定制 Ansible 模块

上一页TCP/IP 历险记:TCP BBLog下一页FreeBSD 内核开发工作流程

最后更新于24天前

  • 原文链接:

  • 作者:Benedict Reuschling

Ansible 提供了许多不同的模块,普通用户通常可以直接使用这些模块,而无需编写自己的模块,因为现有模块的数量庞大。即使在模块 ansible.builtin 中未提供所需的功能,Ansible Galaxy 也有大量来自爱好者的第三方模块,这些模块进一步丰富了模块的数量。

当所需功能未被单一模块及其组合覆盖时,就需要开发自己的模块。开发者可以选择将自定义模块保留为本地模块,而无需将其发布到互联网或通过 Ansible Galaxy 使用。模块通常用 Python 开发,但若不打算把该模块提交到官方 Ansible 生态系统中,使用其他编程语言也是可以的。

要测试自定义模块,可以安装包 ansible-core,它通过提供 Ansible 内部使用的通用代码来提供帮助。然后,你可以将自定义模块与大部分现有模块使用的核心 Ansible 功能结合,从而确保其可靠性和稳定性。

使用 Shell 编程的示例模块

我们从一个简单的示例开始,帮助理解基本概念。稍后,我们将丰富它,使用 Python 实现更多功能。

自定义模块的描述:我们的自定义模块名为 touch,它会检查 /tmp 目录下是否有名为 BSD.txt 的文件。若文件存在,模块返回 true(状态未更改)。若文件不存在,模块会创建该空文件,并返回 state: changed。

自定义模块通常存放在与使用该模块的 playbook 同一目录下的 library 文件夹中。可以使用 mkdir 命令创建该目录:

mkdir library

在 library 目录中创建一个包含模块代码的 Shell 脚本:

touch library/touch

在 library/touch 文件中输入以下代码,作为模块逻辑:

1  FILENAME=/tmp/BSD.txt
2  changed=false
3  msg=''
4  if [ ! -f ${FILENAME} ]; then
5      touch ${FILENAME}
6      msg="${FILENAME} created"
7      changed=true
8  fi
9  printf '{"changed": "%s", "msg": "%s"}' "$changed" "$msg"

首先,我们定义一些变量,同时设置默认值。第 4 行检查文件是否不存在。若文件不存在,模块就创建该文件,同时更新变量 msg。我们需要通知 Ansible 状态已更改,因此在最后返回变量 changed,并附带更新后的信息。

接着,在与 library 目录相同的位置创建一个名为 touch.yml 的 playbook,内容如下:

---- 
hosts: localhost
  gather_facts: false
  tasks:
    - name: Run our custom touch module
      touch:
        register: result

    - debug: var=result

注意:我们可以在任何远程节点上执行自定义模块,而不仅是 localhost。在开发过程中,先在 localhost 上测试会更容易。

像以前编写的其他 playbook 一样运行这个 playbook:

ansible-playbook touch.yml

运行示例模块

当文件 /tmp/BSD.txt 不存在时,playbook 输出如下:

PLAY [localhost] *****************************************

TASK [Run our custom touch module] ***********************
changed: [localhost]

TASK [debug] *********************************************
ok: [localhost] => {
    “changed”: true,
    “result”: {
        “failed”: false,
        “msg”: “/tmp/BSD.txt created”
    }
}

当文件 /tmp/BSD.txt 存在(来自之前的运行)时,输出如下:

PLAY [localhost] *****************************************

TASK [Run our custom touch module] ***********************
ok: [localhost]

TASK [debug] *********************************************
ok: [localhost] => {
“result”: {
        “changed”: false,
        “failed”: false,
        “msg”: “”
    }
}

使用 Python 编写自定义模块

编写 Python 模块有哪些好处呢?像模块 ansible.builtin 一样,使用 Python 编写模块的一个好处是,我们能使用现有的解析库来处理模块参数,而不必重造一个。用 Shell 编写模块时,定义每个参数的名称非常困难,而在 Python 中,我们可以教会模块接受一些参数作为可选参数,其他的作为必需项。数据类型定义了模块用户为每个参数提供的输入类型。例如,参数 dest 应该是路径类型,而非整数类型。Ansible 提供了一些便捷的功能,可以让我们在脚本中使用,从而使我们能够专注于模块的核心功能。

Ansiballz 框架

现代 Ansible 模块使用 Ansiballz 框架。与 2.1 版本之前使用的模块热替换不同,它使用来自 ansible/module_utils 的真实 Python 导入,而不是预处理模块。

模块功能:Ansiballz 构建了一个压缩文件,内容包括:

  • 模块文件

  • 模块导入的 ansible/module_utils 文件

  • 模块参数的模板代码

压缩文件经过 Base64 编码,并被封装成一个小的 Python 脚本用于解码。接着,Ansible 会将其复制到目标节点的临时目录。当执行时,Ansible 模块脚本会解压文件并将其自身放置到临时目录中。然后它会设置 PYTHONPATH 来查找压缩文件中的 Python 模块,并以特殊名称导入 Ansible 模块。Python 会认为它正在执行一个常规的脚本,而不是在导入模块。这能让 Ansible 在目标主机上通过同一个 Python 实例运行包装脚本和模块代码。

创建 Python 模块

要创建模块,可以使用 venv 和 virtualenv 来进行开发。我们像之前一样,从创建目录 library 开始,在其中创建一个新的 hello.py 模块,内容如下:

#!/usr/bin/env python3
from ansible.module_utils.basic import *
def main():
    module = AnsibleModule(argument_spec={})
    response = {"hello": "world!"}
    module.exit_json(changed=False, meta=response)

if __name__ == "__main__":
    main()

import 导入 Ansiballz 框架来构建模块。它包括参数解析、文件操作和将返回值格式化为 JSON 等代码构造。

从 Playbook 执行 Python 模块

创建一个名为 hello.yml 的 playbook,内容如下:

---
- hosts: localhost
  gather_facts: false
  tasks:
    - name: Testing the Python module
      hello:
      register: result

    - debug: var=result

再次像往常一样运行这个 playbook:

ansible-playbook hello.yml

输出如下:

PLAY [localhost] *****************************************

TASK [Testing the Python module] *************************
ok: [localhost]

TASK [debug] *********************************************
ok: [localhost] => {
“result”: {
        “changed”: false,
        “failed”: false,
        “meta”: {
            “hello”: “world!”
        }
    }
}

定义模块参数

我们使用的模块有一些参数,如 path:、src: 和 dest:,用于控制模块的行为。这些参数中的一些对于模块的正常运行至关重要,而其他一些则是可选的。在我们自己的模块中,我们希望控制哪些参数是必须的,哪些是可选的。定义数据类型可以让我们的模块在面对错误输入时更加健壮。

AnsibleModule 提供的 argument_spec 定义了支持的模块参数,以及它们的类型、默认值等。

示例参数定义:

parameters = {
    'name': {“required”: True, “type”: 'str'},
'age': {“required”: False, “type”: 'int', “default”: 0},
    'homedir': {“required”: False, “type”: 'path'}
}

必需的参数 name 是字符串类型。age(整数类型)和 homedir(路径类型)是可选的,若未定义,age 默认为 0。一个新的模块使用这些参数定义,计算通过传两个数字和一个可选的数学运算符得到的结果。若未提供运算符,默认假定为加法。创建一个新的 Python 文件 calc.py,放在目录 library 下:

#!/usr/bin/env python3
from ansible.module_utils.basic import AnsibleModule

def main():
    parameters = {
       “number1”: {“required”: True, “type”: “int”},
“number2”: {“required”: True, “type”: “int”},
        “math_op”: {“required”: False, “type”: “str”, “default”: “+”},
    }

    module = AnsibleModule(argument_spec=parameters)

    number1 = module.params[“number1”]
    number2 = module.params[“number2”]
    math_op = module.params[“math_op”]

    if math_op == “+”:
        result = number1 + number2

    output = {
        “result”: result,
    }

    module.exit_json(changed=False, **output)

if __name__ == “__main__”:
    main()

模块的 Playbook

---- 
hosts: localhost  
gather_facts: false  
tasks:
  - name: Testing the calc module      
    calc:
      number1: 4
      number2: 3
    register: result  
  - debug: var=result

calc 模块可选地接受一个参数 math_op,但由于我们为其定义了默认操作(+),用户可以在 playbook 和命令行中省略它。运行该模块的任务必须指定必需的参数,否则 playbook 将执行失败。

运行 calc 模块

playbook 执行的相关输出如下:

ok: [localhost] => {
    “result”: {
        “changed”: false,
        “failed”: false,
“result”: 7
    }
}

我们扩展了示例来正确处理 +、-、*、/。当模块接收到一个不同于已定义的 math_op 时,它返回 false。此外,通过返回“Invalid Operation”来处理除以零的情况,这一直是学生作业中的经典题目。从前我并没有好好学习 Python,但直到现在,我的解决方案看起来是这样的:

#!/usr/bin/env python3
from ansible.module_utils.basic import AnsibleModule

def main():
    parameters = {
        “number1”: {“required”: True, “type”: “int”},
“number2”: {“required”: True, “type”: “int”},
        “operation”: {“required”: False, “type”: “str”, “default”: “+”},
}

    module = AnsibleModule(argument_spec=parameters)

number1 = module.params[“number1”]
    number2 = module.params[“number2”]
    operation = module.params[“operation”]
    result = “”

    if operation == “+”:
        result = number1 + number2
    elif operation == “-”:
        result = number1 - number2
    elif operation == “*”:
        result = number1 * number2
    elif operation == “/”:
        if number2 == 0:
            module.fail_json(msg=”Invalid Operation”)
        else:
            result = number1 / number2
    else:
        result = False

    output = {
        “result”: result,
    }

    module.exit_json(changed=False, **output)

if __name__ == “__main__”:
main()

测试扩展后的模块非常简单。以下是测试除以零的情况:

---
- hosts: localhost
  gather_facts: false
  tasks:
    - name: Testing the calc module
      calc:
        number1: 4
        number2: 0
        map_op: ‘/’
      register: result

    - debug: var=result

这将得到如下预期的输出:

TASK [Testing the calc module] **********************************************
fatal: [localhost]: FAILED! => {“changed”: false, “msg”: “Invalid Operation”}

结论

掌握了这些基础,开始编写自定义模块就变得容易了。请记住,这些模块会在不同的操作系统上运行。请添加额外的检查来确定某些命令的可用性,或者直接让模块在某些环境下拒绝运行。尽可能提高兼容性,以增加模块的兼容性和实用性。目前能用的 BSD 特定模块并不多。为什么不尝试添加一个 bhyve 模块,或者一个管理启动环境、pf 防火墙或 rc.conf 条目的模块呢?对于有 Ansible 和 Python 背景的勇敢开发者来说,机会仍然很多。

参考文献


BENEDICT REUSCHLING 是 FreeBSD 项目的文档贡献者,也是文档工程团队的成员。他曾担任两届 FreeBSD 核心团队成员。他在德国达姆施塔特应用技术大学管理一个大数据集群,并且教授“Unix 开发者”课程。Benedict 也是每周播出的 播客的主持人之一。

Practical Ports: Developing Custom Ansible Modules
Ansible 模块架构
bsdnow.tv