Linux 秉承 Unix 的传统,没有全面的系统管理 API。相反,管理是通过各种专用工具和 API 完成的,所有这些工具和 API 都有自己的约定和特性。这使得编写简单的系统管理任务脚本也变得困难且脆弱。
例如,更改 “app” 用户的登录 shell 是通过运行 usermod -s /sbin/nologin app 完成的。这在没有 app 用户的系统上尝试之前都很好用。为了解决随之而来的失败,有进取心的脚本编写者现在可能会求助于
grep -q app /etc/passwd \
&& usermod -s /sbin/nologin app \
|| useradd ... -s /sbin/nologin app
以便在系统上存在 app 用户时执行登录 shell 的更改,并在用户尚不存在时创建用户。不幸的是,这种编写系统管理任务脚本的方法是不可持续的:对于每种资源,都必须考虑不同的工具集及其特性;不一致且常常不完整的错误报告使错误处理变得困难;并且很容易被所涉及工具的临时性质引起的小错误绊倒。
事实上,上面的例子是不正确的:grep 并没有查找 app 用户,它只是在 /etc/passwd 中查找包含字符串 app 的任何行,这在大多数情况下可能有效,但可能会失败——通常在最糟糕的时刻。
显然,使得从脚本执行简单任务变得困难的管理工具,充其量也只是构建大型管理系统的困难基础。认识到这一点,现有的配置管理系统,例如 Puppet、Chef 或 Ansible,已经竭尽全力围绕基本操作系统资源的管理构建了自己的内部 API。这些资源抽象是内部 API,并且与各自工具的需求紧密相关。这不仅导致了巨大的重复劳动,而且为新的和创新的管理工具的进入设置了强大的障碍。
此进入壁垒变得明显的一个领域是构建 VM 或容器镜像:在构建此类镜像的过程中,通常需要回答关于它们的简单问题或对它们进行简单的更改。但是,由于所有这些工具都需要特殊处理,因此这些问题和更改面临着尝试编写脚本的人员所面临的完全相同的问题。因此,镜像构建必须依赖于临时脚本或使用(和安装)相当大的配置管理系统。
Libral 通过提供跨系统资源的通用管理 API,并通过命令行工具 ralsh 使其可用,为管理工具和任务奠定了坚实的基础,ralsh 使用户能够以统一的方式查询和修改系统资源,并具有可预测的错误报告。在上面的示例中,检查 app 用户是否存在使用 ralsh -aq user app 完成;检查是否安装了软件包 foo 使用 ralsh -aq package foo 完成;并且,通常,检查类型为 TYPE 名称为 NAME 的资源是否存在使用 ralsh -aq TYPE NAME 完成。类似地,要创建或更改现有用户,请运行
ralsh user app home=/srv/app shell=/sbin/nologin
要创建或更改 /etc/hosts 中的条目,请运行
ralsh hostmyhost.example.com ip=10.0.0.1 \
host_aliases=myhost,apphost
通过这种方式,ralsh 的用户与以下事实隔离开来:这两个命令在内部的工作方式截然不同:第一个命令需要正确调用 useradd 或 usermod,而第二个命令需要编辑文件 /etc/hosts。但是,对于用户而言,它们看起来都具有相同的形状:“确保此资源处于我需要的状态。”
从哪里获取 Libral 以及如何使用它
Libral 可从 此 git 仓库 获取。它的核心是用 C++ 编写的,构建说明可以在 仓库中 找到。只有当您实际上想为 Libral 的 C++ 核心做出贡献时,这才必要。Libral 站点还包含一个 预构建的 tarball,可在任何使用 glibc 2.12 或更高版本的 Linux 机器上使用。该 tarball 的内容既可以用于进一步探索 ralsh,也可以用于开发新的提供程序,这些提供程序使 Libral 能够管理新型资源。
下载并解压缩 tarball 后,可以在 ral/bin 中找到 ralsh 命令。不带参数运行它将列出 Libral 知道的所有资源类型。传递 --help 选项会打印包含更多关于如何使用 ralsh 的示例的输出。
与配置管理系统的关系
众所周知的配置管理系统,例如 Puppet、Chef 或 Ansible,解决了与 Libral 解决的一些相同问题。Libral 与它们的不同之处主要在于这些系统所做的事情以及 Libral 不做的事情。配置管理系统的构建是为了处理跨大量节点管理许多不同事物的多样性和复杂性。另一方面,Libral 旨在提供一个定义明确、独立于任何特定工具且可与各种编程语言一起使用的低级系统管理 API。
通过删除大型配置管理系统包含的应用程序逻辑,Libral 在如何使用方面更加通用,从引言中提到的简单脚本任务,到充当复杂管理应用程序的构建块。专注于这些基础知识也使其非常小巧,目前小于 2.5 MB,这对于资源受限的环境(包括容器和小型设备)来说是一个重要的考虑因素。
Libral API
Libral API 的设计以过去十年实施大型配置管理系统的经验为指导;虽然它不直接与其中任何一个相关联,但它考虑了它们并做出了选择来克服它们的缺点。
API 设计基于四个重要原则
- 期望状态
- 双向性
- 轻量级抽象
- 易于扩展
基于期望状态的管理 API,即用户表达操作后系统应呈现的状态,而不是如何进入该状态的想法,在此时几乎没有争议。双向性使得可以使用相同的 API,更重要的是,可以使用相同的资源抽象来读取现有状态并强制更改它。轻量级抽象确保易于学习 API 并快速使用它;过去对此类管理 API 的尝试不适当地给用户带来了学习建模框架的负担,这是它们缺乏采用的重要因素。
最后,必须易于扩展 Libral 的管理功能,以便用户可以教 Libral 如何管理新型资源。这很重要,既是因为人们可能想要管理的资源数量庞大(Libral 将在适当的时候管理这些资源),也因为即使是完全构建的 Libral 也总是无法满足用户的自定义管理需求。
目前,与 Libral API 交互的主要方式是通过 ralsh 命令行工具。它公开了底层的 C++ API,该 API 仍在不断变化,并且主要面向简单的脚本任务。该项目还为 CRuby 提供了语言绑定,其他语言绑定也将陆续推出。
将来,Libral 还将提供一个带有远程 API 的守护进程,以便它可以充当不需要在受管节点上安装其他代理的管理系统的基础。这与定制 Libral 管理功能的能力相结合,使得可以严格控制系统的哪些方面可以管理,哪些方面受到保护免受任何干扰。
例如,一个仅限于管理用户和服务的 Libral 安装将保证不会干扰节点上安装的软件包。目前,任何现有配置管理系统都无法控制以这种方式管理的内容;特别是,需要对受管节点进行任意 SSH 访问的系统也会使该系统受到不必要的意外或恶意干扰。
Libral API 的基础由两个非常简单的操作组成:get 用于检索资源的当前状态,set 用于强制执行当前资源的状态。从实际实现中理想化一下,它们可以被认为是
provider.get(names) -> List[resource]
provider.set(List[update]) -> List[change]
provider 是知道如何管理某种资源(如用户、服务或软件包)的对象,Libral API 提供了查找某种资源的提供程序的方法。
get 操作接收资源名称列表,例如用户名,并且需要生成资源列表,这些资源本质上是列出每个资源属性的哈希。此列表必须包含具有提供的名称的资源,但可能包含更多,以便简单的 get 实现可以简单地忽略名称并列出它知道的所有资源。
set 操作用于强制执行期望状态并接收更新列表。每个更新都包含 update.is(表示当前状态的资源)和 update.should(表示期望状态的资源)。调用 set 方法将确保更新列表中提到的资源将处于 update.should 中指示的状态,并生成对每个资源所做的更改列表。
使用 ralsh,可以使用命令 ralsh user root 检索 root 用户的当前状态;默认情况下,该命令生成人类可读的输出,类似于 Puppet,但 ralsh 也支持 --json 标志,使其生成 JSON 输出以供
脚本使用。人类可读的输出是
# ralsh user root
user::useradd { 'root':
ensure => 'present',
comment => 'root',
gid => '0',
groups => ['root'],
home => '/root',
shell => '/bin/bash',
uid => '0',
}
类似地,可以使用以下命令更改用户:
# ralsh user root comment='The superuser'
user::useradd { 'root':
ensure => 'present',
comment => 'The superuser',
gid => '0',
groups => ['root'],
home => '/root',
shell => '/bin/bash',
uid => '0',
}
comment(root->The superuser)
ralsh 的输出既列出了 root 用户的新状态(带有已更改的 comment 属性),又列出了所做的更改(在本例中仅针对 comment 属性)。第二次运行相同的命令将产生大致相同的输出,但没有任何更改指示,因为不需要任何更改。
编写提供程序
至关重要的是,为 ralsh 编写新提供程序要容易且只需最少的工作量。因此,ralsh 提供了许多调用约定,可以用来权衡实现提供程序的复杂性与提供程序可以执行的功能的强大程度。提供程序可以是遵循特定调用约定的外部脚本,也可以用 C++ 实现并构建到 Libral 中。目前,有三种调用约定
- simple 调用约定适用于编写充当提供程序的 shell 脚本
- JSON 调用约定旨在用 Ruby 或 Python 等脚本语言编写提供程序
- 内部 C++ API 可用于以原生方式实现提供程序
强烈建议使用 simple 或 JSON 调用约定开始提供程序开发。GitHub 上的文件 simple.prov 包含简单 shell 提供程序的框架,应该很容易将其调整为自己的提供程序。文件 python.prov 包含用 Python 编写的 JSON 提供程序的框架。
对提供程序使用更高级别的脚本语言的一个问题是,这些语言的运行时环境(包括所有支持库)需要存在于 Libral 将在其上运行的系统上。在某些情况下,这不是障碍;例如,基于 yum 进行软件包管理的提供程序可以期望系统上存在 Python,因为 yum 是用它编写的。
但在许多其他情况下,除了 Bourne shell(或 Bash)之外,没有其他语言可以被期望安装在所有受管系统上。通常,提供程序编写者需要比这更强大的脚本环境。不幸的是,将完整的 Ruby 或 Python 解释器与其运行时环境捆绑在一起会使 Libral 的大小增加到超出合理范围的
在资源受限的环境中使用。另一方面,Lua 或 Javascript 作为小型可嵌入脚本语言的典型选择不适用于此上下文,因为它们既不为大多数提供程序编写者所熟悉,也需要大量工作来公开系统管理常用的工具。
Libral 捆绑了 mruby 的一个版本,mruby 是 Ruby 的一个小型、可嵌入版本,为提供程序编写者提供了一个稳定的基础和一个强大的编程语言用于他们的实现。mruby 是 Ruby 语言的完整实现,尽管标准库大大减少。与 Libral 捆绑的 mruby 包含 Ruby 标准库中对于编写管理任务脚本最重要的部分,这将根据提供程序编写者的需求在未来进一步增强。Libral 的 mruby 还捆绑了一个 API 适配器,该适配器使编写符合 json 约定的提供程序更加舒适,因为它包含简单的实用程序(例如 Augeas 用于修改结构化文件)和围绕解析和输出 JSON 的便利功能。文件 mruby.prov 包含用 mruby 编写的 json 提供程序的框架示例。
未来工作
Libral 最重要的下一步是使其更广泛地可用——预编译的 tarball 是入门的好方法,并且足以开发提供程序,但 Libral 也需要打包并在主流发行版中提供。同样,Libral 的实用性在很大程度上取决于它附带的提供程序集,并且需要扩展这些提供程序以涵盖核心管理功能。Libral 站点包含 待办事项列表,其中显示了最迫切需要的提供程序。
还可以通过多种方式改进 Libral 在不同用途中的可用性:从为其他语言(例如 Python 或 Go)编写绑定,到通过提供除了现有的人类可读输出和 JSON 输出之外的输出格式,使在 shell 脚本中使用 ralsh 更加容易,该输出格式易于在 shell 脚本中处理。通过添加上面讨论的远程 API,并通过更好地支持通过 SSH 等传输方式批量安装 Libral,也可以改进 Libral 在更大规模管理中的使用——这主要需要为更多架构提供预构建的 tarball 和脚本,这些脚本可以根据目标系统的已发现架构选择正确的 tarball。
Libral、其 API 和其功能可能以更多方式发展;一种有趣的可能性是向 API 添加通知功能,以便 Libral 可以报告系统资源在其范围之外发生更改的情况。Libral 面临的挑战将是在覆盖不断增长的用途和管理功能集的同时,继续保持小巧、轻量级和定义明确的工具——这是一个挑战和一段旅程,我鼓励每位读者都参与其中。
如果以上任何内容引起了您的好奇心,我很乐意收到您的来信,无论是通过 pull request、增强请求,还是只是您试用 ralsh 的体验报告。
评论已关闭。