news 2026/9/27 21:35:10

Puppet Catalog 深度解析:Resource Catalog 与 RAL Catalog 两种形态及其在测试与 Settings 中的应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Puppet Catalog 深度解析:Resource Catalog 与 RAL Catalog 两种形态及其在测试与 Settings 中的应用
  • 运维
  • DevOps
  • IaC

【免费下载链接】puppet

Server automation framework and application

项目地址:https://gitcode.com/gh_mirrors/pu/puppet
点击查看免费下载

导读:Puppet 的 Catalog(目录)是贯穿编译、传输、应用到最终系统配置的核心数据模型,但很多开发者只把它当作"一张资源清单"。实际上,在 Puppet 内部存在两种本质不同的 Catalog 形态:用于网络传输的Resource Catalog与用于实际应用配置的RAL Catalog,此外 Puppet 还会在启动时构造一个管理自身配置文件(settings)的Settings Catalog。本文以仓库文档 docs/catalogs.md 为主线,结合 lib/puppet/resource/catalog.rb、lib/puppet/settings.rb 等源码实现,讲清这三种 Catalog 的区别、转换路径、依赖图获取方法,以及写 spec 测试时如何快速伪造一个可用的 Catalog。

为什么需要区分两种 Catalog

当你在 Puppet 中开发与 Catalog 打交道的子系统(例如编写新的 catalog terminus、provider、或对 Catalog 做过滤的 indirector terminus)时,首先必须意识到:Catalog 不是一个单一概念,而是两种不同形态的对象。

从整体数据流看,二者的分工非常清晰:

  • Resource Catalog是"内存中可序列化并跨网络传输"的对象。服务端编译器(compiler terminus)负责产出 Resource Catalog,它由Puppet::Resource实例组成,描述"应该管理哪些资源、资源之间有什么关系"。
  • RAL Catalog是 agent 端把 Resource Catalog 转换后得到的对象,包含的是Puppet::Type实例(即资源抽象层 RAL 中的类型实例),真正负责把配置模型应用到系统上的是它。

对应到源码,lib/puppet/resource/catalog.rb#L16 定义了核心类:

class Puppet::Resource::Catalog < Puppet::Graph::SimpleGraph

