
Nomad 设备插件示例实战用文件系统模拟设备并打通 Fingerprint、Reserve、Stats 全流程【免费下载链接】nomadNomad is an easy-to-use, flexible, and performant workload orchestrator that can deploy a mix of microservice, batch, containerized, and non-containerized applications. Nomad is easy to operate and scale and has native Consul and Vault integrations.项目地址: https://gitcode.com/gh_mirrors/no/nomadNomad 通过设备插件Device Plugin机制将 GPU、FPGA 等异构硬件抽象为可调度的设备资源而 plugins/device/cmd/example/README.md 提供的nomad-device-example是官方开箱即用的最小参考实现它把指定目录下的普通文件当作设备来暴露、检测健康状态并采集统计信息。本文以该示例为骨架完整讲解设备插件的安装、配置、验证以及如何在作业与配额规范中引用设备并深入 plugins/device 包源码揭示插件背后的 gRPC 接口与 Nomad 调度原理。读完本文你将能够独立部署一个设备插件、理解Fingerprint / Reserve / Stats三条 RPC 的职责并能在 Job 与 Quota 中正确书写device块。一、示例插件是什么把文件当成设备示例插件源码位于 plugins/device/cmd/example/device.go其核心思路是将配置项dir指定目录下的文件建模为设备实例目录会被跳过周期性地扫描目录并通过流式 Fingerprint RPC将检测结果推送给 Nomad 客户端若文件的权限位与配置项unhealthy_perm完全一致则该设备被标记为unhealthy并附带人类可读的原因说明对已检测到的设备还会周期性采集统计信息文件大小、修改时间、文件模式。插件暴露的标识在源码中以常量形式定义device.go标识值含义pluginNameexample-fs-device插件名称内部日志用vendornomad设备厂商deviceTypefile设备类型deviceNamemock设备型号名三者拼接成设备组的唯一标识nomad/file/mock这正是 Job 与 Quota 中device块所使用的取值。需要特别说明示例中的devices目录可能尚未完整包含该示例的构建入口实际主入口在 plugins/device/cmd/example/cmd/main.go它通过plugins.Serve(factory)启动插件进程工厂函数返回example.NewExampleDevice(log)。该示例定位是“纯参考用途”的教学实现源码注释明确说明生产环境请参照 plugins/device/plugin_test.go 中的测试方式来验证自己的插件实现。二、插件进程如何启动Serve 与 gRPC 桥接设备插件本质是一个独立的进程通过 HashiCorp go-plugin 与 Nomad 客户端以 gRPC 通信。入口 cmd/main.go 调用的plugins.Serve(factory)定义在 plugins/serve.gofunc Serve(f PluginFactory) { logger : log.New(log.LoggerOptions{ Level: log.Trace, JSONFormat: true, }) plugin : f(logger) serve(plugin, logger) }serve内部通过类型断言区分插件种类plugins/serve.go若工厂返回值实现了device.DevicePlugin接口则调用 plugins/device/plugin.go 中的device.Serve注册PluginTypeBase与PluginTypeDevice两类 gRPC 插件若实现了drivers.DriverPlugin则按驱动插件方式启动否则打印Unsupported plugin type。设备插件的全部能力被抽象为DevicePlugin接口plugins/device/device.gotype DevicePlugin interface { base.BasePlugin // Fingerprint returns a stream of devices that are detected. Fingerprint(ctx context.Context) (-chan *FingerprintResponse, error) // Reserve is used to reserve a set of devices and retrieve mount instructions. Reserve(deviceIDs []string) (*ContainerReservation, error) // Stats returns a stream of statistics per device collected at the passed interval. Stats(ctx context.Context, interval time.Duration) (-chan *StatsResponse, error) }对应的 gRPC 服务定义在 plugins/device/proto/device.protoFingerprint(FingerprintRequest) returns (stream FingerprintResponse)流式上报检测到的设备及健康状态Reserve(ReserveRequest) returns (ReserveResponse)在分配前被调用返回设备的挂载与环境变量信息Stats(StatsRequest) returns (stream StatsResponse)按采集间隔持续返回设备统计。客户端侧的调用封装位于 plugins/device/client.godevicePluginClient将 gRPC 流转换为 Go channel并处理上下文取消与插件退出时的错误传播。这套接口设计意味着Nomad 调度器看到的永远是统一抽象的DeviceGroup与Device而具体硬件的差异被完全封装在插件内部。三、安装与客户端配置3.1 编译并放置插件将插件二进制编译到客户端代理的插件目录该目录由下文客户端配置中的plugin_dir指定nomad_plugin_dir/opt/nomad/plugins # for example go build -o $nomad_plugin_dir/nomad-device-example ./cmd需要在仓库根目录的 Go module 环境中执行当前仓库为github.com/hashicorp/nomad的镜像构建命令以仓库实际结构为准。plugin_dir配置项在客户端配置结构中对应 command/agent/config.go 的PluginDir string \hcl:plugin_dir 字段。3.2 客户端 Agent 配置在 Nomad 客户端 Agent 配置中加入plugin块原文档的完整示例client { enabled true } plugin_dir /opt/nomad/plugins plugin nomad-device-example { config { dir /tmp/nomad-device list_period 1s unhealthy_perm -rwxrwxrwx } }其中plugin nomad-device-example的名称必须与插件二进制文件名一致nomad-device-example。配置项汇总如下配置项类型默认值说明dirstring.要扫描的目录其中的文件被建模为“虚拟设备”list_periodstring5s扫描目录变化的间隔Go duration 语法如1s、500msunhealthy_permstring-rwxrwxrwx若某文件的权限位与该值一致则该设备被视为 unhealthy上述默认值、参数声明与 HCL 规范定义在 device.go 的configSpechclspec 声明以及Config结构体含codec标签中。配置下发后SetConfig会通过base.MsgPackDecode解码配置并用time.ParseDuration解析list_period解析失败会返回failed to parse list period错误然后以d.logger.Info打印最终生效的配置device.go。四、运行与验证Fingerprint 输出解读4.1 造两个设备一个健康一个不健康按原文档步骤创建设备目录与两个“设备”mkdir -p /tmp/nomad-device cd /tmp/nomad-device touch device01 chmod 0777 device01 touch device02device01权限为0777即-rwxrwxrwx与默认unhealthy_perm一致 →不健康device02权限为默认0644即-rw-r--r--→健康。健康判定逻辑在 device.go 的diffFiles中perms : f.Mode().Perm().String(); healthy : perms ! d.unhealthyPerm即按字符串精确比较权限位同时该函数会剔除已消失的文件、跳过目录只有设备集合或健康状态发生变化时才向外发送新的 Fingerprint 消息无变化则continue避免无谓流量。4.2 通过 node status 验证等待一个list_period后插件首次扫描源码中fingerprint使用time.NewTimer(0)保证启动立即触发一次此后按list_period周期重置定时器见 device.go可用以下命令查看客户端节点资源中的设备nomad node status -json -self | jq .NodeResources.Devices预期输出原文档完整示例[ { Attributes: null, Instances: [ { HealthDescription: Device has bad permissions \-rwxrwxrwx\, Healthy: false, ID: device01, Locality: null }, { HealthDescription: , Healthy: true, ID: device02, Locality: null } ], Name: mock, Type: file, Vendor: nomad } ]对照上述输出理解关键字段ID设备实例 ID即目录中的文件名它必须跨插件重启保持稳定因为 Nomad 调度时会用该 ID 进行设备分配proto 注释也强调ID必须稳定见 device.protoHealthy/HealthDescription健康状态与原因HealthDescription由diffFiles中的unhealthyDesc : fmt.Sprintf(Device has bad permissions %q, d.unhealthyPerm)生成Vendor / Type / Name即nomad / file / mockgetDeviceGroup还会附加一个演示用属性cool-attributedevice.go可用于约束constraint与亲和性affinityLocality硬件局部性信息如 PCI 总线 ID示例未填充所以为null。如果你修改了文件的权限或新增/删除文件插件会在下一个list_period后推送增量变化nomad node status -json -self会随之更新。五、在 Job 与 Quota 中引用设备设备组的引用形式为{Vendor}/{Type}/{Name}即nomad/file/mock。5.1 作业规范Job Specification在任务的resources块中声明设备及其数量原文档完整示例job job { group grp { task tsk { driver ... config {} resources { device nomad/file/mock { count 1 } } } } }调度器会为需要该设备的作业预留对应数量的健康设备并在任务启动前调用插件的Reserve获取挂载信息。示例插件的Reserve实现会校验设备 ID 是否已知未知返回unknown device %q错误并为每个设备生成一条挂载HostPath为dir下对应文件的绝对路径TaskPath为/tmp/task-mounts/{deviceID}device.go。挂载、环境变量与设备 cgroup 权限分别对应 proto 中ContainerReservation的envs、mounts、devices字段device.proto。5.2 配额规范Quota Specification在配额限制中为设备预留容量原文档完整示例name dev limit { region global region_limit { device nomad/file/mock { count 2 # to allow for deployments/reschedules } } }配额中的count代表该命名空间在global区域内可同时占用的nomad/file/mock设备总数示例注释提示设为 2 以容纳部署与重新调度deployment/reschedule过程中的额外占用。5.3 验证设备被正确识别可通过nomad node status -self查看客户端节点可用设备以及nomad job plan确认作业可以匹配到健康设备当设备全部不健康或不足时作业评估将因资源不满足而无法安置。六、源码级原理三条 RPC 是如何实现的6.1 Fingerprint流式上报与增量推送FsDevice.Fingerprintdevice.go先校验deviceDir是否已配置未配置返回codes.Internal错误随后启动长驻 goroutinefingerprint用time.NewTimer(0)保证立即执行首次扫描每次扫描通过ioutil.ReadDir列出目录文件读取失败则向通道发送device.NewFingerprintError(err)并结束调用diffFiles计算差异仅当检测到新增/删除/健康状态变化时才发送device.NewFingerprint(getDeviceGroup(detected))监听ctx.Done()实现优雅退出。diffFiles内部用deviceLock保护devices映射设备 ID → 健康状态保证与Stats并发安全。健康描述、跳过目录、删除失效设备等逻辑均在此完成device.go。6.2 Reserve预留设备并返回挂载指令Reserve(deviceIDs []string)接收调度器选中的设备 ID 列表返回ContainerReservation环境变量、挂载、设备规格。示例实现空列表返回codes.InvalidArgument逐个校验设备是否已知未知返回codes.InvalidArgument为每个设备追加一条MountHostPath指向宿主机文件TaskPath指向任务内的挂载点ReadOnly: false。这一设计呼应 proto 中 Reserve 的注释——它允许插件在分配前执行任何准备步骤如初始化驱动、加载库并把挂载细节交给 Nomad 客户端。6.3 Stats按间隔采集并流式输出Stats(ctx, interval)启动另一个长驻 goroutine以time.NewTimer(0)立即执行第一次采集之后按interval重置定时器device.go。collectStatsdevice.go对每个已检测设备执行os.Stat产出Summary文件大小bytesStats属性表sizebytes、modify_time最后修改时间、mode文件模式字符串。这些指标通过DeviceGroupStats按Vendor/Type/Name分组最终可由nomad alloc status或指标采集端消费。若目录下没有设备collectStats返回nil发送端continue跳过空消息。6.4 测试验证仓库用 plugins/device/mock.go 提供的MockDevicePlugin与StaticFingerprinter、StaticReserve、StaticStats等辅助构造器在 plugins/device/plugin_test.go 中覆盖了 PluginInfo、ConfigSchema、SetConfig、Fingerprint含流错误与上下文取消、Reserve、Stats含流错误与上下文取消等全部路径。例如TestDevicePlugin_Fingerprint验证流式返回多组设备并正确关闭通道TestDevicePlugin_Fingerprint_CancelCtx验证取消上下文后能收到context.Canceled错误——这些测试同时也是编写自定义设备插件时的行为契约参考。七、FAQ 与注意事项为什么nomad node status里看不到设备检查插件二进制是否放在plugin_dir指向的目录、文件名是否与plugin块名称一致确认dir目录存在且包含非目录文件查看客户端日志中failed to list device directory错误device.go。list_period解析失败怎么办SetConfig会返回failed to parse list period ...错误请使用合法的 Go duration 字符串如1s、5s、500ms。设备 ID 能变吗不能。proto 与Device.Validate均要求设备 ID 非空且稳定device.proto、device.go否则调度与已分配任务的挂载会失配。健康状态如何更新插件只在“变化”发生时推送 Fingerprint 消息这是一种高效的事件驱动设计修改文件权限后等待下一个list_period再查询即可看到更新。此示例能否直接上生产它被设计为教学参考源码注释明确标注 “purely for use as an example”请基于 plugins/device 的接口与测试范式实现真实硬件的设备插件并遵循仓库 contributing 中的插件开发规范。结语通过nomad-device-example你已走通设备插件从“把文件伪装成设备”到“被 Nomad 调度并挂载进任务”的完整链路。其价值在于它以极简的实现完整覆盖了DevicePlugin接口的Fingerprint / Reserve / Stats三条 RPC配合 plugins/device/proto/device.proto 的接口定义与 plugins/device/plugin_test.go 的测试契约可以作为开发真实设备插件GPU、FPGA、加速卡等的起点模板——替换设备发现逻辑、健康判定与挂载策略即可复用到你的硬件场景。【免费下载链接】nomadNomad is an easy-to-use, flexible, and performant workload orchestrator that can deploy a mix of microservice, batch, containerized, and non-containerized applications. Nomad is easy to operate and scale and has native Consul and Vault integrations.项目地址: https://gitcode.com/gh_mirrors/no/nomad创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考