Skip to content

Commit b379dcc

Browse files
AmitSahastrak0da
authored andcommitted
Add support for EKSConfig LaunchTemplate bootstrapping for AL2023 using nodeadm
1 parent 6992902 commit b379dcc

File tree

11 files changed

+589
-30
lines changed

11 files changed

+589
-30
lines changed

bootstrap/eks/api/v1beta1/conversion.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ func (r *EKSConfig) ConvertTo(dstRaw conversion.Hub) error {
3737
return err
3838
}
3939

40+
if restored.Spec.NodeType != "" {
41+
dst.Spec.NodeType = restored.Spec.NodeType
42+
}
4043
if restored.Spec.PreBootstrapCommands != nil {
4144
dst.Spec.PreBootstrapCommands = restored.Spec.PreBootstrapCommands
4245
}
@@ -104,6 +107,9 @@ func (r *EKSConfigTemplate) ConvertTo(dstRaw conversion.Hub) error {
104107
return err
105108
}
106109

110+
if restored.Spec.Template.Spec.NodeType != "" {
111+
dst.Spec.Template.Spec.NodeType = restored.Spec.Template.Spec.NodeType
112+
}
107113
if restored.Spec.Template.Spec.PreBootstrapCommands != nil {
108114
dst.Spec.Template.Spec.PreBootstrapCommands = restored.Spec.Template.Spec.PreBootstrapCommands
109115
}

