Skip to content
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

✨ Support BYO Public IPv4 Pool when provision infrastructure #4905

Merged
merged 1 commit into from
Jun 12, 2024
Merged
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
12 changes: 12 additions & 0 deletions api/v1beta1/awscluster_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,18 @@ func (src *AWSCluster) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.NetworkSpec.VPC.PrivateDNSHostnameTypeOnLaunch = restored.Spec.NetworkSpec.VPC.PrivateDNSHostnameTypeOnLaunch
dst.Spec.NetworkSpec.VPC.CarrierGatewayID = restored.Spec.NetworkSpec.VPC.CarrierGatewayID

if restored.Spec.NetworkSpec.VPC.ElasticIPPool != nil {
if dst.Spec.NetworkSpec.VPC.ElasticIPPool == nil {
dst.Spec.NetworkSpec.VPC.ElasticIPPool = &infrav2.ElasticIPPool{}
}
if restored.Spec.NetworkSpec.VPC.ElasticIPPool.PublicIpv4Pool != nil {
dst.Spec.NetworkSpec.VPC.ElasticIPPool.PublicIpv4Pool = restored.Spec.NetworkSpec.VPC.ElasticIPPool.PublicIpv4Pool
}
if restored.Spec.NetworkSpec.VPC.ElasticIPPool.PublicIpv4PoolFallBackOrder != nil {
dst.Spec.NetworkSpec.VPC.ElasticIPPool.PublicIpv4PoolFallBackOrder = restored.Spec.NetworkSpec.VPC.ElasticIPPool.PublicIpv4PoolFallBackOrder
}
}

// Restore SubnetSpec.ResourceID, SubnetSpec.ParentZoneName, and SubnetSpec.ZoneType fields, if any.
for _, subnet := range restored.Spec.NetworkSpec.Subnets {
for i, dstSubnet := range dst.Spec.NetworkSpec.Subnets {
Expand Down
22 changes: 22 additions & 0 deletions api/v1beta1/awsmachine_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ func (src *AWSMachine) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.PlacementGroupPartition = restored.Spec.PlacementGroupPartition
dst.Spec.PrivateDNSName = restored.Spec.PrivateDNSName
dst.Spec.SecurityGroupOverrides = restored.Spec.SecurityGroupOverrides
if restored.Spec.ElasticIPPool != nil {
if dst.Spec.ElasticIPPool == nil {
dst.Spec.ElasticIPPool = &infrav1.ElasticIPPool{}
}
if restored.Spec.ElasticIPPool.PublicIpv4Pool != nil {
dst.Spec.ElasticIPPool.PublicIpv4Pool = restored.Spec.ElasticIPPool.PublicIpv4Pool
}
if restored.Spec.ElasticIPPool.PublicIpv4PoolFallBackOrder != nil {
dst.Spec.ElasticIPPool.PublicIpv4PoolFallBackOrder = restored.Spec.ElasticIPPool.PublicIpv4PoolFallBackOrder
}
}

return nil
}
Expand Down Expand Up @@ -91,6 +102,17 @@ func (r *AWSMachineTemplate) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.Template.Spec.PlacementGroupPartition = restored.Spec.Template.Spec.PlacementGroupPartition
dst.Spec.Template.Spec.PrivateDNSName = restored.Spec.Template.Spec.PrivateDNSName
dst.Spec.Template.Spec.SecurityGroupOverrides = restored.Spec.Template.Spec.SecurityGroupOverrides
if restored.Spec.Template.Spec.ElasticIPPool != nil {
if dst.Spec.Template.Spec.ElasticIPPool == nil {
dst.Spec.Template.Spec.ElasticIPPool = &infrav1.ElasticIPPool{}
}
if restored.Spec.Template.Spec.ElasticIPPool.PublicIpv4Pool != nil {
dst.Spec.Template.Spec.ElasticIPPool.PublicIpv4Pool = restored.Spec.Template.Spec.ElasticIPPool.PublicIpv4Pool
}
if restored.Spec.Template.Spec.ElasticIPPool.PublicIpv4PoolFallBackOrder != nil {
dst.Spec.Template.Spec.ElasticIPPool.PublicIpv4PoolFallBackOrder = restored.Spec.Template.Spec.ElasticIPPool.PublicIpv4PoolFallBackOrder
}
}

return nil
}
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.

