☰
Puppet V3 Facts HTTP API 详解:节点事实上报、Schema 约束与间接层实现原理
2026/9/27 9:14:20 网站建设 项目流程
  • 运维
  • DevOps
  • IaC

【免费下载链接】puppet

Server automation framework and application

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

导读

facts端点是 Puppet V3 HTTP API 中用于**按节点名写入(保存)事实(facts)**的专用通道:Puppet agent 在每次运行前把通过 Facter 采集到的节点事实以 JSON 形式 PUT 到 Puppet Server,服务端据此生成该节点的 catalog。本文以 api/docs/http_facts.md 为核心,完整讲解该端点的请求规范、JSON 请求体字段语义、facts.json 校验 Schema 的约束细节,并结合仓库源码(REST 间接层、Node::Facts 数据模型、HTTP 服务客户端)还原一条 PUT 请求从客户端组装、网络传输到服务端处理的完整链路,帮助开发者在自定义 Agent 工具或调试上报问题时快速定位。

一、端点定位:facts 是 V3 API 间接层端点之一

在 Puppet 4+ 中,Puppet 的 HTTP API 按职责拆分为配置服务(前缀/puppet)与证书服务(前缀/puppet-ca),所有配置类端点显式携带版本号,facts即属于/puppet/v3下的配置端点。根据 api/docs/http_api_index.md 的说明,V3 API 中每个派发到内部 "indirector" 框架的端点都遵循统一形式:

/puppet/v3/:indirection/:key?environment=:environment

其中:indirection为间接层名称(此处为facts),:key为间接层调用的键(此处为节点名:nodename),:environment为本次请求生效的环境名——即使某些端点不真正依赖环境,该查询参数也必须显式给出。另外该文档特别强调:服务器会忽略它不期望接收的任何多余参数,因此请求方无需担心多带参数导致失败,但也不能依赖未声明的参数生效。

从路由实现看,facts是一个特殊的单数形式间接层:lib/puppet/network/http/api/indirected_routes.rb 的plurality方法为facts单独返回:singular,因此端点路径是/facts/:nodename而非/factss/...,HTTP 方法到间接层操作的映射也按单数集合处理。

二、Save 操作:PUT 请求完整规范

原文档定义的唯一受支持操作是Save(保存事实),请求格式如下:

PUT /puppet/v3/facts/:nodename?environment=:environment

支持的 HTTP 方法

  • PUT:将请求体中的 JSON 事实存入指定节点。

支持的格式

  • application/json:请求体必须以 JSON 编码。

参数

  • 原文档声明None(无额外参数)。这一点在源码中得到印证:lib/puppet/indirector/facts/rest.rb 的save方法第一行即为:
raise ArgumentError, _("PUT does not accept options") unless request.options.empty?

即一旦请求携带任何 options,服务端间接层会直接以ArgumentError拒绝,这从实现层面证实了 "Parameters: None" 的契约。环境仍通过 URL 查询串中的environment传递,属于端点的通用约定而非 PUT 专用参数。

请求示例(原文档原始内容)

说明:为便于阅读,事实列表已做精简;JSON 已做格式化。

PUT /puppet/v3/facts/elmo.mydomain.com?environment=env Content-Type: application/json { "name": "elmo.mydomain.com", "values": { "architecture": "x86_64", "kernel": "Darwin", "domain": "local", "macaddress": "70:11:24:8c:33:a9", "osfamily": "Darwin", "operatingsystem": "Darwin", "facterversion": "1.7.2", "fqdn": "elmo.mydomain.com", }, "timestamp": "2013-09-09 15:49:27 -0700", "expiration": "2013-09-09 16:19:27 -0700" }

成功时服务端返回:

HTTP/1.1 200 OK Content-Type: application/json

三、请求体字段语义

对照 facts.json Schema,请求体是一个四字段 JSON 对象,四个字段全部为必填(required),且顶层不允许出现未声明字段(additionalProperties: false):

