ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

karpenter-provider-aws 嵌套虚拟化支持:EC2NodeClass 的 `cpuOptions.nestedVirtualization` 配置与实例类型兼容性实现指南

karpenter-provider-aws 嵌套虚拟化支持:EC2NodeClass 的 `cpuOptions.nestedVirtualization` 配置与实例类型兼容性实现指南 karpenter-provider-aws 嵌套虚拟化支持EC2NodeClass 的cpuOptions.nestedVirtualization配置与实例类型兼容性实现指南【免费下载链接】karpenter-provider-awsKarpenter is a Kubernetes Node Autoscaler built for flexibility, performance, and simplicity.项目地址: https://gitcode.com/GitHub_Trending/ka/karpenter-provider-aws导读本文围绕 karpenter-provider-aws 中嵌套虚拟化Nested Virtualization支持的设计与实现展开说明如何在EC2NodeClass上通过spec.cpuOptions.nestedVirtualization请求开启嵌套虚拟化的节点并深入解析 Karpenter 如何通过实例类型兼容性检查避免调度到不支持该特性的机型。读完本文你将掌握该字段的 YAML 配置方法、底层 API 传递链路从 NodeClass 到 EC2 Launch Template、实例类型筛选机制以及未来coreCount/threadsPerCore支持的设计方向。背景为什么嵌套虚拟化对某些工作负载是刚需嵌套虚拟化Nested Virtualization允许一台虚拟机VM在其内部再运行一个 hypervisor。这在 AWS 上意味着 EC2 实例可以承载需要直接访问 CPU 虚拟化指令的工作负载典型场景包括容器沙箱Container Sandboxes在 Pod 内再启动一层隔离边界每租户 MicroVM为多租户场景提供硬件级隔离的微型虚拟机开发环境需要自行启动虚拟机进行本地开发或测试的团队。在 EC2 增加CpuOptions.NestedVirtualization字段之前想要在 AWS 上获得嵌套虚拟化能力唯一的途径是租用裸金属bare-metal实例。而现在这只是一个开关在RunInstances和CreateLaunchTemplateAPI 中将该字段设置为enabled实例即以开启嵌套虚拟化的方式启动。需要特别注意的是并非所有实例家族都支持该特性。如果对不支持的机型开启嵌套虚拟化启动会直接失败并返回UnsupportedOperation错误。这正是 Karpenter 引入该特性的价值所在一方面让用户通过EC2NodeClass声明需求另一方面在调度阶段就过滤掉不支持的实例类型从根源上避免创建注定启动失败的 NodeClaim。设计目标与非目标Goals根据 cpu-options-nested-virtualization.md 的设计文档本次特性有三个明确目标在EC2NodeClass.spec上暴露cpuOptions.nestedVirtualization字段将CpuOptions透传到 EC2 Launch Template仅允许 Karpenter 选择在DescribeInstanceTypes返回的ProcessorInfo.SupportedFeatures中报告了nested-virtualization的实例类型。Non-Goals后续工作EC2 的CpuOptions还包含coreCount与threadsPerCore两个字段用于将实例固定到特定的核心数/每核线程数。虽然 EC2 API 允许这三个字段同时设置设计文档作者通过调用create-launch-template实测确认三者并存可以成功但本次 PR不涉及这两个字段原因是正确支持它们需要尚不存在的实例类型感知逻辑每个实例类型都有自己合法的核心数列表来自DescribeInstanceTypes的VCpuInfo.ValidCores。例如m8i.xlarge仅接受[1, 2]而c8i.2xlarge接受[1, 2, 3, 4]如果 NodeClass 硬编码coreCount: 4Karpenter 会静默丢弃所有合法核心数列表中不包含 4 的实例类型导致候选池收缩且不向用户暴露任何提示后续 PR 计划增加根据 Karpenter 实际选中的实例类型动态计算取值的逻辑从而让coreCount/threadsPerCore在不破坏 NodePool 多样性的前提下保持可用。API 更新EC2NodeClass 新增cpuOptionsSpec 用法示例在EC2NodeClass上启用嵌套虚拟化的最小配置如下apiVersion: karpenter.k8s.aws/v1 kind: EC2NodeClass metadata: name: nested-virt spec: cpuOptions: nestedVirtualization: enabled字段位于spec.cpuOptions.nestedVirtualization合法的取值只有两个enabled和disabled。这一点在 CRD 中有明确约束——karpenter.k8s.aws_ec2nodeclasses.yaml 中nestedVirtualization的enum即[enabled, disabled]超出范围的值会被 Kubernetes API Server 直接拒绝。CPUOptions 结构体与指针语义对应的 Go 类型定义在 ec2nodeclass.go// CPUOptions contains parameters for specifying the CPU configuration for provisioned EC2 nodes. type CPUOptions struct { // NestedVirtualization enables or disables nested virtualization on the instance. // When enabled, Karpenter filters instance types to only those reporting // nested-virtualization in ProcessorInfo.SupportedFeatures from DescribeInstanceTypes. // kubebuilder:validation:Enum:{enabled,disabled} // optional NestedVirtualization *string json:nestedVirtualization,omitempty }设计上特意使用指针类型*string而非值类型原因很关键大多数 NodeClass 不会设置该字段cpuOptions整体为 nil此时不应在每个生成的 Launch Template 中写入一个空的CpuOptions块。指针为 nil 时可以直接短路返回 nil从而保持 Launch Template 的简洁。在EC2NodeClassSpec中CPUOptions以指针形式挂在spec.cpuOptions下ec2nodeclass.go并提供了访问器方法CPUOptions()ec2nodeclass.go供下游 provider 读取。为什么不暴露 Kubernetes Node Label设计文档明确指出该特性不暴露任何 Kubernetes Node LabelNodeClass Spec 是唯一的用户可见入口。原因值得深入理解如果未来要将该能力与 Pod 级需求联动类似instance-tenancy那样由 Pod 需求驱动实例类型配置届时再增加 Label 才有意义但在缺少这套联动机制的情况下贸然发布一个类似instance-nested-virtualizationtrue的 Label 会产生误导它读起来像该特性已开启实际含义却是硬件可能支持该特性。用户几乎不可能按作者的意图去理解它。这一决策体现了先做对最小闭环不提前暴露语义含糊的接口的工程取舍。实例类型选择兼容性检查而非启动路径过滤当用户设置cpuOptions.nestedVirtualization: enabled后Karpenter 会在实例类型解析阶段丢弃任何未在处理器特性中声明nested-virtualization的类型。这项检查位于pkg/providers/instancetype/compatibility包中而不是pkg/providers/instance/filter的启动路径过滤器里文档对其中差异做了细致的解释。两层检查的执行时机差异兼容性检查Compatibility Check运行在实例类型解析期间即调度器选中某个类型之前。因此调度器永远不会看到不兼容的类型也永远不会为一个不兼容类型创建 NodeClaim启动路径过滤Launch-path Filter运行在调度器已经提交决策之后。若某个不兼容类型侥幸穿透到这一层过滤器会拒绝它Karpenter 使 NodeClaim 失败调度器随即创建另一个 NodeClaim……如此往复直到所有兼容类型被耗尽。用户看到的表象将是Karpenter 不停地创建又删除 NodeClaim体验极差。把筛选放在兼容性检查层本质上是把错误消灭在调度决策之前避免在启动阶段用失败重试来试错。源码实现兼容性检查的注册位于 compatibility.gonestedVirtualizationCompatibility与networkInterfaceCompatibility、amiFamilyCompatibility、placementGroupCompatibility、connectionTrackingCompatibility并列共同构成IsCompatibleWithNodeClass的检查链func IsCompatibleWithNodeClass(info ec2types.InstanceTypeInfo, nodeClass NodeClass, pg *placementgroup.PlacementGroup) bool { networkInterfaces : amifamily.ResolveNetworkInterfaces(nodeClass.NetworkInterfaces()) for _, check : range []CompatibleCheck{ networkInterfaceCompatibility(networkInterfaces), amiFamilyCompatibility(nodeClass.AMIFamily()), nestedVirtualizationCompatibility(nodeClass.CPUOptions()), placementGroupCompatibility(pg), connectionTrackingCompatibility(nodeClass.ConnectionTracking()), } { if !check.compatibleCheck(info) { return false } } return true }NodeClass接口要求提供CPUOptions() *v1.CPUOptions方法compatibility.go与EC2NodeClass上的访问器一一对应。嵌套虚拟化检查的核心逻辑compatibility.gotype nestedVirtualizationCheck struct { cpuOptions *v1.CPUOptions } func nestedVirtualizationCompatibility(cpuOptions *v1.CPUOptions) CompatibleCheck { return nestedVirtualizationCheck{ cpuOptions: cpuOptions, } } func (c nestedVirtualizationCheck) compatibleCheck(info ec2types.InstanceTypeInfo) bool { if c.cpuOptions nil || c.cpuOptions.NestedVirtualization nil || *c.cpuOptions.NestedVirtualization ! enabled { return true } if info.ProcessorInfo nil { return false } return lo.Contains(info.ProcessorInfo.SupportedFeatures, ec2types.SupportedAdditionalProcessorFeatureNestedVirtualization) }三个值得注意的实现细节短路径cpuOptions为 nil、NestedVirtualization为 nil、或取值不是enabled时直接返回true不限制任何类型。这保证未配置该字段的 NodeClass 完全不受影响数据缺失兜底ProcessorInfo为 nil即 EC2 未返回处理器信息时返回false宁可保守地排除该类型也不冒险放行真相唯一来源最终判定交给lo.Contains(info.ProcessorInfo.SupportedFeatures, ec2types.SupportedAdditionalProcessorFeatureNestedVirtualization)即完全以 EC2 API 返回的SupportedFeatures为准。测试验证兼容性测试在 compatibility/suite_test.go 中以表驱动方式覆盖了三种关键场景场景nestedVirtualization取值SupportedFeatures预期结果特性存在enabled包含nested-virtualization兼容true特性缺失enabled为空不兼容false显式禁用disabled为空兼容true不参与筛选这三个用例恰好覆盖了开启时严格筛选、关闭时不筛选的全部行为分支也印证了设计文档中只有在enabled时才收紧候选池的语义。Launch Template 传递cpuOptions()助手函数组装入口在 Launch Template 构建器CreateLaunchTemplateInputBuilder.Build中CpuOptions通过cpuOptions(b.options.CPUOptions)组装进RequestLaunchTemplateDatatypes.golt : ec2.CreateLaunchTemplateInput{ LaunchTemplateName: lo.ToPtr(LaunchTemplateName(b.options)), LaunchTemplateData: ec2types.RequestLaunchTemplateData{ BlockDeviceMappings: blockDeviceMappings(b.options.BlockDeviceMappings), CpuOptions: cpuOptions(b.options.CPUOptions), IamInstanceProfile: ec2types.LaunchTemplateIamInstanceProfileSpecificationRequest{ Name: lo.ToPtr(b.options.InstanceProfile), }, // ... }, }转换逻辑cpuOptions()助手函数定义在 launchtemplate.gofunc cpuOptions(cpuOptions *v1.CPUOptions) *ec2types.LaunchTemplateCpuOptionsRequest { if cpuOptions nil || cpuOptions.NestedVirtualization nil { return nil } return ec2types.LaunchTemplateCpuOptionsRequest{ NestedVirtualization: ec2types.NestedVirtualizationSpecification(*cpuOptions.NestedVirtualization), } }其行为与 CRD 的指针语义完全呼应cpuOptions为 nilNodeClass 未配置该字段最常见情况→ 返回 nilLaunch Template不包含任何CpuOptions块NestedVirtualization为 nil配置了cpuOptions但未设置该子字段→ 同样返回 nil字段被设置enabled或disabled→ 字符串被强转为 SDK 的ec2types.NestedVirtualizationSpecification枚举写入LaunchTemplateCpuOptionsRequest。单测佐证launchtemplate/cpuoptions_test.go 针对该函数提供了四个用例传入nil→ 返回 nil传入空的v1.CPUOptions{}→ 返回 nil即使结构体本身非空只要NestedVirtualization为 nil 就不写 CpuOptionsNestedVirtualization为enabled→ 返回非 nil且枚举值等于ec2types.NestedVirtualizationSpecification(enabled)NestedVirtualization为disabled→ 返回非 nil且枚举值等于ec2types.NestedVirtualizationSpecification(disabled)。这组测试从函数层面固化了指针语义 枚举转换的契约。实例类型兼容性以DescribeInstanceTypes为唯一真相ProcessorInfo.SupportedFeatures来自DescribeInstanceTypesAPI是判定某实例类型是否支持嵌套虚拟化的唯一权威来源。用 CLI 查询当前区域的支持列表可以直接用 AWS CLI 查询当前区域中支持该特性的实例类型aws ec2 describe-instance-types \ --filters Nameprocessor-info.supported-features,Valuesnested-virtualization \ --query InstanceTypes[*].InstanceType刻意不维护手写机型清单设计文档明确强调代码和文档中刻意不维护任何手写的支持机型家族清单。原因有二清单必然过时AWS 每新增一个支持该特性的家族手写清单就会立刻过时误导读者更糟的是它会让未来的读者把文档当成比 API 更权威的依据。因此实现完全依赖lo.Contains(info.ProcessorInfo.SupportedFeatures, nested-virtualization)这一行判断答案永远跟随 EC2 的真实返回天然具备AWS 新增机型即自动支持的演进能力。端到端数据流回顾将上文各环节串联一次开启嵌套虚拟化的节点供给完整链路为用户在EC2NodeClass.spec.cpuOptions.nestedVirtualization写入enabledCRD 枚举校验通过实例类型解析阶段nestedVirtualizationCompatibility检查每个候选类型的ProcessorInfo.SupportedFeatures丢弃不含nested-virtualization的类型compatibility.go调度器在过滤后的候选池中选定实例类型创建 NodeClaim生成 Launch Template 时cpuOptions()将CPUOptions结构体转为LaunchTemplateCpuOptionsRequest写入CpuOptions.NestedVirtualizationlaunchtemplate.go、types.goEC2 侧CreateLaunchTemplate/RunInstances携带NestedVirtualizationenabled启动实例由于类型已在前置阶段筛选不会触发UnsupportedOperation。未配置该字段的 NodeClass 则在第 2、4 步全部短路跳过兼容性检查直接返回 trueLaunch Template 不写CpuOptions块——这正是指针语义设计的意义所在。总结与后续展望嵌套虚拟化支持为 karpenter-provider-aws 补齐了声明式请求嵌套虚拟化节点的能力其设计要点可归纳为声明式 API通过EC2NodeClass.spec.cpuOptions.nestedVirtualization: enabled一个字段完成声明取值仅有enabled/disabled前置过滤兼容性检查在调度决策之前剔除不支持的类型避免反复创建又删除 NodeClaim的失败循环零默认开销指针语义保证绝大多数未配置的 NodeClass 生成的 Launch Template 完全不受影响真相单一来源机型支持列表不落地为静态清单永远以DescribeInstanceTypes的返回为准。未来工作方向是coreCount与threadsPerCoreEC2 API 已接受三者并存但要让它们可用且不破坏 NodePool 多样性需要依据VCpuInfo.ValidCores实现按实际选中的实例类型动态计算取值的逻辑这将是后续 PR 的主题。延伸阅读设计文档原文designs/cpu-options-nested-virtualization.mdAPI 类型定义pkg/apis/v1/ec2nodeclass.goCRD 校验约束pkg/apis/crds/karpenter.k8s.aws_ec2nodeclasses.yaml兼容性检查实现与测试pkg/providers/instancetype/compatibility/compatibility.go、pkg/providers/instancetype/compatibility/suite_test.goLaunch Template 传递实现与测试pkg/providers/launchtemplate/launchtemplate.go、pkg/providers/launchtemplate/types.go、pkg/providers/launchtemplate/cpuoptions_test.go【免费下载链接】karpenter-provider-awsKarpenter is a Kubernetes Node Autoscaler built for flexibility, performance, and simplicity.项目地址: https://gitcode.com/GitHub_Trending/ka/karpenter-provider-aws创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进