16 changes: 16 additions & 0 deletions api/v1beta2/awscluster_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,22 @@ func (r *AWSCluster) validateNetwork() field.ErrorList {
}
}

if r.Spec.NetworkSpec.VPC.ElasticIPPool != nil {
eipp := r.Spec.NetworkSpec.VPC.ElasticIPPool
if eipp.PublicIpv4Pool != nil {
if eipp.PublicIpv4PoolFallBackOrder == nil {
return append(allErrs, field.Invalid(field.NewPath("elasticIpPool.publicIpv4PoolFallbackOrder"), r.Spec.NetworkSpec.VPC.ElasticIPPool, "publicIpv4PoolFallbackOrder must be set when publicIpv4Pool is defined."))
}
awsPublicIpv4PoolPrefix := "ipv4pool-ec2-"
if !strings.HasPrefix(*eipp.PublicIpv4Pool, awsPublicIpv4PoolPrefix) {
return append(allErrs, field.Invalid(field.NewPath("elasticIpPool.publicIpv4Pool"), r.Spec.NetworkSpec.VPC.ElasticIPPool, fmt.Sprintf("publicIpv4Pool must start with %s.", awsPublicIpv4PoolPrefix)))
}
}
if eipp.PublicIpv4Pool == nil && eipp.PublicIpv4PoolFallBackOrder != nil {
return append(allErrs, field.Invalid(field.NewPath("elasticIpPool.publicIpv4PoolFallbackOrder"), r.Spec.NetworkSpec.VPC.ElasticIPPool, "publicIpv4Pool must be set when publicIpv4PoolFallbackOrder is defined."))
}
}

return allErrs
}

Expand Down
5 changes: 5 additions & 0 deletions api/v1beta2/awsmachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ type AWSMachineSpec struct {
// +optional
PublicIP *bool `json:"publicIP,omitempty"`

// ElasticIPPool is the configuration to allocate Public IPv4 address (Elastic IP/EIP) from user-defined pool.
//
// +optional
ElasticIPPool *ElasticIPPool `json:"elasticIpPool,omitempty"`

// AdditionalSecurityGroups is an array of references to security groups that should be applied to the
// instance. These security groups would be set in addition to any security groups defined
// at the cluster level or in the actuator. It is possible to specify either IDs of Filters. Using Filters
Expand Down
27 changes: 27 additions & 0 deletions api/v1beta2/awsmachine_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/utils/ptr"
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 @@ -64,6 +65,7 @@ func (r *AWSMachine) ValidateCreate() (admission.Warnings, error) {
allErrs = append(allErrs, r.validateSSHKeyName()...)
allErrs = append(allErrs, r.validateAdditionalSecurityGroups()...)
allErrs = append(allErrs, r.Spec.AdditionalTags.Validate()...)
allErrs = append(allErrs, r.validateNetworkElasticIPPool()...)

return nil, aggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, allErrs)
}
Expand Down Expand Up @@ -334,6 +336,31 @@ func (r *AWSMachine) validateRootVolume() field.ErrorList {
return allErrs
}

