ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

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

Puppet Catalog 深度解析:Resource Catalog 与 RAL Catalog 两种形态及其在测试与 Settings 中的应用 运维DevOpsIaC【免费下载链接】puppetServer 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-L22extend 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_hashlib/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注意两点关键细节构造参数是节点名node name不是 certname。Puppet::Resource::Catalog.new(name, environment, code_id)的第一个参数只用于标识这个 Catalog 服务于哪个节点见 lib/puppet/resource/catalog.rb#L316-L339 的初始化逻辑同时会生成随机的catalog_uuid并将catalog_format初始化为 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 Cataloglib/puppet/resource/catalog.rb#L494-L496def 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::ErrorResource type Unknown was not found。顺带一提同一个to_catalog机制也被to_resourcelib/puppet/resource/catalog.rb#L499-L501和filterlib/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::RelationshipGraphlib/puppet/graph/relationship_graph.rb#L3-L9的类注释说得很直白它是 Catalog 的最终形态所有依赖边都被显式地放进图里用于按管理顺序遍历资源。其populate_fromlib/puppet/graph/relationship_graph.rb#L24-L34的构建流程是add_all_resources_as_vertices—— 所有资源作为顶点build_manual_dependencies—— 显式依赖require、before、subscribe、notifybuild_autorelation_dependencies—— 自动关系autorequire 等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 → 关系图这条链路的测试封装。此外applylib/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 CatalogPuppet 管理自身的迷你 Catalog它是什么除了上述两种 CatalogPuppet 还会在初始化时创建第三个迷你 CatalogSettings 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 Catalogconfig[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 Catalogto_ral含Puppet::Type实例可创建 Transaction遍历资源依赖、检查边与顺序RAL Catalog 的relationship_graph依赖边已显式展开见 lib/puppet/graph/relationship_graph.rb调试 Puppet 启动时的自管理文件锁、配置、目录Settings CatalogSettings#to_cataloglib/puppet/settings.rb#L1064单元测试快速造一个假 Catalog直接Puppet::Resource::Catalog.newadd_resource本文示例与 spec/unit/resource/catalog_spec.rb几个关键判断准则看对象类型Puppet::Resource实例居多是 Resource CatalogPuppet::Type实例居多是 RAL Catalog。看依赖遍历是否可用relationship_graph报错时先检查是不是还没调用to_ral。看是否要真正执行只有 RAL Catalog 能交给apply创建 Transaction 应用到系统。写测试时注意节点名构造 Catalog 用 node name 而非 certname并善用rsrc.file/rsrc.line获得更友好的错误定位信息。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 自身初始化阶段的诡异竞态都能快速定位到正确的处理对象。赞分享运维DevOpsIaC【免费下载链接】puppetServer automation framework and application项目地址https://gitcode.com/gh_mirrors/pu/puppet点击查看免费下载相关推荐Apache DataFusion Catalog 体系深度解析实现自定义 Catalog、Schema 与 Table ProviderApache DataFusion Catalog 体系深度解析实现自定义 Catalog、Schema 与 Table Provider Apache Da大数据数据分析后端Gravitino项目中的Paimon Catalog深度解析Gravitino项目中的Paimon Catalog深度解析 概述 在现代数据架构中Lakehouse架构正逐渐成为企业数据管理的标准模式。Apache G大数据数据目录数据治理数据湖后端StarRocks catalog() 函数详解查询当前 Catalog 名称与多 Catalog 会话管理StarRocks catalog 函数详解查询当前 Catalog 名称与多 Catalog 会话管理 导读 catalog 是 StarRocks 提供的数据库OLAP数据仓库大数据湖仓一体数据分析上一篇技术侦探手记form-generator与Vue3整合谜案全记录下一篇PaddleNLP RoFormerv2Tokenizer 深度解析基于 WordPiece 的中文预训练分词器使用与实现创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表