该类通过Puppet::Indirector提供间接路由(lib/puppet/resource/catalog.rb#L21-L22):

extend Puppet::Indirector indirects :catalog, :terminus_setting => :catalog_terminus

也就是说,Catalog 本身就是一个 indirector 对象,服务端通过catalog_terminus设置决定由哪个 terminus(默认是 compiler)来生产它。编译端点在 lib/puppet/indirector/catalog/compiler.rb#L52 的find方法中完成整个编译流程并返回Puppet::Resource::Catalog。

Resource Catalog:服务端的"传输形态"

特征:由 Puppet::Resource 组成,可序列化

Resource Catalog 是"server side"的处理对象。如果你在写的是处理 Catalog 的服务端逻辑——例如一个新的 catalog terminus——那么你面对的就是 Resource Catalog。

它的资源都是Puppet::Resource实例。序列化层面,lib/puppet/resource/catalog.rb#L469-L491 的to_data_hash把 tags、name、version、code_id、catalog_uuid、catalog_format、environment、resources、edges、classes 全部导出为哈希;反向的from_data_hash(lib/puppet/resource/catalog.rb#L411-L467)则负责从网络载荷重建 Catalog。这正是文档所说"serialize and transfer around the network"的代码级印证——Catalog 携带catalog_uuid、catalog_format等元数据,用于版本对齐与缓存判断。

在 spec 测试中伪造一个 Resource Catalog

写单元测试时,经常需要"接一个假的 Catalog"来驱动 type、provider 或对 Catalog 做过滤的 terminus。文档给出了标准的构造范式:

let(:catalog) do catalog = Puppet::Resource::Catalog.new("node-name-val") # NOT certname! rsrc = Puppet::Resource.new("file", "sshd_config", :parameters => { :ensure => 'file', :source => 'puppet:///modules/filetest/sshd_config', } ) rsrc.file = 'site.pp' rsrc.line = 21 catalog.add_resource(rsrc) end

注意两点关键细节:

  1. 构造参数是节点名(node name),不是 certname。Puppet::Resource::Catalog.new(name, environment, code_id)的第一个参数只用于标识这个 Catalog 服务于哪个节点(见 lib/puppet/resource/catalog.rb#L316-L339 的初始化逻辑,同时会生成随机的catalog_uuid并将catalog_format初始化为 2)。
  2. 设置rsrc.file与rsrc.line。这会把资源声明位置(site.pp 第 21 行)记录到资源上。一旦后续发生"重复声明"(duplicate declaration)等错误,lib/puppet/resource/catalog.rb#L574-L587 的fail_on_duplicate_type_and_title会利用这些信息拼出带文件位置的错误消息,方便定位问题。

add_resource的实现(lib/puppet/resource/catalog.rb#L126-L163)内部依次完成三件事:把资源写入资源表(@resource_table)与有序列表(@resources)、创建资源别名(显式alias以及同构资源的 uniqueness key 别名,见 lib/puppet/resource/catalog.rb#L165-L180)、并把资源作为顶点加入图中(add_vertex(resource))。所以从构造那一刻起,Catalog 既是一张"资源表",也是一张"图"。

访问资源与依赖图的局限

构造完成后,可以通过catalog.resources访问所有资源(lib/puppet/resource/catalog.rb#L405-L409),也可以按引用字符串(如File[/etc/passwd])用catalog.resource(...)精确查找(lib/puppet/resource/catalog.rb#L378-L395)。

但文档明确指出:Resource Catalog 不方便直接遍历依赖树。原因在于,依赖边(edges)在 Resource Catalog 阶段还没有被"展开"成完整的、可直接按序执行的图结构;真正的依赖遍历需要先把 Catalog 转换为 RAL Catalog。

RAL Catalog:应用侧的"执行形态"

转换入口与转换机制

Resource Catalog 通过catalog.to_ral转换为 RAL Catalog(lib/puppet/resource/catalog.rb#L494-L496):

def to_ral to_catalog :to_ral end

私有的to_catalog方法(lib/puppet/resource/catalog.rb#L592-L650)是两种形态互转的通用实现:

  • 逐个遍历资源,跳过"虚拟且未导出"的资源(virtual_not_exported?,见 lib/puppet/resource/catalog.rb#L652-L654);
  • 每个资源先copy_as_resource,当目标不是:to_resource时再调用to_ral,从而把Puppet::Resource实例转换为Puppet::Type实例;
  • 通过map[resource.ref]记录转换前后的对应关系,然后把原 Catalog 中的每条边(edge)映射到新 Catalog 中对应资源之间,依赖关系得以保留;
  • 最后复制 classes 与 tags。

因此 RAL Catalog 里装的都是Puppet::Type实例,这是它与 Resource Catalog 最本质的区别。仓库的单元测试也明确验证了这一点:spec/unit/resource/catalog_spec.rb#L185-L192 断言to_ral之后catalog.resource(resource.ref)的结果是Puppet::Type的实例;而 spec/unit/resource/catalog_spec.rb#L194-L204 验证了遇到未知资源类型时to_ral会抛出Puppet::Error("Resource type 'Unknown' was not found")。

顺带一提:同一个to_catalog机制也被to_resource(lib/puppet/resource/catalog.rb#L499-L501)和filter(lib/puppet/resource/catalog.rb#L506-L519)复用。filter用于剔除虚拟/导出资源(如编译器 terminus 中的filter调用,见 lib/puppet/indirector/catalog/compiler.rb#L92-L96),并在environment_instance存在时用Puppet.override切换到对应环境上下文(对应 PUP-3755)。

用 relationship_graph 遍历依赖

文档给出了一组非常实用的 IRB 操作序列,用于直观观察资源依赖关系:

irb> catalog = catalog.to_ral irb> graph = catalog.relationship_graph irb> pp graph.edges [{ Notify[alpha] => File[/tmp/file_20.txt] }, { Notify[alpha] => File[/tmp/file_21.txt] }, ... { File[/tmp/file_29.txt] => Notify[omega] }]

relationship_graph的实现在 lib/puppet/resource/catalog.rb#L264-L270:首次调用时懒加载创建Puppet::Graph::RelationshipGraph,并以Puppet::Graph::SequentialPrioritizer(或由ordering设置决定的 prioritizer)作为排序策略,然后调用populate_from(self)填充图。

Puppet::Graph::RelationshipGraph(lib/puppet/graph/relationship_graph.rb#L3-L9)的类注释说得很直白:它是 Catalog 的最终形态,所有依赖边都被显式地放进图里,用于按管理顺序遍历资源。其populate_from(lib/puppet/graph/relationship_graph.rb#L24-L34)的构建流程是:

  1. add_all_resources_as_vertices—— 所有资源作为顶点;
  2. build_manual_dependencies—— 显式依赖(require、before、subscribe、notify);
  3. build_autorelation_dependencies—— 自动关系(autorequire 等);
  4. replace_containers_with_anchors—— 用锚点替换容器,展开为可执行的扁平依赖序列。

这也解释了文档中的一句排查经验:如果relationship_graph抛异常,大概率你拿到的还不是 RAL Catalog。因为只有 RAL Catalog(或已经完成 populate 的图)才具备完整的依赖边。测试代码同样沿用了这一模式,例如 spec/integration/transaction_spec.rb#L507 用catalog.relationship_graph.direct_dependents_of(purge_dir)获取某资源的直接依赖者,以验证失败传播行为;spec/lib/puppet_spec/compiler.rb#L17-L33 提供的compile_to_ral与compile_to_relationship_graph辅助方法正是"manifest → Resource Catalog → RAL Catalog → 关系图"这条链路的测试封装。

此外,apply(lib/puppet/resource/catalog.rb#L233-L254)也是基于 RAL Catalog 工作的:它创建Puppet::Transaction执行事务、记录transaction_evaluation耗时,并且只在host_config?为真时才读写状态数据库(Storage)。非 host catalog(如 Settings Catalog)则"从不发报告、从不改状态库"——这一点在 spec/unit/resource/catalog_spec.rb#L724-L740 中有对应断言。

Settings Catalog:Puppet 管理自身的迷你 Catalog

它是什么

除了上述两种 Catalog,Puppet 还会在初始化时创建第三个迷你 Catalog:Settings Catalog。它会在本地应用,用来管理来自 settings(即 puppet.conf 配置)的那些文件资源——例如各种 pid 文件、目录、配置文件本身。

它的生成入口在 lib/puppet/settings.rb#L1064-L1086 的to_catalog(*sections):

def to_catalog(*sections) sections = nil if sections.empty? catalog = Puppet::Resource::Catalog.new("Settings", Puppet::Node::Environment::NONE) @config.keys.find_all { |key| @config[key].is_a?(FileSetting) }.each do |key| file = @config[key] next if file.value.nil? next unless sections.nil? or sections.include?(file.section) resource = file.to_resource next unless resource next if catalog.resource(resource.ref) catalog.add_resource(resource) end add_user_resources(catalog, sections) add_environment_resources(catalog, sections) catalog end

可以看到:

  • 只有FileSetting类型的配置项才会进入 Settings Catalog(@config[key].is_a?(FileSetting));
  • 可按 section(如:main、:server、:agent)筛选要管理哪些配置段;
  • 每个 FileSetting 通过to_resource转成一个file资源后加入 Catalog;
  • 随后还会补入用户资源(add_user_resources)和环境资源(add_environment_resources)。

实际应用发生在use方法(lib/puppet/settings.rb#L1125-L1159):它受settings_catalog设置开关控制(if Puppet[:settings_catalog]),把to_catalog(*sections)的结果.to_ral转换为 RAL Catalog,设置catalog.host_config = false(所以它不会发报告、不会写状态库),然后catalog.apply执行事务;若有资源失败,则从 report 中收集失败事件并抛出初始化错误。

一个隐蔽的竞态条件:文件存在性决定资源是否入册

文档特别提醒了一个"令人惊讶"的行为:File[puppetdlockfile]只有在磁盘上真实存在时才会被加入 Settings Catalog。这正是 lib/puppet/settings/file_setting.rb#L125-L137 的to_resource逻辑:

def to_resource type = self.type return nil unless type path = value return nil unless path.is_a?(String) path = File.expand_path(path) return nil unless type == :directory || Puppet::FileSystem.exist?(path) return nil if path =~ %r{^/dev} || path =~ %r{^[A-Z]:/dev}i resource = Puppet::Resource.new(:file, path) ... end

只有目录类型总是被管理;普通文件必须已存在于磁盘上才会生成资源,/dev设备路径被显式排除。由此引发文档提到的竞态条件:当另一个进程正持有 puppetdlock 锁(文件因此存在)并在应用 Catalog 时,Settings Catalog 就会试图管理这个锁文件;一旦时序错开,锁文件被删除或重建,就会在文件存在与不存在两种状态间"抖动"。这正是当年 PUP-1070 中 race condition 的根源。

用 File.open 包装法定位竞态

文档给出了一种非常实用的调试手段:包装File.open方法,在有人以非白名单调用路径访问puppetdlock文件时中断(drop into pry),从而揪出"幽灵调用者":

# 包装 File.open,出处见文档引用的调试技巧 class File WHITELIST = [ /pidlock.rb:39/ ] class << self alias xxx_orig_open open end def self.open(name, *rest, &block) # 检查白名单中任何"合法"的 File.open 调用 white_listed = caller(0).find do |line| WHITELIST.find { |re| re.match(line) } end # 如果在这里进入 IRB,请查看 caller,它可能就是你要找的"鬼" binding.pry if name =~ /puppetdlock/ and not white_listed xxx_orig_open(name, *rest, &block) end end

其思路是:pidlock.rb:39对应的锁定代码是被允许的调用者;其余任何对puppetdlock的File.open都值得怀疑。在断点处通过caller回溯调用栈,即可定位是哪个模块、哪条路径在意外读写锁文件。binding.pry需要项目里引入了 pry 中可查到相关开发依赖);若未引入,也可改用binding.irb或打日志堆栈替代。

实践小结:什么时候该用哪种 Catalog

场景使用的 Catalog 形态依据
编写 catalog terminus、服务端过滤逻辑Resource Catalog由 compiler terminus 产出并序列化传输
编写 type / provider 测试,需要执行 applyRAL Catalog(to_ral)含Puppet::Type实例,可创建 Transaction
遍历资源依赖、检查边与顺序RAL Catalog 的relationship_graph依赖边已显式展开(见 lib/puppet/graph/relationship_graph.rb)
调试 Puppet 启动时的自管理文件(锁、配置、目录)Settings Catalog(Settings#to_catalog)lib/puppet/settings.rb#L1064
单元测试快速造一个假 Catalog直接Puppet::Resource::Catalog.new+add_resource本文示例与 spec/unit/resource/catalog_spec.rb

几个关键判断准则:

  1. 看对象类型:Puppet::Resource实例居多是 Resource Catalog;Puppet::Type实例居多是 RAL Catalog。
  2. 看依赖遍历是否可用:relationship_graph报错时,先检查是不是还没调用to_ral。
  3. 看是否要真正执行:只有 RAL Catalog 能交给apply创建 Transaction 应用到系统。
  4. 写测试时注意节点名:构造 Catalog 用 node name 而非 certname,并善用rsrc.file/rsrc.line获得更友好的错误定位信息。
  5. Settings Catalog 的文件资源是"条件性"的:普通文件资源仅在文件存在时入册(lib/puppet/settings/file_setting.rb#L136),排查锁文件相关竞态时务必把这一点考虑进去。

理解了这三种 Catalog 形态及其转换链路(to_ral→relationship_graph→apply,以及Settings#to_catalog→to_ral→apply),无论是开发新 terminus、编写 type/provider 测试,还是排查 Puppet 自身初始化阶段的诡异竞态,都能快速定位到正确的处理对象。

  • 运维
  • DevOps
  • IaC

【免费下载链接】puppet

Server automation framework and application

项目地址:https://gitcode.com/gh_mirrors/pu/puppet
点击查看免费下载
上一篇:技术侦探手记:form-generator与Vue3整合谜案全记录
下一篇:PaddleNLP RoFormerv2Tokenizer 深度解析:基于 WordPiece 的中文预训练分词器使用与实现

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/27 21:34:29

3招搞定wordpress只保留二级目录,被黑挂马怎么选方案

3招搞定wordpress只保留二级目录,被黑挂马怎么选方案 网站被黑挂马不知道怎么办?别慌,先检查你的目录结构。很多站长在折腾wordpress只保留二级目录时,往往忽略了权限隔离,导致攻击者通过上传漏洞直接拿到Shell。这时候, 怎么选…

作者头像 李华
网站建设 2026/9/27 21:34:24

网站怎么做留言板:揭秘3种方案,从几百到几千多少钱才不亏

网站怎么做留言板:揭秘3种方案,从几百到几千多少钱才不亏 别被那些花里胡哨的模板骗了,看着挺像那么回事,真上线后才发现:排版僵化、交互卡顿、最要命的是那个留言板,要么根本用不了,要么发个言还得等半天。这种“模板网站太丑不够用”的窘境,是绝大多数中小企业主和运营人踩过的深坑。 很多人问:…

作者头像 李华
网站建设 2026/9/27 21:34:10

2026最新南阳网站备案全流程解析:避开域名服务器坑

2026最新南阳网站备案全流程解析:避开域名服务器坑 刚接手南阳这边的项目,最头疼的不是代码写不出,而是域名解析和服务器对接那些破事儿。很多老板以为买好域名、租好服务器就能开干,结果卡在“南阳网站备案”这一步,域名解析报错,服务器IP没备案直接404,搞不懂这里面的逻辑,网站根本打不开。…

作者头像 李华
网站建设 2026/9/27 21:34:05

哪一个军事网站做的比较好看这5个实战案例报价

哪一个军事网站做的比较好看这5个实战案例报价 模板网站太丑不够用,这是很多甲方在找外包时最直接的吐槽。我干这行十年,见过太多客户拿着几百块的模板站来问“为什么没流量”,也见过花大几十万定制站最后因备案问题卡壳的。今天不聊虚的,直接拆解 实战案例…

作者头像 李华
网站建设 2026/9/27 21:34:02

存储过程基本写法:TaoToken 统一 Key 接入 settings.json 配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/27 21:33:59

重生做网站小说避坑指南:3个SEO实操让流量翻倍

重生做网站小说避坑指南:3个SEO实操让流量翻倍 别被那些花里胡哨的模板网站骗了,真的丑到掉渣还不好用。 很多老板拿着几千块买的模板,上线后不仅客户跑光,百度收录还慢得让人想哭。 这份避坑指南,专门给想做小说网站的朋友,教你怎么从技术底层把SEO做透。 一、 为什么你的小说站SEO总是扑街…

作者头像 李华