func (r *AWSMachine) validateNetworkElasticIPPool() field.ErrorList {
var allErrs field.ErrorList

if r.Spec.ElasticIPPool == nil {
return allErrs
}
if !ptr.Deref(r.Spec.PublicIP, false) {
allErrs = append(allErrs, field.Required(field.NewPath("spec.elasticIpPool"), "publicIp must be set to 'true' to assign custom public IPv4 pools with elasticIpPool"))
}
eipp := r.Spec.ElasticIPPool
if eipp.PublicIpv4Pool != nil {
if eipp.PublicIpv4PoolFallBackOrder == nil {
allErrs = append(allErrs, field.Invalid(field.NewPath("spec.elasticIpPool.publicIpv4PoolFallbackOrder"), r.Spec.ElasticIPPool, "publicIpv4PoolFallbackOrder must be set when publicIpv4Pool is defined."))
}
awsPublicIpv4PoolPrefix := "ipv4pool-ec2-"
if !strings.HasPrefix(*eipp.PublicIpv4Pool, awsPublicIpv4PoolPrefix) {
allErrs = append(allErrs, field.Invalid(field.NewPath("spec.elasticIpPool.publicIpv4Pool"), r.Spec.ElasticIPPool, fmt.Sprintf("publicIpv4Pool must start with %s.", awsPublicIpv4PoolPrefix)))
}
} else if eipp.PublicIpv4PoolFallBackOrder != nil {
allErrs = append(allErrs, field.Invalid(field.NewPath("spec.elasticIpPool.publicIpv4PoolFallbackOrder"), r.Spec.ElasticIPPool, "publicIpv4Pool must be set when publicIpv4PoolFallbackOrder is defined."))
}
mtulio marked this conversation as resolved.
Show resolved Hide resolved

return allErrs
}

