Skip to content

Commit

Permalink
S3 user data support for AWSMachinePool (#592)
Browse files Browse the repository at this point in the history
  • Loading branch information
AndiDog authored Jun 6, 2024
1 parent e2651cb commit 0bcf5b8
Show file tree
Hide file tree
Showing 21 changed files with 372 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ func (t Template) ControllersPolicy() *iamv1.PolicyDocument {
"s3:DeleteObject",
"s3:PutBucketPolicy",
"s3:PutBucketTagging",
"s3:PutLifecycleConfiguration",
},
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ Resources:
- s3:DeleteObject
- s3:PutBucketPolicy
- s3:PutBucketTagging
- s3:PutLifecycleConfiguration
Effect: Allow
Resource:
- arn:*:s3:::cluster-api-provider-aws-*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,23 @@ spec:
to become stable after it enters the InService state. If no value
is supplied by user a default value of 300 seconds is set
type: string
ignition:
description: Ignition defined options related to the bootstrapping
systems where Ignition is used.
properties:
version:
default: "2.3"
description: Version defines which version of Ignition will be
used to generate bootstrap data.
enum:
- "2.3"
- "3.0"
- "3.1"
- "3.2"
- "3.3"
- "3.4"
type: string
type: object
maxSize:
default: 1
description: MaxSize defines the maximum size of the group.
Expand Down
3 changes: 3 additions & 0 deletions exp/api/v1beta1/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ func (src *AWSMachinePool) ConvertTo(dstRaw conversion.Hub) error {
if restored.Spec.AvailabilityZoneSubnetType != nil {
dst.Spec.AvailabilityZoneSubnetType = restored.Spec.AvailabilityZoneSubnetType
}
if restored.Spec.Ignition != nil {
dst.Spec.Ignition = restored.Spec.Ignition
}

dst.Spec.DefaultInstanceWarmup = restored.Spec.DefaultInstanceWarmup
dst.Spec.AWSLaunchTemplate.NonRootVolumes = restored.Spec.AWSLaunchTemplate.NonRootVolumes
Expand Down
1 change: 1 addition & 0 deletions exp/api/v1beta1/zz_generated.conversion.go

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

4 changes: 4 additions & 0 deletions exp/api/v1beta2/awsmachinepool_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ type AWSMachinePoolSpec struct {
// SuspendProcesses defines a list of processes to suspend for the given ASG. This is constantly reconciled.
// If a process is removed from this list it will automatically be resumed.
SuspendProcesses *SuspendProcessesTypes `json:"suspendProcesses,omitempty"`

// Ignition defined options related to the bootstrapping systems where Ignition is used.
// +optional
Ignition *infrav1.Ignition `json:"ignition,omitempty"`
}

// SuspendProcessesTypes contains user friendly auto-completable values for suspended process names.
Expand Down
28 changes: 25 additions & 3 deletions exp/api/v1beta2/awsmachinepool_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

"sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
"sigs.k8s.io/cluster-api-provider-aws/v2/feature"
)

var log = ctrl.Log.WithName("awsmachinepool-resource")
Expand Down Expand Up @@ -62,12 +63,12 @@ func (r *AWSMachinePool) validateRootVolume() field.ErrorList {
return allErrs
}

if v1beta2.VolumeTypesProvisioned.Has(string(r.Spec.AWSLaunchTemplate.RootVolume.Type)) && r.Spec.AWSLaunchTemplate.RootVolume.IOPS == 0 {
if infrav1.VolumeTypesProvisioned.Has(string(r.Spec.AWSLaunchTemplate.RootVolume.Type)) && r.Spec.AWSLaunchTemplate.RootVolume.IOPS == 0 {
allErrs = append(allErrs, field.Required(field.NewPath("spec.awsLaunchTemplate.rootVolume.iops"), "iops required if type is 'io1' or 'io2'"))
}

if r.Spec.AWSLaunchTemplate.RootVolume.Throughput != nil {
if r.Spec.AWSLaunchTemplate.RootVolume.Type != v1beta2.VolumeTypeGP3 {
if r.Spec.AWSLaunchTemplate.RootVolume.Type != infrav1.VolumeTypeGP3 {
allErrs = append(allErrs, field.Required(field.NewPath("spec.awsLaunchTemplate.rootVolume.throughput"), "throughput is valid only for type 'gp3'"))
}
if *r.Spec.AWSLaunchTemplate.RootVolume.Throughput < 0 {
Expand Down Expand Up @@ -109,6 +110,22 @@ func (r *AWSMachinePool) validateAdditionalSecurityGroups() field.ErrorList {
return allErrs
}

func (r *AWSMachinePool) ignitionEnabled() bool {
return r.Spec.Ignition != nil
}

func (r *AWSMachinePool) validateIgnition() field.ErrorList {
var allErrs field.ErrorList

// Feature gate is not enabled but ignition is enabled then send a forbidden error.
if !feature.Gates.Enabled(feature.BootstrapFormatIgnition) && r.ignitionEnabled() {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "ignition"),
"can be set only if the BootstrapFormatIgnition feature gate is enabled"))
}

return allErrs
}

// ValidateCreate will do any extra validation when creating a AWSMachinePool.
func (r *AWSMachinePool) ValidateCreate() (admission.Warnings, error) {
log.Info("AWSMachinePool validate create", "machine-pool", klog.KObj(r))
Expand All @@ -120,6 +137,7 @@ func (r *AWSMachinePool) ValidateCreate() (admission.Warnings, error) {
allErrs = append(allErrs, r.Spec.AdditionalTags.Validate()...)
allErrs = append(allErrs, r.validateSubnets()...)
allErrs = append(allErrs, r.validateAdditionalSecurityGroups()...)
allErrs = append(allErrs, r.validateIgnition()...)

if len(allErrs) == 0 {
return nil, nil
Expand Down Expand Up @@ -168,4 +186,8 @@ func (r *AWSMachinePool) Default() {
log.Info("DefaultInstanceWarmup is zero, setting 300 seconds as default")
r.Spec.DefaultInstanceWarmup.Duration = 300 * time.Second
}

if r.ignitionEnabled() && r.Spec.Ignition.Version == "" {
r.Spec.Ignition.Version = infrav1.DefaultIgnitionVersion
}
}
5 changes: 5 additions & 0 deletions exp/api/v1beta2/zz_generated.deepcopy.go

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

41 changes: 29 additions & 12 deletions exp/controllers/awsmachinepool_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import (
"sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services"
asg "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services/autoscaling"
"sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services/ec2"
"sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services/s3"
"sigs.k8s.io/cluster-api-provider-aws/v2/pkg/logger"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
expclusterv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
Expand All @@ -62,6 +63,7 @@ type AWSMachinePoolReconciler struct {
asgServiceFactory func(cloud.ClusterScoper) services.ASGInterface
ec2ServiceFactory func(scope.EC2Scope) services.EC2Interface
reconcileServiceFactory func(scope.EC2Scope) services.MachinePoolReconcileInterface
objectStoreServiceFactory func(scope.S3Scope) services.ObjectStoreInterface
TagUnmanagedNetworkResources bool
}

Expand All @@ -88,6 +90,19 @@ func (r *AWSMachinePoolReconciler) getReconcileService(scope scope.EC2Scope) ser
return ec2.NewService(scope)
}

func (r *AWSMachinePoolReconciler) getObjectStoreService(scope scope.S3Scope) services.ObjectStoreInterface {
if scope.Bucket() == nil {
// S3 bucket usage not enabled, so object store service not needed
return nil
}

if r.objectStoreServiceFactory != nil {
return r.objectStoreServiceFactory(scope)
}

return s3.NewService(scope)
}

// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=awsmachinepools,verbs=get;list;watch;update;patch;delete
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=awsmachinepools/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=machinepools;machinepools/status,verbs=get;list;watch;patch
Expand Down Expand Up @@ -129,7 +144,7 @@ func (r *AWSMachinePoolReconciler) Reconcile(ctx context.Context, req ctrl.Reque

log = log.WithValues("cluster", klog.KObj(cluster))

infraCluster, err := r.getInfraCluster(ctx, log, cluster, awsMachinePool)
infraCluster, s3Scope, err := r.getInfraCluster(ctx, log, cluster, awsMachinePool)
if err != nil {
return ctrl.Result{}, errors.New("error getting infra provider cluster or control plane object")
}
Expand All @@ -140,6 +155,7 @@ func (r *AWSMachinePoolReconciler) Reconcile(ctx context.Context, req ctrl.Reque

// Create the machine pool scope
machinePoolScope, err := scope.NewMachinePoolScope(scope.MachinePoolScopeParams{
Logger: log,
Client: r.Client,
Cluster: cluster,
MachinePool: machinePool,
Expand Down Expand Up @@ -176,13 +192,13 @@ func (r *AWSMachinePoolReconciler) Reconcile(ctx context.Context, req ctrl.Reque
return ctrl.Result{}, r.reconcileDelete(machinePoolScope, infraScope, infraScope)
}

return ctrl.Result{}, r.reconcileNormal(ctx, machinePoolScope, infraScope, infraScope)
return ctrl.Result{}, r.reconcileNormal(ctx, machinePoolScope, infraScope, infraScope, s3Scope)
case *scope.ClusterScope:
if !awsMachinePool.ObjectMeta.DeletionTimestamp.IsZero() {
return ctrl.Result{}, r.reconcileDelete(machinePoolScope, infraScope, infraScope)
}

return ctrl.Result{}, r.reconcileNormal(ctx, machinePoolScope, infraScope, infraScope)
return ctrl.Result{}, r.reconcileNormal(ctx, machinePoolScope, infraScope, infraScope, s3Scope)
default:
return ctrl.Result{}, errors.New("infraCluster has unknown type")
}
Expand All @@ -200,7 +216,7 @@ func (r *AWSMachinePoolReconciler) SetupWithManager(ctx context.Context, mgr ctr
Complete(r)
}

func (r *AWSMachinePoolReconciler) reconcileNormal(ctx context.Context, machinePoolScope *scope.MachinePoolScope, clusterScope cloud.ClusterScoper, ec2Scope scope.EC2Scope) error {
func (r *AWSMachinePoolReconciler) reconcileNormal(ctx context.Context, machinePoolScope *scope.MachinePoolScope, clusterScope cloud.ClusterScoper, ec2Scope scope.EC2Scope, s3Scope scope.S3Scope) error {
clusterScope.Info("Reconciling AWSMachinePool")

// If the AWSMachine is in an error state, return early.
Expand Down Expand Up @@ -236,6 +252,7 @@ func (r *AWSMachinePoolReconciler) reconcileNormal(ctx context.Context, machineP
ec2Svc := r.getEC2Service(ec2Scope)
asgsvc := r.getASGService(clusterScope)
reconSvc := r.getReconcileService(ec2Scope)
objectStoreSvc := r.getObjectStoreService(s3Scope)

// Find existing ASG
asg, err := r.findASG(machinePoolScope, asgsvc)
Expand Down Expand Up @@ -278,7 +295,7 @@ func (r *AWSMachinePoolReconciler) reconcileNormal(ctx context.Context, machineP
machinePoolScope.Info("starting instance refresh", "number of instances", machinePoolScope.MachinePool.Spec.Replicas)
return asgsvc.StartASGInstanceRefresh(machinePoolScope)
}
if err := reconSvc.ReconcileLaunchTemplate(machinePoolScope, ec2Svc, canUpdateLaunchTemplate, runPostLaunchTemplateUpdateOperation); err != nil {
if err := reconSvc.ReconcileLaunchTemplate(machinePoolScope, machinePoolScope, s3Scope, ec2Svc, objectStoreSvc, canUpdateLaunchTemplate, runPostLaunchTemplateUpdateOperation); err != nil {
r.Recorder.Eventf(machinePoolScope.AWSMachinePool, corev1.EventTypeWarning, "FailedLaunchTemplateReconcile", "Failed to reconcile launch template: %v", err)
machinePoolScope.Error(err, "failed to reconcile launch template")
return err
Expand Down Expand Up @@ -605,7 +622,7 @@ func machinePoolToInfrastructureMapFunc(gvk schema.GroupVersionKind) handler.Map
}
}

func (r *AWSMachinePoolReconciler) getInfraCluster(ctx context.Context, log *logger.Logger, cluster *clusterv1.Cluster, awsMachinePool *expinfrav1.AWSMachinePool) (scope.EC2Scope, error) {
func (r *AWSMachinePoolReconciler) getInfraCluster(ctx context.Context, log *logger.Logger, cluster *clusterv1.Cluster, awsMachinePool *expinfrav1.AWSMachinePool) (scope.EC2Scope, scope.S3Scope, error) {
var clusterScope *scope.ClusterScope
var managedControlPlaneScope *scope.ManagedControlPlaneScope
var err error
Expand All @@ -619,7 +636,7 @@ func (r *AWSMachinePoolReconciler) getInfraCluster(ctx context.Context, log *log

if err := r.Get(ctx, controlPlaneName, controlPlane); err != nil {
// AWSManagedControlPlane is not ready
return nil, nil //nolint:nilerr
return nil, nil, nil //nolint:nilerr
}

managedControlPlaneScope, err = scope.NewManagedControlPlaneScope(scope.ManagedControlPlaneScopeParams{
Expand All @@ -631,10 +648,10 @@ func (r *AWSMachinePoolReconciler) getInfraCluster(ctx context.Context, log *log
TagUnmanagedNetworkResources: r.TagUnmanagedNetworkResources,
})
if err != nil {
return nil, err
return nil, nil, err
}

return managedControlPlaneScope, nil
return managedControlPlaneScope, managedControlPlaneScope, nil
}

awsCluster := &infrav1.AWSCluster{}
Expand All @@ -646,7 +663,7 @@ func (r *AWSMachinePoolReconciler) getInfraCluster(ctx context.Context, log *log

if err := r.Client.Get(ctx, infraClusterName, awsCluster); err != nil {
// AWSCluster is not ready
return nil, nil //nolint:nilerr
return nil, nil, nil //nolint:nilerr
}

// Create the cluster scope
Expand All @@ -659,8 +676,8 @@ func (r *AWSMachinePoolReconciler) getInfraCluster(ctx context.Context, log *log
TagUnmanagedNetworkResources: r.TagUnmanagedNetworkResources,
})
if err != nil {
return nil, err
return nil, nil, err
}

return clusterScope, nil
return clusterScope, clusterScope, nil
}
Loading

0 comments on commit 0bcf5b8

Please sign in to comment.