Skip to content

✨Add support for AMD SEV-SNP instances #5598

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/v1beta1/awscluster_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func (src *AWSCluster) ConvertTo(dstRaw conversion.Hub) error {
dst.Status.Bastion.NetworkInterfaceType = restored.Status.Bastion.NetworkInterfaceType
dst.Status.Bastion.CapacityReservationID = restored.Status.Bastion.CapacityReservationID
dst.Status.Bastion.MarketType = restored.Status.Bastion.MarketType
dst.Status.Bastion.CPUOptions = restored.Status.Bastion.CPUOptions
}
dst.Spec.Partition = restored.Spec.Partition

Expand Down
2 changes: 2 additions & 0 deletions api/v1beta1/awsmachine_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func (src *AWSMachine) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.CapacityReservationID = restored.Spec.CapacityReservationID
dst.Spec.MarketType = restored.Spec.MarketType
dst.Spec.NetworkInterfaceType = restored.Spec.NetworkInterfaceType
dst.Spec.CPUOptions = restored.Spec.CPUOptions
if restored.Spec.ElasticIPPool != nil {
if dst.Spec.ElasticIPPool == nil {
dst.Spec.ElasticIPPool = &infrav1.ElasticIPPool{}
Expand Down Expand Up @@ -109,6 +110,7 @@ func (r *AWSMachineTemplate) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.Template.Spec.CapacityReservationID = restored.Spec.Template.Spec.CapacityReservationID
dst.Spec.Template.Spec.MarketType = restored.Spec.Template.Spec.MarketType
dst.Spec.Template.Spec.NetworkInterfaceType = restored.Spec.Template.Spec.NetworkInterfaceType
dst.Spec.Template.Spec.CPUOptions = restored.Spec.Template.Spec.CPUOptions
if restored.Spec.Template.Spec.ElasticIPPool != nil {
if dst.Spec.Template.Spec.ElasticIPPool == nil {
dst.Spec.Template.Spec.ElasticIPPool = &infrav1.ElasticIPPool{}
Expand Down
2 changes: 2 additions & 0 deletions api/v1beta1/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions api/v1beta2/awsmachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,33 @@ const (
NetworkInterfaceTypeEFAWithENAInterface NetworkInterfaceType = NetworkInterfaceType("efa")
)

// AmdSevSnpSpecification defines the different values for AmdSevSnp
type AmdSevSnpSpecification string

const (
// AmdSevSnpSpecificationEnabled means AMD SEV SNP is enabled for the instance.
AmdSevSnpSpecificationEnabled AmdSevSnpSpecification = "enabled"

// AmdSevSnpSpecificationDisabled means AMD SEV SNP is disabled for the instance.
AmdSevSnpSpecificationDisabled AmdSevSnpSpecification = "disabled"
)

// CPUOptions defines the cpu options for the instance.
type CPUOptions struct {
// amdSevSnp specifies AMD SEV-SNP for the instance.
// +kubebuilder:validation:Enum=enabled;disabled
// +optional
AmdSevSnp AmdSevSnpSpecification `json:"amdSevSnp,omitempty"`
}

// Confidential computing support depends on the instance type.
// Only certain instance types in M6a, R6a and C6a series support AMD SEV-SNP. Reference: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/sev-snp.html
var (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While having this list allows us to avoid an API call to validate whether the instance type is supported or not, it's also going to be a burden to maintain and keep up-to-date.

I wonder if we could describe the instance and check for support via the returned API object, raising an error if it's not present, something like how CAPZ does it, though obviously their data types are different.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CAPZ uses cache to store the sku data, and then get data from the cache. It seems a lot of code changes if we want to do the same.
Or we don't do the check and rely on the AWS end to fail when creating the instance?

instanceTypesSupportingAmdSevsnp = []string{"m6a.large", "m6a.xlarge", "m6a.2xlarge", "m6a.4xlarge", "m6a.8xlarge",
"c6a.large", "c6a.xlarge", "c6a.2xlarge", "c6a.4xlarge", "c6a.8xlarge", "c6a.12xlarge", "c6a.16xlarge",
"r6a.large", "r6a.xlarge", "r6a.2xlarge", "r6a.4xlarge"}
)

// AWSMachineSpec defines the desired state of an Amazon EC2 instance.
// +kubebuilder:validation:XValidation:rule="!has(self.capacityReservationId) || !has(self.marketType) || self.marketType != 'Spot'",message="capacityReservationId may not be set when marketType is Spot"
// +kubebuilder:validation:XValidation:rule="!has(self.capacityReservationId) || !has(self.spotMarketOptions)",message="capacityReservationId cannot be set when spotMarketOptions is specified"
Expand Down Expand Up @@ -116,6 +143,10 @@ type AWSMachineSpec struct {
// +kubebuilder:validation:MinLength:=2
InstanceType string `json:"instanceType"`

// cpuOptions is the set of cpu options for the instance
// +optional
CPUOptions *CPUOptions `json:"cpuOptions,omitempty"`

// AdditionalTags is an optional set of tags to add to an instance, in addition to the ones added by default by the
// AWS provider. If both the AWSCluster and the AWSMachine specify the same tag name with different values, the
// AWSMachine's value takes precedence.
Expand Down
13 changes: 13 additions & 0 deletions api/v1beta2/awsmachine_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/utils/ptr"
"k8s.io/utils/strings/slices"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
Expand Down Expand Up @@ -78,6 +79,7 @@ func (*awsMachineWebhook) ValidateCreate(_ context.Context, obj runtime.Object)
allErrs = append(allErrs, r.Spec.AdditionalTags.Validate()...)
allErrs = append(allErrs, r.validateNetworkElasticIPPool()...)
allErrs = append(allErrs, r.validateInstanceMarketType()...)
allErrs = append(allErrs, r.validateInstanceTypeForConfidentialCompute()...)

return nil, aggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, allErrs)
}
Expand Down Expand Up @@ -417,6 +419,17 @@ func (r *AWSMachine) validateNonRootVolumes() field.ErrorList {
return allErrs
}

func (r *AWSMachine) validateInstanceTypeForConfidentialCompute() field.ErrorList {
var allErrs field.ErrorList
if r.Spec.CPUOptions != nil {
if r.Spec.CPUOptions.AmdSevSnp == "enabled" && !slices.Contains(instanceTypesSupportingAmdSevsnp, r.Spec.InstanceType) {
allErrs = append(allErrs, field.Required(field.NewPath("spec.InstanceType"), "this instance type doesn't support AMD SEV-SNP"))
}
}

return allErrs
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type.
func (*awsMachineWebhook) ValidateDelete(_ context.Context, _ runtime.Object) (admission.Warnings, error) {
return nil, nil
Expand Down
24 changes: 24 additions & 0 deletions api/v1beta2/awsmachine_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,30 @@ func TestAWSMachineCreate(t *testing.T) {
},
wantErr: true,
},
{
name: "invalid instance type for AMD SEV-SNP",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "test",
CPUOptions: &CPUOptions{
AmdSevSnp: "enabled",
},
},
},
wantErr: true,
},
{
name: "valid instance type for AMD SEV-SNP",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "m6a.large",
CPUOptions: &CPUOptions{
AmdSevSnp: "enabled",
},
},
},
wantErr: false,
},
{
name: "invalid tags return error",
machine: &AWSMachine{
Expand Down
3 changes: 3 additions & 0 deletions api/v1beta2/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ type Instance struct {
// The instance type.
Type string `json:"type,omitempty"`

// The cpu options of the instance.
CPUOptions *CPUOptions `json:"cpuOptions,omitempty"`

// The ID of the subnet of the instance.
SubnetID string `json:"subnetId,omitempty"`

Expand Down
25 changes: 25 additions & 0 deletions api/v1beta2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,16 @@ spec:
description: CapacityReservationID specifies the target Capacity
Reservation into which the instance should be launched.
type: string
cpuOptions:
description: The cpu options of the instance.
properties:
amdSevSnp:
description: amdSevSnp specifies AMD SEV-SNP for the instance.
enum:
- enabled
- disabled
type: string
type: object
ebsOptimized:
description: Indicates whether the instance is optimized for Amazon
EBS I/O.
Expand Down Expand Up @@ -3395,6 +3405,16 @@ spec:
description: CapacityReservationID specifies the target Capacity
Reservation into which the instance should be launched.
type: string
cpuOptions:
description: The cpu options of the instance.
properties:
amdSevSnp:
description: amdSevSnp specifies AMD SEV-SNP for the instance.
enum:
- enabled
- disabled
type: string
type: object
ebsOptimized:
description: Indicates whether the instance is optimized for Amazon
EBS I/O.
Expand Down
10 changes: 10 additions & 0 deletions config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2197,6 +2197,16 @@ spec:
description: CapacityReservationID specifies the target Capacity
Reservation into which the instance should be launched.
type: string
cpuOptions:
description: The cpu options of the instance.
properties:
amdSevSnp:
description: amdSevSnp specifies AMD SEV-SNP for the instance.
enum:
- enabled
- disabled
type: string
type: object
ebsOptimized:
description: Indicates whether the instance is optimized for Amazon
EBS I/O.
Expand Down
10 changes: 10 additions & 0 deletions config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachines.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,16 @@ spec:
- ssm-parameter-store
type: string
type: object
cpuOptions:
description: cpuOptions is the set of cpu options for the instance
properties:
amdSevSnp:
description: amdSevSnp specifies AMD SEV-SNP for the instance.
enum:
- enabled
- disabled
type: string
type: object
elasticIpPool:
description: ElasticIPPool is the configuration to allocate Public
IPv4 address (Elastic IP/EIP) from user-defined pool.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,17 @@ spec:
- ssm-parameter-store
type: string
type: object
cpuOptions:
description: cpuOptions is the set of cpu options for the
instance
properties:
amdSevSnp:
description: amdSevSnp specifies AMD SEV-SNP for the instance.
enum:
- enabled
- disabled
type: string
type: object
elasticIpPool:
description: ElasticIPPool is the configuration to allocate
Public IPv4 address (Elastic IP/EIP) from user-defined pool.
Expand Down
24 changes: 24 additions & 0 deletions pkg/cloud/services/ec2/instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,8 @@ func (s *Service) CreateInstance(ctx context.Context, scope *scope.MachineScope,

input.MarketType = scope.AWSMachine.Spec.MarketType

input.CPUOptions = scope.AWSMachine.Spec.CPUOptions

s.scope.Debug("Running instance", "machine-role", scope.Role())
s.scope.Debug("Running instance with instance metadata options", "metadata options", input.InstanceMetadataOptions)
out, err := s.runInstance(scope.Role(), input)
Expand Down Expand Up @@ -656,6 +658,7 @@ func (s *Service) runInstance(role string, i *infrav1.Instance) (*infrav1.Instan
input.MetadataOptions = getInstanceMetadataOptionsRequest(i.InstanceMetadataOptions)
input.PrivateDnsNameOptions = getPrivateDNSNameOptionsRequest(i.PrivateDNSName)
input.CapacityReservationSpecification = getCapacityReservationSpecification(i.CapacityReservationID)
input.CpuOptions = getInstanceCPUOptionsRequest(i.CPUOptions)

if i.Tenancy != "" {
input.Placement = &types.Placement{
Expand Down Expand Up @@ -1251,3 +1254,24 @@ func getPrivateDNSNameOptionsRequest(privateDNSName *infrav1.PrivateDNSName) *ty
HostnameType: types.HostnameType(aws.ToString(privateDNSName.HostnameType)),
}
}

func getInstanceCPUOptionsRequest(cpuOptions *infrav1.CPUOptions) *types.CpuOptionsRequest {
if cpuOptions == nil {
return nil
}

request := &types.CpuOptionsRequest{}
switch cpuOptions.AmdSevSnp {
case infrav1.AmdSevSnpSpecificationEnabled:
request.AmdSevSnp = types.AmdSevSnpSpecificationEnabled
case infrav1.AmdSevSnpSpecificationDisabled:
request.AmdSevSnp = types.AmdSevSnpSpecificationDisabled
default:
}

if *request == (types.CpuOptionsRequest{}) {
return nil
}

return request
}
Loading