func (r *AWSMachine) validateNonRootVolumes() field.ErrorList {
var allErrs field.ErrorList

Expand Down
68 changes: 68 additions & 0 deletions api/v1beta2/awsmachine_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,74 @@ func TestAWSMachineCreate(t *testing.T) {
},
wantErr: true,
},
{
name: "create with valid BYOIPv4",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "type",
PublicIP: aws.Bool(true),
ElasticIPPool: &ElasticIPPool{
PublicIpv4Pool: aws.String("ipv4pool-ec2-0123456789abcdef0"),
PublicIpv4PoolFallBackOrder: ptr.To(PublicIpv4PoolFallbackOrderAmazonPool),
},
},
},
wantErr: false,
},
{
name: "error when BYOIPv4 without fallback",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "type",
PublicIP: aws.Bool(true),
ElasticIPPool: &ElasticIPPool{
PublicIpv4Pool: aws.String("ipv4pool-ec2-0123456789abcdef0"),
},
},
},
wantErr: true,
},
{
name: "error when BYOIPv4 without public ipv4 pool",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "type",
PublicIP: aws.Bool(true),
ElasticIPPool: &ElasticIPPool{
PublicIpv4PoolFallBackOrder: ptr.To(PublicIpv4PoolFallbackOrderAmazonPool),
},
},
},
wantErr: true,
},
{
name: "error when BYOIPv4 with non-public IP set",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "type",
PublicIP: aws.Bool(false),
ElasticIPPool: &ElasticIPPool{
PublicIpv4Pool: aws.String("ipv4pool-ec2-0123456789abcdef0"),
PublicIpv4PoolFallBackOrder: ptr.To(PublicIpv4PoolFallbackOrderAmazonPool),
},
},
},
wantErr: true,
},
{
name: "error when BYOIPv4 with invalid pool name",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "type",
PublicIP: aws.Bool(true),
ElasticIPPool: &ElasticIPPool{
PublicIpv4Pool: aws.String("ipv4poolx-ec2-0123456789abcdef"),
PublicIpv4PoolFallBackOrder: ptr.To(PublicIpv4PoolFallbackOrderAmazonPool),
},
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
76 changes: 76 additions & 0 deletions api/v1beta2/network_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,12 @@ type VPCSpec struct {
// +optional
// +kubebuilder:validation:Enum:=ip-name;resource-name
PrivateDNSHostnameTypeOnLaunch *string `json:"privateDnsHostnameTypeOnLaunch,omitempty"`

// ElasticIPPool contains specific configuration to allocate Public IPv4 address (Elastic IP) from user-defined pool
// brought to AWS for core infrastructure resources, like NAT Gateways and Public Network Load Balancers for
// the API Server.
// +optional
ElasticIPPool *ElasticIPPool `json:"elasticIpPool,omitempty"`
}

// String returns a string representation of the VPC.
Expand All @@ -477,6 +483,22 @@ func (v *VPCSpec) IsIPv6Enabled() bool {
return v.IPv6 != nil
}

// GetElasticIPPool returns the custom Elastic IP Pool configuration when present.
func (v *VPCSpec) GetElasticIPPool() *ElasticIPPool {
return v.ElasticIPPool
}

// GetPublicIpv4Pool returns the custom public IPv4 pool brought to AWS when present.
func (v *VPCSpec) GetPublicIpv4Pool() *string {
if v.ElasticIPPool == nil {
return nil
}
if v.ElasticIPPool.PublicIpv4Pool != nil {
return v.ElasticIPPool.PublicIpv4Pool
}
return nil
}

// SubnetSpec configures an AWS Subnet.
type SubnetSpec struct {
// ID defines a unique identifier to reference this resource.
Expand Down Expand Up @@ -1013,3 +1035,57 @@ func (z ZoneType) String() string {
func (z ZoneType) Equal(other ZoneType) bool {
return z == other
}

// ElasticIPPool allows configuring a Elastic IP pool for resources allocating
// public IPv4 addresses on public subnets.
type ElasticIPPool struct {
// PublicIpv4Pool sets a custom Public IPv4 Pool used to create Elastic IP address for resources
// created in public IPv4 subnets. Every IPv4 address, Elastic IP, will be allocated from the custom
// Public IPv4 pool that you brought to AWS, instead of Amazon-provided pool. The public IPv4 pool
// resource ID starts with 'ipv4pool-ec2'.
//
// +kubebuilder:validation:MaxLength=30
nrb marked this conversation as resolved.
Show resolved Hide resolved
// +optional
PublicIpv4Pool *string `json:"publicIpv4Pool,omitempty"`

// PublicIpv4PoolFallBackOrder defines the fallback action when the Public IPv4 Pool has been exhausted,
// no more IPv4 address available in the pool.
//
// When set to 'amazon-pool', the controller check if the pool has available IPv4 address, when pool has reached the
// IPv4 limit, the address will be claimed from Amazon-pool (default).
//
// When set to 'none', the controller will fail the Elastic IP allocation when the publicIpv4Pool is exhausted.
//
// +kubebuilder:validation:Enum:=amazon-pool;none
// +optional
PublicIpv4PoolFallBackOrder *PublicIpv4PoolFallbackOrder `json:"publicIpv4PoolFallbackOrder,omitempty"`
mtulio marked this conversation as resolved.
Show resolved Hide resolved

// TODO(mtulio): add future support of user-defined Elastic IP to allow users to assign BYO Public IP from
// 'static'/preallocated amazon-provided IPsstrucute currently holds only 'BYO Public IP from Public IPv4 Pool' (user brought to AWS),
// although a dedicated structure would help to hold 'BYO Elastic IP' variants like:
// - AllocationIdPoolApiLoadBalancer: an user-defined (static) IP address to the Public API Load Balancer.
// - AllocationIdPoolNatGateways: an user-defined (static) IP address to allocate to NAT Gateways (egress traffic).
}

// PublicIpv4PoolFallbackOrder defines the list of available fallback action when the PublicIpv4Pool is exhausted.
// 'none' let the controllers return failures when the PublicIpv4Pool is exhausted - no more IPv4 available.
// 'amazon-pool' let the controllers to skip the PublicIpv4Pool and use the Amazon pool, the default.
// +kubebuilder:validation:XValidation:rule="self in ['none','amazon-pool']",message="allowed values are 'none' and 'amazon-pool'"
type PublicIpv4PoolFallbackOrder string

const (
// PublicIpv4PoolFallbackOrderAmazonPool refers to use Amazon-pool Public IPv4 Pool as a fallback strategy.
PublicIpv4PoolFallbackOrderAmazonPool = PublicIpv4PoolFallbackOrder("amazon-pool")

// PublicIpv4PoolFallbackOrderNone refers to not use any fallback strategy.
PublicIpv4PoolFallbackOrderNone = PublicIpv4PoolFallbackOrder("none")
)

func (r PublicIpv4PoolFallbackOrder) String() string {
return string(r)
}

// Equal compares PublicIpv4PoolFallbackOrder types and return true if input param is equal.
func (r PublicIpv4PoolFallbackOrder) Equal(e PublicIpv4PoolFallbackOrder) bool {
return r == e
}
35 changes: 35 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.

Loading