Skip to content

Commit

Permalink
feat: Ubuntu 24.04 support
Browse files Browse the repository at this point in the history
This adds image lookup support for Ubuntu 24.04 (using image names and ssm param resolution).

Co-authored-by: Robby Pocase <robby.pocase@canonical.com>
  • Loading branch information
toabctl and rpocase committed Dec 19, 2024
1 parent 31f72ab commit 62d675a
Show file tree
Hide file tree
Showing 13 changed files with 253 additions and 5 deletions.
31 changes: 31 additions & 0 deletions integration/tests/custom_ami/custom_ami_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ var (
customAMIAL2023 string
customAMIBottlerocket string
customAMIUbuntuPro2204 string
customAMIUbuntuPro2404 string
)

var _ = BeforeSuite(func() {
Expand Down Expand Up @@ -79,6 +80,14 @@ var _ = BeforeSuite(func() {
Expect(err).NotTo(HaveOccurred())
customAMIUbuntuPro2204 = *output.Parameter.Value

// retrieve Ubuntu Pro 24.04 AMI
input = &awsssm.GetParameterInput{
Name: aws.String(fmt.Sprintf("/aws/service/canonical/ubuntu/eks-pro/24.04/%s/stable/current/amd64/hvm/ebs-gp3/ami-id", params.Version)),
}
output, err = ssm.GetParameter(context.Background(), input)
Expect(err).NotTo(HaveOccurred())
customAMIUbuntuPro2404 = *output.Parameter.Value

cmd := params.EksctlCreateCmd.WithArgs(
"cluster",
"--verbose", "4",
Expand Down Expand Up @@ -178,6 +187,28 @@ var _ = Describe("(Integration) [Test Custom AMI]", func() {
})

})

Context("ubuntu-pro-2404 un-managed nodegroups", func() {

It("can create a working nodegroup which can join the cluster", func() {
By(fmt.Sprintf("using the following EKS optimised AMI: %s", customAMIUbuntuPro2404))
content, err := os.ReadFile(filepath.Join("testdata/ubuntu-pro-2404.yaml"))
Expect(err).NotTo(HaveOccurred())
content = bytes.ReplaceAll(content, []byte("<generated>"), []byte(params.ClusterName))
content = bytes.ReplaceAll(content, []byte("<generated-region>"), []byte(params.Region))
content = bytes.ReplaceAll(content, []byte("<generated-ami>"), []byte(customAMIUbuntuPro2404))
cmd := params.EksctlCreateCmd.
WithArgs(
"nodegroup",
"--config-file", "-",
"--verbose", "4",
).
WithoutArg("--region", params.Region).
WithStdin(bytes.NewReader(content))
Expect(cmd).To(RunSuccessfully())
})

})
})

var _ = AfterSuite(func() {
Expand Down
17 changes: 17 additions & 0 deletions integration/tests/custom_ami/testdata/ubuntu-pro-2404.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

# name is generated
metadata:
name: <generated>
region: <generated-region>

nodeGroups:
- name: unm-ubuntu-pro-2404
ami: <generated-ami>
amiFamily: UbuntuPro2404
desiredCapacity: 1
overrideBootstrapCommand: |
#!/bin/bash
source /var/lib/cloud/scripts/eksctl/bootstrap.helper.sh
/etc/eks/bootstrap.sh <generated> --kubelet-extra-args "--node-labels=${NODE_LABELS}"
10 changes: 9 additions & 1 deletion pkg/ami/auto_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,18 @@ func MakeImageSearchPatterns(version string) map[string]map[int]string {
ImageClassNeuron: fmt.Sprintf("amazon-eks-gpu-node-%s-*", version),
ImageClassARM: fmt.Sprintf("amazon-eks-arm64-node-%s-*", version),
},
api.NodeImageFamilyUbuntuPro2404: {
ImageClassGeneral: fmt.Sprintf("ubuntu-eks-pro/k8s_%s/images/*24.04-amd64*", version),
ImageClassARM: fmt.Sprintf("ubuntu-eks-pro/k8s_%s/images/*24.04-arm64*", version),
},
api.NodeImageFamilyUbuntuPro2204: {
ImageClassGeneral: fmt.Sprintf("ubuntu-eks-pro/k8s_%s/images/*22.04-amd64*", version),
ImageClassARM: fmt.Sprintf("ubuntu-eks-pro/k8s_%s/images/*22.04-arm64*", version),
},
api.NodeImageFamilyUbuntu2404: {
ImageClassGeneral: fmt.Sprintf("ubuntu-eks/k8s_%s/images/*24.04-amd64*", version),
ImageClassARM: fmt.Sprintf("ubuntu-eks/k8s_%s/images/*24.04-arm64*", version),
},
api.NodeImageFamilyUbuntu2204: {
ImageClassGeneral: fmt.Sprintf("ubuntu-eks/k8s_%s/images/*22.04-amd64*", version),
ImageClassARM: fmt.Sprintf("ubuntu-eks/k8s_%s/images/*22.04-arm64*", version),
Expand Down Expand Up @@ -68,7 +76,7 @@ func MakeImageSearchPatterns(version string) map[string]map[int]string {
// OwnerAccountID returns the AWS account ID that owns worker AMI.
func OwnerAccountID(imageFamily, region string) (string, error) {
switch imageFamily {
case api.NodeImageFamilyUbuntuPro2204, api.NodeImageFamilyUbuntu2204, api.NodeImageFamilyUbuntu2004, api.NodeImageFamilyUbuntu1804:
case api.NodeImageFamilyUbuntuPro2404, api.NodeImageFamilyUbuntu2404, api.NodeImageFamilyUbuntuPro2204, api.NodeImageFamilyUbuntu2204, api.NodeImageFamilyUbuntu2004, api.NodeImageFamilyUbuntu1804:
return ownerIDUbuntuFamily, nil
case api.NodeImageFamilyAmazonLinux2023, api.NodeImageFamilyAmazonLinux2:
return api.EKSResourceAccountID(region), nil
Expand Down
10 changes: 10 additions & 0 deletions pkg/ami/auto_resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@ var _ = Describe("AMI Auto Resolution", func() {
Expect(ownerAccount).To(BeEquivalentTo("099720109477"))
Expect(err).NotTo(HaveOccurred())
})
It("should return the Ubuntu Account ID for Ubuntu images", func() {
ownerAccount, err := OwnerAccountID(api.NodeImageFamilyUbuntu2404, region)
Expect(ownerAccount).To(BeEquivalentTo("099720109477"))
Expect(err).NotTo(HaveOccurred())
})
It("should return the Ubuntu Account ID for Ubuntu images", func() {
ownerAccount, err := OwnerAccountID(api.NodeImageFamilyUbuntuPro2404, region)
Expect(ownerAccount).To(BeEquivalentTo("099720109477"))
Expect(err).NotTo(HaveOccurred())
})

It("should return the Windows Account ID for Windows Server images", func() {
ownerAccount, err := OwnerAccountID(api.NodeImageFamilyWindowsServer2022CoreContainer, region)
Expand Down
23 changes: 23 additions & 0 deletions pkg/ami/ssm_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,16 @@ func MakeSSMParameterName(version, instanceType, imageFamily string) (string, er
return fmt.Sprint("/aws/service/canonical/ubuntu/", eksProduct, "/", ubuntuReleaseName(imageFamily), "/", version, "/stable/current/", ubuntuArchName(instanceType), "/hvm/ebs-gp2/ami-id"), nil
default:
return "", fmt.Errorf("unknown image family %s", imageFamily)
case api.NodeImageFamilyUbuntu2404,
api.NodeImageFamilyUbuntuPro2404:
if err := validateVersionForUbuntu(version, imageFamily); err != nil {
return "", err
}
eksProduct := "eks"
if imageFamily == api.NodeImageFamilyUbuntuPro2404 {
eksProduct = "eks-pro"
}
return fmt.Sprint("/aws/service/canonical/ubuntu/", eksProduct, "/", ubuntuReleaseName(imageFamily), "/", version, "/stable/current/", ubuntuArchName(instanceType), "/hvm/ebs-gp3/ami-id"), nil
}
}

Expand Down Expand Up @@ -177,6 +187,8 @@ func ubuntuReleaseName(imageFamily string) string {
return "20.04"
case api.NodeImageFamilyUbuntu2204, api.NodeImageFamilyUbuntuPro2204:
return "22.04"
case api.NodeImageFamilyUbuntu2404, api.NodeImageFamilyUbuntuPro2404:
return "24.04"
default:
return "18.04"
}
Expand Down Expand Up @@ -214,6 +226,17 @@ func validateVersionForUbuntu(version, imageFamily string) error {
if !supportsUbuntu {
return &UnsupportedQueryError{msg: fmt.Sprintf("%s requires EKS version greater or equal than %s", imageFamily, minVersion)}
}
case api.NodeImageFamilyUbuntu2404, api.NodeImageFamilyUbuntuPro2404:
var err error
supportsUbuntu := false
const minVersion = api.Version1_31
supportsUbuntu, err = utils.IsMinVersion(minVersion, version)
if err != nil {
return err
}
if !supportsUbuntu {
return &UnsupportedQueryError{msg: fmt.Sprintf("%s requires EKS version greater or equal than %s", imageFamily, minVersion)}
}
default:
return &UnsupportedQueryError{msg: fmt.Sprintf("SSM Parameter lookups for %s AMIs is not supported", imageFamily)}
}
Expand Down
112 changes: 112 additions & 0 deletions pkg/ami/ssm_resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,118 @@ var _ = Describe("AMI Auto Resolution", func() {
})
})

Context("and Ubuntu2404 family", func() {
BeforeEach(func() {
p = mockprovider.NewMockProvider()
instanceType = "t2.medium"
imageFamily = "Ubuntu2404"
})

DescribeTable("should return an error",
func(version string) {
resolver := NewSSMResolver(p.MockSSM())
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)

Expect(err).To(HaveOccurred())
Expect(err).To(MatchError("Ubuntu2404 requires EKS version greater or equal than 1.31"))
},
EntryDescription("When EKS version is %s"),
Entry(nil, "1.21"),
Entry(nil, "1.30"),
)

DescribeTable("should return a valid AMI",
func(version string) {
addMockGetParameter(p, fmt.Sprintf("/aws/service/canonical/ubuntu/eks/24.04/%s/stable/current/amd64/hvm/ebs-gp3/ami-id", version), expectedAmi)

resolver := NewSSMResolver(p.MockSSM())
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)

Expect(err).NotTo(HaveOccurred())
Expect(p.MockSSM().AssertNumberOfCalls(GinkgoT(), "GetParameter", 1)).To(BeTrue())
Expect(resolvedAmi).To(BeEquivalentTo(expectedAmi))
},
EntryDescription("When EKS version is %s"),
Entry(nil, "1.31"),
)

Context("for arm instance type", func() {
BeforeEach(func() {
instanceType = "a1.large"
})
DescribeTable("should return a valid AMI for arm64",
func(version string) {
addMockGetParameter(p, fmt.Sprintf("/aws/service/canonical/ubuntu/eks/24.04/%s/stable/current/arm64/hvm/ebs-gp3/ami-id", version), expectedAmi)

resolver := NewSSMResolver(p.MockSSM())
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)

Expect(err).NotTo(HaveOccurred())
Expect(p.MockSSM().AssertNumberOfCalls(GinkgoT(), "GetParameter", 1)).To(BeTrue())
Expect(resolvedAmi).To(BeEquivalentTo(expectedAmi))
},
EntryDescription("When EKS version is %s"),
Entry(nil, "1.31"),
)
})
})

Context("and UbuntuPro2404 family", func() {
BeforeEach(func() {
p = mockprovider.NewMockProvider()
instanceType = "t2.medium"
imageFamily = "UbuntuPro2404"
})

DescribeTable("should return an error",
func(version string) {
resolver := NewSSMResolver(p.MockSSM())
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)

Expect(err).To(HaveOccurred())
Expect(err).To(MatchError("UbuntuPro2404 requires EKS version greater or equal than 1.31"))
},
EntryDescription("When EKS version is %s"),
Entry(nil, "1.21"),
Entry(nil, "1.30"),
)

DescribeTable("should return a valid AMI",
func(version string) {
addMockGetParameter(p, fmt.Sprintf("/aws/service/canonical/ubuntu/eks-pro/24.04/%s/stable/current/amd64/hvm/ebs-gp3/ami-id", version), expectedAmi)

resolver := NewSSMResolver(p.MockSSM())
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)

Expect(err).NotTo(HaveOccurred())
Expect(p.MockSSM().AssertNumberOfCalls(GinkgoT(), "GetParameter", 1)).To(BeTrue())
Expect(resolvedAmi).To(BeEquivalentTo(expectedAmi))
},
EntryDescription("When EKS version is %s"),
Entry(nil, "1.31"),
)

Context("for arm instance type", func() {
BeforeEach(func() {
instanceType = "a1.large"
})
DescribeTable("should return a valid AMI for arm64",
func(version string) {
addMockGetParameter(p, fmt.Sprintf("/aws/service/canonical/ubuntu/eks-pro/24.04/%s/stable/current/arm64/hvm/ebs-gp3/ami-id", version), expectedAmi)

resolver := NewSSMResolver(p.MockSSM())
resolvedAmi, err = resolver.Resolve(context.Background(), region, version, instanceType, imageFamily)

Expect(err).NotTo(HaveOccurred())
Expect(p.MockSSM().AssertNumberOfCalls(GinkgoT(), "GetParameter", 1)).To(BeTrue())
Expect(resolvedAmi).To(BeEquivalentTo(expectedAmi))
},
EntryDescription("When EKS version is %s"),
Entry(nil, "1.31"),
)
})
})

Context("and Bottlerocket image family", func() {
BeforeEach(func() {
instanceType = "t2.medium"
Expand Down
2 changes: 2 additions & 0 deletions pkg/apis/eksctl.io/v1alpha5/outposts_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,8 @@ var _ = Describe("Outposts validation", func() {
Entry("Ubuntu2004", api.NodeImageFamilyUbuntu2004, true),
Entry("Ubuntu2204", api.NodeImageFamilyUbuntu2204, true),
Entry("UbuntuPro2204", api.NodeImageFamilyUbuntuPro2204, true),
Entry("Ubuntu2404", api.NodeImageFamilyUbuntuPro2204, true),
Entry("UbuntuPro2404", api.NodeImageFamilyUbuntuPro2204, true),
Entry("Windows2019Core", api.NodeImageFamilyWindowsServer2019CoreContainer, true),
Entry("Windows2019Full", api.NodeImageFamilyWindowsServer2019FullContainer, true),
Entry("Windows2022Core", api.NodeImageFamilyWindowsServer2022CoreContainer, true),
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/eksctl.io/v1alpha5/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@ const (
DefaultNodeImageFamily = NodeImageFamilyAmazonLinux2
NodeImageFamilyAmazonLinux2023 = "AmazonLinux2023"
NodeImageFamilyAmazonLinux2 = "AmazonLinux2"
NodeImageFamilyUbuntuPro2404 = "UbuntuPro2404"
NodeImageFamilyUbuntu2404 = "Ubuntu2404"
NodeImageFamilyUbuntuPro2204 = "UbuntuPro2204"
NodeImageFamilyUbuntu2204 = "Ubuntu2204"
NodeImageFamilyUbuntu2004 = "Ubuntu2004"
Expand Down Expand Up @@ -615,6 +617,8 @@ func SupportedAMIFamilies() []string {
return []string{
NodeImageFamilyAmazonLinux2023,
NodeImageFamilyAmazonLinux2,
NodeImageFamilyUbuntuPro2404,
NodeImageFamilyUbuntu2404,
NodeImageFamilyUbuntuPro2204,
NodeImageFamilyUbuntu2204,
NodeImageFamilyUbuntu2004,
Expand Down
4 changes: 3 additions & 1 deletion pkg/apis/eksctl.io/v1alpha5/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -1568,7 +1568,9 @@ func IsAmazonLinuxImage(imageFamily string) bool {

func IsUbuntuImage(imageFamily string) bool {
switch imageFamily {
case NodeImageFamilyUbuntuPro2204,
case NodeImageFamilyUbuntuPro2404,
NodeImageFamilyUbuntu2404,
NodeImageFamilyUbuntuPro2204,
NodeImageFamilyUbuntu2204,
NodeImageFamilyUbuntu2004,
NodeImageFamilyUbuntu1804:
Expand Down
8 changes: 8 additions & 0 deletions pkg/apis/eksctl.io/v1alpha5/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2183,6 +2183,14 @@ var _ = Describe("ClusterConfig validation", func() {
err = api.ValidateManagedNodeGroup(0, mng)
Expect(err).NotTo(HaveOccurred())

mng.AMIFamily = api.NodeImageFamilyUbuntu2404
err = api.ValidateManagedNodeGroup(0, mng)
Expect(err).NotTo(HaveOccurred())

mng.AMIFamily = api.NodeImageFamilyUbuntuPro2404
err = api.ValidateManagedNodeGroup(0, mng)
Expect(err).NotTo(HaveOccurred())

mng.AMIFamily = api.NodeImageFamilyBottlerocket
mng.OverrideBootstrapCommand = nil
err = api.ValidateManagedNodeGroup(0, mng)
Expand Down
29 changes: 29 additions & 0 deletions pkg/eks/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,35 @@ var _ = Describe("eksctl API", func() {
testEnsureAMI(Equal("ami-ubuntu"), "1.14")
})

It("should resolve AMI using SSM Parameter Store for Ubuntu2404 on 1.31", func() {
provider.MockSSM().On("GetParameter", mock.Anything, &ssm.GetParameterInput{
Name: aws.String("/aws/service/canonical/ubuntu/eks/24.04/1.31/stable/current/amd64/hvm/ebs-gp3/ami-id"),
}).Return(&ssm.GetParameterOutput{
Parameter: &ssmtypes.Parameter{
Value: aws.String("ami-ubuntu"),
},
}, nil)
ng.AMIFamily = api.NodeImageFamilyUbuntu2404

testEnsureAMI(Equal("ami-ubuntu"), "1.31")
})

It("should fall back to auto resolution for Ubuntu2404", func() {
ng.AMIFamily = api.NodeImageFamilyUbuntu2404
mockDescribeImages(provider, "ami-ubuntu", func(input *ec2.DescribeImagesInput) bool {
return input.Owners[0] == "099720109477"
})
testEnsureAMI(Equal("ami-ubuntu"), "1.14")
})

It("should fall back to auto resolution for UbuntuPro2404", func() {
ng.AMIFamily = api.NodeImageFamilyUbuntuPro2404
mockDescribeImages(provider, "ami-ubuntu", func(input *ec2.DescribeImagesInput) bool {
return input.Owners[0] == "099720109477"
})
testEnsureAMI(Equal("ami-ubuntu"), "1.14")
})

It("should retrieve the AMI from EC2 when AMI is auto", func() {
ng.AMI = "auto"
ng.InstanceType = "p2.xlarge"
Expand Down
4 changes: 2 additions & 2 deletions pkg/nodebootstrap/userdata.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func NewBootstrapper(clusterConfig *api.ClusterConfig, ng *api.NodeGroup) (Boots
return NewWindowsBootstrapper(clusterConfig, ng, clusterDNS), nil
}
switch ng.AMIFamily {
case api.NodeImageFamilyUbuntuPro2204, api.NodeImageFamilyUbuntu2204, api.NodeImageFamilyUbuntu2004, api.NodeImageFamilyUbuntu1804:
case api.NodeImageFamilyUbuntuPro2404, api.NodeImageFamilyUbuntu2404, api.NodeImageFamilyUbuntuPro2204, api.NodeImageFamilyUbuntu2204, api.NodeImageFamilyUbuntu2004, api.NodeImageFamilyUbuntu1804:
return NewUbuntuBootstrapper(clusterConfig, ng, clusterDNS), nil
case api.NodeImageFamilyBottlerocket:
return NewBottlerocketBootstrapper(clusterConfig, ng), nil
Expand Down Expand Up @@ -80,7 +80,7 @@ func NewManagedBootstrapper(clusterConfig *api.ClusterConfig, ng *api.ManagedNod
return NewManagedAL2Bootstrapper(ng), nil
case api.NodeImageFamilyBottlerocket:
return NewManagedBottlerocketBootstrapper(clusterConfig, ng), nil
case api.NodeImageFamilyUbuntu1804, api.NodeImageFamilyUbuntu2004, api.NodeImageFamilyUbuntu2204, api.NodeImageFamilyUbuntuPro2204:
case api.NodeImageFamilyUbuntu1804, api.NodeImageFamilyUbuntu2004, api.NodeImageFamilyUbuntu2204, api.NodeImageFamilyUbuntuPro2204, api.NodeImageFamilyUbuntu2404, api.NodeImageFamilyUbuntuPro2404:
return NewUbuntuBootstrapper(clusterConfig, ng, clusterDNS), nil
}
return nil, nil
Expand Down
Loading

0 comments on commit 62d675a

Please sign in to comment.