字段类型必填语义
namestring是事实所属的节点名,须与 URL 中的:nodename一致
valuesobject是该节点的全部事实键值对,键名必须匹配^[a-z][a-z0-9_]*$
timestampstring是事实采集时间;注意不遵循 JSON 标准的 date-time 格式
expirationstring是事实过期时间;同样不遵循 JSON date-time 格式

几点值得注意的约束细节:

  1. values的键名白名单:Schema 通过patternProperties限定事实键必须是小写字母开头、后续只能是[a-z0-9_]的正则形态,同时additionalProperties: false禁止出现任何不匹配该模式的键。例如fqdn、macaddress合法,而FQDN、os-family(含连字符)这类键会被判定非法。这对应 Puppet/Facter 长期沿用的"小写下划线"事实命名规范。
  2. timestamp/expiration的格式宽容性:Schema 只要求它们是 string,并显式注明不遵循 JSON 的 date-time 格式。示例中的"2013-09-09 15:49:27 -0700"(RFC 2822 风格)正是典型用法;而在 lib/puppet/node/facts.rb 的to_data_hash序列化路径中,Time 对象会被输出为iso8601(9)格式,两种字符串格式在反序列化时都能被Time.parse正确解析(见该文件initialize_from_hash,lib/puppet/node/facts.rb#L43-L61)。
  3. expiration与timestamp的间距:示例中两者相差 30 分钟(15:49:27 → 16:19:27),表达"这份事实在半小时内有效"的生命周期语义;过期后的事实应被视为不可靠数据。

四、源码视角:一条 PUT 请求的完整链路

4.1 客户端组装(Agent 侧)

Agent 侧的事实保存由 REST 间接层终结器发起。lib/puppet/indirector/facts/rest.rb 的save方法在通过空 options 校验后,从全局 HTTP session 路由到 puppet API,并调用 lib/puppet/http/service/compiler.rb 的put_facts:

def put_facts(name, environment:, facts:) formatter = Puppet::Network::FormatHandler.format_for(Puppet[:preferred_serialization_format]) headers = add_puppet_headers( 'Accept' => get_mime_types(Puppet::Node::Facts).join(', '), 'Content-Type' => formatter.mime ) response = @client.put( with_base_url("/facts/#{name}"), serialize(formatter, facts), headers: headers, params: { environment: environment } ) process_response(response) response end

可以看到请求 URL 正是with_base_url("/facts/#{name}"),环境通过params: { environment: environment }注入查询串,与文档的端点形式一一对应。序列化格式由Puppet[:preferred_serialization_format]设置决定,Content-Type与Accept均按该格式协商。

4.2 服务端间接层派发

服务端收到PUT /puppet/v3/facts/:nodename后,按 indirector 框架将facts间接层映射到对应终结器(terminus),终结器由facts_terminus设置决定(lib/puppet/node/facts.rb 中indirects :facts, :terminus_setting => :facts_terminus)。仓库中提供的主要终结器包括:

  • REST(lib/puppet/indirector/facts/rest.rb):本端点的客户端实现,负责远程 find/save;
  • Facter(lib/puppet/indirector/facts/facter.rb):本地采集终结器,allow_remote_requests?返回false,只用于从本机 Facter 读取事实,destroy/save均直接抛错,因为"代码存储只用于从 Facter 取事实";
  • YAML / JSON(lib/puppet/indirector/facts/yaml.rb、lib/puppet/indirector/facts/json.rb):把事实序列化为扁平文件落盘,分别存放于yamldir/facts/*.yaml(服务端模式)或clientyamldir/facts/*.yaml(客户端模式)等路径;
  • StoreConfigs(lib/puppet/indirector/facts/store_configs.rb):storeconfigs 功能的组成部分,同样禁止远程请求。

4.3 保存后的缓存联动:NodeExpirer

事实保存并不只是"写一份数据",它还会触发节点缓存的失效。lib/puppet/node/facts.rb 中定义了NodeExpirer模块并注入间接层:

module NodeExpirer def save(instance, key = nil, options = {}) Puppet::Node.indirection.expire(instance.name, options) super end end

即在保存事实的同时expire对应节点的已缓存 node 数据,确保下次 catalog 编译使用最新事实,避免陈旧缓存导致的配置漂移。

4.4 事实值的清洗与本地补充

服务端/客户端在保存前还会对事实做归一化处理:

  • sanitize(lib/puppet/node/facts.rb):把非 String/Boolean/Numeric/Array/Hash 的值统一to_s转成字符串,并尽力转码为 UTF-8;
  • add_local_facts(lib/puppet/node/facts.rb):补充clientcert(取certname)、clientversion(取Puppet.version)、clientnoop(取noop设置)三个本地事实。

五、Schema 校验与错误处理

5.1 Schema 文件

请求体必须严格遵循 api/schemas/facts.json。其完整约束要点已在第三节列出,核心是四个必填字段 +values键名模式 + 顶层与values的additionalProperties: false。任何缺失必填字段、出现未声明字段或非法事实键名,都会导致校验失败。

5.2 服务端错误响应

lib/puppet/network/http/api/indirected_routes.rb 展示了媒体类型协商失败的典型响应:当客户端发送的Content-Type不在支持集合内时,服务端抛出HTTPUnsupportedMediaTypeError(携带UNSUPPORTED_MEDIA_TYPEissue),因此务必使用application/json。REST 终结器的save在发生Puppet::HTTP::ResponseError时始终将其转换为 HTTP 错误抛出(与 find 不同,find 在fail_on_404为 false 时可对 404 返回 nil,见 rest.rb)。

六、配套能力:事实检索、过滤与数据模型

虽然本端点的文档主体是 Save,但同一间接层还提供了互补能力,理解它们有助于完整掌握 facts 数据流:

6.1 Find(GET 读取)

lib/puppet/http/service/compiler.rb 的get_facts实现GET /puppet/v3/facts/:name?environment=:environment,返回反序列化后的Puppet::Node::Facts对象,供需要读取远端节点事实的场景使用。

6.2 事实过滤检索(search)

YAML/JSON 终结器通过include Puppet::Indirector::FactSearch(lib/puppet/indirector/fact_search.rb)支持按条件检索节点:过滤条件形如facts.<fact_name>.<operator>或meta.timestamp.<operator>,可用操作符为eq、le、ge、lt、gt、ne(数值比较基于to_f,字符串比较基于to_s)。例如facts.kernel=eq=Darwin可筛选内核为 Darwin 的节点。

6.3 数据模型与序列化

Puppet::Node::Facts(lib/puppet/node/facts.rb)是事实的承载模型:name、values、timestamp为属性,to_data_hash输出name/values/timestamp/expiration四字段结构,from_data_hash(initialize_from_hash)负责反序列化,并可兼容 YAML 老格式中藏在values['_timestamp']里的时间戳。也就是说,文档中定义的 JSON 形态正是该模型在网络与磁盘两个维度上的统一表示。

七、实战要点小结

  • URL 必须带环境:PUT /puppet/v3/facts/:nodename?environment=:environment,:nodename与请求体name保持一致;
  • 只用 PUT + application/json:其他方法或媒体类型会被间接层路由/协商逻辑拒绝;
  • 不要携带额外 options:save在request.options非空时直接抛ArgumentError;
  • 四个字段缺一不可:name、values、timestamp、expiration均为必填,values键名必须匹配^[a-z][a-z0-9_]*$;
  • 时间字段用字符串表达:timestamp/expiration不遵循 JSON date-time,示例使用"2013-09-09 15:49:27 -0700"这类可被Time.parse解析的格式即可;
  • 保存事实会顺带清理节点缓存:借助NodeExpirer,避免 catalog 使用陈旧节点数据。

以上内容同时以 api/docs/http_facts.md 的接口定义和仓库内间接层源码为双重依据,读者可继续查阅 api/schemas/facts.json(校验规则)、lib/puppet/indirector/facts/rest.rb(REST 终结器)与 lib/puppet/node/facts.rb(数据模型)进行深入验证。

  • 运维
  • DevOps
  • IaC

【免费下载链接】puppet

Server automation framework and application

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

相关推荐

上一篇:为什么选择SJVideoPlayer:iOS视频播放器的终极解决方案
下一篇:音乐相似度分析利器:SongSim自相似性矩阵完整指南

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询