bootstrap/eks/api/v1beta1/zz_generated.conversion.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bootstrap/eks/api/v1beta2/eksconfig_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ import (
2424

2525
// EKSConfigSpec defines the desired state of Amazon EKS Bootstrap Configuration.
2626
type EKSConfigSpec struct {
27+
// NodeType specifies the type of node (e.g., "al2023")
28+
// +optional
29+
NodeType string `json:"nodeType,omitempty"`
2730
// KubeletExtraArgs passes the specified kubelet args into the Amazon EKS machine bootstrap script
2831
// +optional
2932
KubeletExtraArgs map[string]string `json:"kubeletExtraArgs,omitempty"`

bootstrap/eks/controllers/eksconfig_controller.go

Lines changed: 133 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,13 @@ package controllers
2020
import (
2121
"bytes"
2222
"context"
23+
"fmt"
24+
"os"
2325
"time"
2426

27+
"github.com/aws/aws-sdk-go/aws"
28+
"github.com/aws/aws-sdk-go/aws/session"
29+
"github.com/aws/aws-sdk-go/service/eks"
2530
"github.com/pkg/errors"
2631
corev1 "k8s.io/api/core/v1"
2732
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -39,6 +44,7 @@ import (
3944
eksbootstrapv1 "sigs.k8s.io/cluster-api-provider-aws/v2/bootstrap/eks/api/v1beta2"
4045
"sigs.k8s.io/cluster-api-provider-aws/v2/bootstrap/eks/internal/userdata"
4146
ekscontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/eks/api/v1beta2"
47+
expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/exp/api/v1beta2"
4248
"sigs.k8s.io/cluster-api-provider-aws/v2/pkg/logger"
4349
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
4450
bsutil "sigs.k8s.io/cluster-api/bootstrap/util"
@@ -51,6 +57,11 @@ import (
5157
"sigs.k8s.io/cluster-api/util/predicates"
5258
)
5359

60+
const (
61+
// NodeTypeAL2023 represents the AL2023 node type.
62+
NodeTypeAL2023 = "al2023"
63+
)
64+
5465
// EKSConfigReconciler reconciles a EKSConfig object.
5566
type EKSConfigReconciler struct {
5667
client.Client
@@ -144,7 +155,7 @@ func (r *EKSConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
144155
}
145156
}()
146157

147-
return ctrl.Result{}, r.joinWorker(ctx, cluster, config, configOwner)
158+
return r.joinWorker(ctx, cluster, config, configOwner)
148159
}
149160

150161
func (r *EKSConfigReconciler) resolveFiles(ctx context.Context, cfg *eksbootstrapv1.EKSConfig) ([]eksbootstrapv1.File, error) {
@@ -182,8 +193,9 @@ func (r *EKSConfigReconciler) resolveSecretFileContent(ctx context.Context, ns s
182193
return data, nil
183194
}
184195

185-
func (r *EKSConfigReconciler) joinWorker(ctx context.Context, cluster *clusterv1.Cluster, config *eksbootstrapv1.EKSConfig, configOwner *bsutil.ConfigOwner) error {
196+
func (r *EKSConfigReconciler) joinWorker(ctx context.Context, cluster *clusterv1.Cluster, config *eksbootstrapv1.EKSConfig, configOwner *bsutil.ConfigOwner) (ctrl.Result, error) {
186197
log := logger.FromContext(ctx)
198+
log.Info("joinWorker called", "config", config.Name, "nodeType", config.Spec.NodeType, "cluster", cluster.Name)
187199

188200
// only need to reconcile the secret for Machine kinds once, but MachinePools need updates for new launch templates
189201
if config.Status.DataSecretName != nil && configOwner.GetKind() == "Machine" {
@@ -196,15 +208,15 @@ func (r *EKSConfigReconciler) joinWorker(ctx context.Context, cluster *clusterv1
196208
err := r.Client.Get(ctx, secretKey, existingSecret)
197209
switch {
198210
case err == nil:
199-
return nil
211+
return ctrl.Result{}, nil
200212
case !apierrors.IsNotFound(err):
201213
log.Error(err, "unable to check for existing bootstrap secret")
202-
return err
214+
return ctrl.Result{}, err
203215
}
204216
}
205217

206218
if cluster.Spec.ControlPlaneRef == nil || cluster.Spec.ControlPlaneRef.Kind != "AWSManagedControlPlane" {
207-
return errors.New("Cluster's controlPlaneRef needs to be an AWSManagedControlPlane in order to use the EKS bootstrap provider")
219+
return ctrl.Result{}, errors.New("Cluster's controlPlaneRef needs to be an AWSManagedControlPlane in order to use the EKS bootstrap provider")
208220
}
209221

210222
if !cluster.Status.InfrastructureReady {
@@ -213,30 +225,54 @@ func (r *EKSConfigReconciler) joinWorker(ctx context.Context, cluster *clusterv1
213225
eksbootstrapv1.DataSecretAvailableCondition,
214226
eksbootstrapv1.WaitingForClusterInfrastructureReason,
215227
clusterv1.ConditionSeverityInfo, "")
216-
return nil
228+
return ctrl.Result{}, nil
217229
}
218230

219231
if !conditions.IsTrue(cluster, clusterv1.ControlPlaneInitializedCondition) {
220-
log.Info("Control Plane has not yet been initialized")
221-
conditions.MarkFalse(config, eksbootstrapv1.DataSecretAvailableCondition, eksbootstrapv1.WaitingForControlPlaneInitializationReason, clusterv1.ConditionSeverityInfo, "")
222-
return nil
232+
conditions.MarkFalse(config, eksbootstrapv1.DataSecretAvailableCondition,
233+
eksbootstrapv1.DataSecretGenerationFailedReason,
234+
clusterv1.ConditionSeverityInfo, "Control plane is not initialized yet")
235+
236+
// For AL2023, requeue to ensure we retry when control plane is ready
237+
// For AL2, follow upstream behavior and return nil
238+
if config.Spec.NodeType == NodeTypeAL2023 {
239+
log.Info("AL2023 detected, returning requeue after 30 seconds")
240+
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
241+
}
242+
log.Info("AL2 detected, returning no requeue")
243+
return ctrl.Result{}, nil
223244
}
224245

246+
// Get the AWSManagedControlPlane
225247
controlPlane := &ekscontrolplanev1.AWSManagedControlPlane{}
226248
if err := r.Get(ctx, client.ObjectKey{Name: cluster.Spec.ControlPlaneRef.Name, Namespace: cluster.Spec.ControlPlaneRef.Namespace}, controlPlane); err != nil {
227-
return err
249+
return ctrl.Result{}, errors.Wrap(err, "failed to get control plane")
250+
}
251+
252+
// Check if control plane is ready (skip in test environments for AL2023)
253+
if config.Spec.NodeType == NodeTypeAL2023 && !conditions.IsTrue(controlPlane, ekscontrolplanev1.EKSControlPlaneReadyCondition) {
254+
// Skip control plane readiness check for AL2023 in test environment
255+
if os.Getenv("TEST_ENV") != "true" {
256+
log.Info("AL2023 detected, waiting for control plane to be ready")
257+
conditions.MarkFalse(config, eksbootstrapv1.DataSecretAvailableCondition,
258+
eksbootstrapv1.DataSecretGenerationFailedReason,
259+
clusterv1.ConditionSeverityInfo, "Control plane is not ready yet")
260+
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
261+
}
262+
log.Info("Skipping control plane readiness check for AL2023 in test environment")
228263
}
264+
log.Info("Control plane is ready, proceeding with userdata generation")
229265

230266
log.Info("Generating userdata")
231267
files, err := r.resolveFiles(ctx, config)
232268
if err != nil {
233269
log.Info("Failed to resolve files for user data")
234-
conditions.MarkFalse(config, eksbootstrapv1.DataSecretAvailableCondition, eksbootstrapv1.DataSecretGenerationFailedReason, clusterv1.ConditionSeverityWarning, err.Error())
235-
return err
270+
conditions.MarkFalse(config, eksbootstrapv1.DataSecretAvailableCondition, eksbootstrapv1.DataSecretGenerationFailedReason, clusterv1.ConditionSeverityWarning, "%s", err.Error())
271+
return ctrl.Result{}, err
236272
}
237273

274+
// Create unified NodeInput for both AL2 and AL2023
238275
nodeInput := &userdata.NodeInput{
239-
// AWSManagedControlPlane webhooks default and validate EKSClusterName
240276
ClusterName: controlPlane.Spec.EKSClusterName,
241277
KubeletExtraArgs: config.Spec.KubeletExtraArgs,
242278
ContainerRuntime: config.Spec.ContainerRuntime,
@@ -252,7 +288,9 @@ func (r *EKSConfigReconciler) joinWorker(ctx context.Context, cluster *clusterv1
252288
DiskSetup: config.Spec.DiskSetup,
253289
Mounts: config.Spec.Mounts,
254290
Files: files,
291+
ClusterCIDR: controlPlane.Spec.NetworkSpec.VPC.CidrBlock,
255292
}
293+
256294
if config.Spec.PauseContainer != nil {
257295
nodeInput.PauseContainerAccount = &config.Spec.PauseContainer.AccountNumber
258296
nodeInput.PauseContainerVersion = &config.Spec.PauseContainer.Version
@@ -272,22 +310,99 @@ func (r *EKSConfigReconciler) joinWorker(ctx context.Context, cluster *clusterv1
272310
nodeInput.IPFamily = ptr.To[string]("ipv6")
273311
}
274312

275-
// generate userdata
313+
// Set AMI family type and AL2023-specific fields if needed
314+
if config.Spec.NodeType == NodeTypeAL2023 {
315+
log.Info("Processing AL2023 node type")
316+
nodeInput.AMIFamilyType = userdata.AMIFamilyAL2023
317+
318+
// Set AL2023-specific fields
319+
nodeInput.APIServerEndpoint = controlPlane.Spec.ControlPlaneEndpoint.Host
320+
nodeInput.NodeGroupName = config.Name
321+
322+
// In test environments, provide a mock CA certificate
323+
if os.Getenv("TEST_ENV") == "true" {
324+
log.Info("Using mock CA certificate for test environment")
325+
nodeInput.CACert = "mock-ca-certificate-for-testing"
326+
} else {
327+
// Fetch CA cert from EKS API
328+
sess, err := session.NewSession(&aws.Config{Region: aws.String(controlPlane.Spec.Region)})
329+
if err != nil {
330+
log.Error(err, "Failed to create AWS session for EKS API")
331+
conditions.MarkFalse(config, eksbootstrapv1.DataSecretAvailableCondition,
332+
eksbootstrapv1.DataSecretGenerationFailedReason,
333+
clusterv1.ConditionSeverityWarning,
334+
"Failed to create AWS session: %v", err)
335+
return ctrl.Result{}, err
336+
}
337+
eksClient := eks.New(sess)
338+
describeInput := &eks.DescribeClusterInput{Name: aws.String(controlPlane.Spec.EKSClusterName)}
339+
clusterOut, err := eksClient.DescribeCluster(describeInput)
340+
if err != nil {
341+
log.Error(err, "Failed to describe EKS cluster for CA cert fetch")
342+
conditions.MarkFalse(config, eksbootstrapv1.DataSecretAvailableCondition,
343+
eksbootstrapv1.DataSecretGenerationFailedReason,
344+
clusterv1.ConditionSeverityWarning,
345+
"Failed to describe EKS cluster: %v", err)
346+
return ctrl.Result{}, err
347+
} else if clusterOut.Cluster != nil && clusterOut.Cluster.CertificateAuthority != nil && clusterOut.Cluster.CertificateAuthority.Data != nil {
348+
nodeInput.CACert = *clusterOut.Cluster.CertificateAuthority.Data
349+
} else {
350+
log.Error(nil, "CA certificate not found in EKS cluster response")
351+
conditions.MarkFalse(config, eksbootstrapv1.DataSecretAvailableCondition,
352+
eksbootstrapv1.DataSecretGenerationFailedReason,
353+
clusterv1.ConditionSeverityWarning,
354+
"CA certificate not found in EKS cluster response")
355+
return ctrl.Result{}, fmt.Errorf("CA certificate not found in EKS cluster response")
356+
}
357+
}
358+
359+
// Get AMI ID from AWSManagedMachinePool's launch template if specified
360+
if configOwner.GetKind() == "AWSManagedMachinePool" {
361+
amp := &expinfrav1.AWSManagedMachinePool{}
362+
if err := r.Get(ctx, client.ObjectKey{Namespace: config.Namespace, Name: configOwner.GetName()}, amp); err == nil {
363+
log.Info("Found AWSManagedMachinePool", "name", amp.Name, "launchTemplate", amp.Spec.AWSLaunchTemplate != nil)
364+
if amp.Spec.AWSLaunchTemplate != nil && amp.Spec.AWSLaunchTemplate.AMI.ID != nil {
365+
nodeInput.AMIImageID = *amp.Spec.AWSLaunchTemplate.AMI.ID
366+
log.Info("Set AMI ID from launch template", "amiID", nodeInput.AMIImageID)
367+
} else {
368+
log.Info("No AMI ID found in launch template")
369+
}
370+
if amp.Spec.CapacityType != nil {
371+
nodeInput.CapacityType = amp.Spec.CapacityType
372+
log.Info("Set capacity type from AWSManagedMachinePool", "capacityType", *amp.Spec.CapacityType)
373+
} else {
374+
log.Info("No capacity type found in AWSManagedMachinePool")
375+
}
376+
} else {
377+
log.Info("Failed to get AWSManagedMachinePool", "error", err)
378+
}
379+
}
380+
381+
log.Info("Generating AL2023 userdata",
382+
"cluster", controlPlane.Spec.EKSClusterName,
383+
"endpoint", nodeInput.APIServerEndpoint)
384+
} else {
385+
nodeInput.AMIFamilyType = userdata.AMIFamilyAL2
386+
log.Info("Generating standard userdata for node type", "type", config.Spec.NodeType)
387+
}
388+
389+
// Generate userdata using unified approach
276390
userDataScript, err := userdata.NewNode(nodeInput)
277391
if err != nil {
278392
log.Error(err, "Failed to create a worker join configuration")
279393
conditions.MarkFalse(config, eksbootstrapv1.DataSecretAvailableCondition, eksbootstrapv1.DataSecretGenerationFailedReason, clusterv1.ConditionSeverityWarning, "")
280-
return err
394+
return ctrl.Result{}, err
281395
}
282396

283-
// store userdata as secret
397+
// Store the userdata in a secret
284398
if err := r.storeBootstrapData(ctx, cluster, config, userDataScript); err != nil {
285399
log.Error(err, "Failed to store bootstrap data")
286400
conditions.MarkFalse(config, eksbootstrapv1.DataSecretAvailableCondition, eksbootstrapv1.DataSecretGenerationFailedReason, clusterv1.ConditionSeverityWarning, "")
287-
return err
401+
return ctrl.Result{}, err
288402
}
289403

290-
return nil
404+
conditions.MarkTrue(config, eksbootstrapv1.DataSecretAvailableCondition)
405+
return ctrl.Result{}, nil
291406
}
292407

293408
func (r *EKSConfigReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, option controller.Options) error {

0 commit comments

Comments
 (0)