Skip to content

Commit

Permalink
Add support for AL2023 (#186)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nuru authored Jun 17, 2024
1 parent ec07111 commit e9f908c
Show file tree
Hide file tree
Showing 23 changed files with 956 additions and 483 deletions.
163 changes: 99 additions & 64 deletions README.md

Large diffs are not rendered by default.

62 changes: 48 additions & 14 deletions README.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,59 @@ related:
url: "https://github.com/cloudposse/terraform-aws-ec2-instance-group"
# Short description of this project
description: |-
Terraform module to provision an EKS Node Group for [Elastic Container Service for Kubernetes](https://aws.amazon.com/eks/).
Terraform module to provision an EKS Managed Node Group for [Elastic Kubernetes Service](https://aws.amazon.com/eks/).
Instantiate it multiple times to create many EKS node groups with specific settings such as GPUs, EC2 instance types, or autoscale parameters.
Instantiate it multiple times to create EKS Managed Node Groups with specific settings such as GPUs, EC2 instance types, or autoscale parameters.
**IMPORTANT:** When SSH access is enabled without specifying a source security group, this module provisions `EKS Node Group` nodes that are globally accessible by SSH (22) port. Normally, AWS recommends that no security group allows unrestricted ingress access to port 22 .
introduction: |-
This module creates an [EKS Managed Node Group](https://docs.aws.amazon.com/eks/latest/userguide/managed-node-groups.html)
for an [EKS](https://aws.amazon.com/eks/) cluster.
It assumes you have already created an EKS cluster, but you can create the cluster and the node group in the
same Terraform configuration. See our
[full-featured root module (a.k.a. component) `eks/cluster`](https://github.com/cloudposse/terraform-aws-components/tree/main/modules/eks/cluster)
for an example of how to do that.
### Launch Templates
This module always uses a [launch template](https://docs.aws.amazon.com/autoscaling/ec2/userguide/launch-templates.html)
to create the node group. You can create your own launch template and
pass in its ID, or else this module will create one for you.
The AWS default for EKS is that if the launch template is updated, the existing nodes will not be affected. Only
new instances added to the node group would get the changes specified in the new launch template. In contrast,
when the launch template changes, this module can immediately create a new node group from the new launch template
to replace the old one.
See the inputs `create_before_destroy` and `immediately_apply_lt_changes` for details about how to control this behavior.
### Operating system differences
Currently, EKS supports 4 Operating Systems: Amazon Linux 2, Amazon Linux 2023, Bottlerocket, and Windows Server.
This module supports all 4 OSes, but support for detailed configuration of the nodes varies by OS. The 4 inputs:
1. `before_cluster_joining_userdata`
2. `kubelet_additional_options`
3. `bootstrap_additional_options`
4. `after_cluster_joining_userdata`
are fully supported for Amazon Linux 2 and Windows, and take advantage of the [bootstrap.sh](https://github.com/awslabs/amazon-eks-ami/blob/main/templates/al2/runtime/bootstrap.sh)
supplied on those AMIs. **NONE** of these inputs are supported on Bottlerocket. On AL2023, only the first 2 are supported.
Note that for all OSes, you can supply the complete `userdata` contents, which will be untouched by this module, via `userdata_override_base64`.
**IMPORTANT:** This module provisions an `EKS Node Group` nodes globally accessible by SSH (22) port. Normally, AWS recommends that no security group allows unrestricted ingress access to port 22 .
introduction: ""
# How to use this project
usage: |2-
usage: |-
### Major Changes (breaking and otherwise)
With the v3.0.0 release of this module, support for Amazon Linux 2023 (AL2023) has
been added, and some breaking changes have been made. Please see the
[release notes](https://github.com/cloudposse/terraform-aws-eks-node-group/releases/tag/3.0.0)
for details.
With the v2.0.0 (a.k.a. v0.25.0) release of this module, it has undergone major breaking
changes and added new features. Please see the [migration](docs/migration-v1-v2.md)
document for details.
Expand All @@ -83,13 +124,6 @@ usage: |2-
For automated tests of the complete example using [bats](https://github.com/bats-core/bats-core) and [Terratest](https://github.com/gruntwork-io/terratest) (which tests and deploys the example on AWS),
see [test](test).
### Terraform Version
Terraform version 1.0 is out. Before that, there was Terraform version 0.15, 0.14, 0.13 and so on.
The v2.0.0 release of this module drops support for Terraform 0.13. That version is old and has lots of known issues.
There are hardly any breaking changes between Terraform 0.13 and 1.0, so please upgrade to
the latest Terraform version before raising any issues about this module.
### Sources of Information
- The code examples below are manually updated and have a tendency to fall out of sync with actual code,
Expand Down Expand Up @@ -159,7 +193,7 @@ usage: |2-
module "eks_cluster" {
source = "cloudposse/eks-cluster/aws"
# Cloud Posse recommends pinning every module to a specific version
# version = "2.x.x"
# version = "4.x.x"
vpc_id = module.vpc.vpc_id
subnet_ids = module.subnets.public_subnet_ids
Expand All @@ -173,7 +207,7 @@ usage: |2-
module "eks_node_group" {
source = "cloudposse/eks-node-group/aws"
# Cloud Posse recommends pinning every module to a specific version
# version = "2.x.x"
# version = "3.x.x"
instance_types = [var.instance_type]
subnet_ids = module.subnets.public_subnet_ids
Expand Down
172 changes: 104 additions & 68 deletions ami.tf
Original file line number Diff line number Diff line change
@@ -1,80 +1,116 @@

# Previously, we found AMIs by using the aws_ami data source with a name_regex filter
# and `most_recent = true`. Unfortunately, `most_recent` means most recently created,
# and may not be the most recent Kubernetes version if, for example, a previous version
# had a new `eksbuild`. So instead, we now use the AMI IDs published in SSM.
# See https://docs.aws.amazon.com/eks/latest/userguide/retrieve-ami-id.html
# https://docs.aws.amazon.com/eks/latest/userguide/retrieve-ami-id-bottlerocket.html

# Amazon Linux: https://docs.aws.amazon.com/eks/latest/userguide/retrieve-ami-id.html
# aws ssm get-parameter --name /aws/service/eks/optimized-ami/1.30/amazon-linux-2/recommended/image_id \
# --query "Parameter.Value" --output text
# Bottlerocket https://github.com/bottlerocket-os/bottlerocket/blob/develop/QUICKSTART-EKS.md#finding-an-ami
# aws ssm get-parameter --name /aws/service/bottlerocket/aws-k8s-1.30/x86_64/latest/image_id \
# --query "Parameter.Value" --output text
# Windows: https://docs.aws.amazon.com/eks/latest/userguide/retrieve-windows-ami-id.html
# aws ssm get-parameter --name /aws/service/ami-windows-latest/Windows_Server-2019-English-Core-EKS_Optimized-1.30/image_id \
# --region region-code --query "Parameter.Value" --output text


locals {
# "amazon-eks-gpu-node-",
arch_label_map = {
AL2_x86_64 = "",
AL2_x86_64_GPU = "-gpu",
AL2_ARM_64 = "-arm64",
BOTTLEROCKET_x86_64 = "x86_64",
BOTTLEROCKET_ARM_64 = "aarch64"
BOTTLEROCKET_ARM_64_NVIDIA = "-gpu"
BOTTLEROCKET_x86_64_NVIDIA = "-gpu"
WINDOWS_CORE_2019_x86_64 = ""
WINDOWS_FULL_2019_x86_64 = ""
WINDOWS_CORE_2022_x86_64 = ""
WINDOWS_FULL_2022_x86_64 = ""
given_ami_id = length(var.ami_image_id) > 0

# Public SSM parameters all start with /aws/service/

ami_os = split("_", var.ami_type)[0]

# format string that makes
# format(fmt, specifier, k8s_version) the SSM parameter name to retrieve

ami_ssm_format = {
AL2_x86_64 = "/aws/service/eks/optimized-ami/%[2]v/amazon-linux-2/%[1]v/image_id"
AL2_x86_64_GPU = "/aws/service/eks/optimized-ami/%[2]v/amazon-linux-2-gpu/%[1]v/image_id"
AL2_ARM_64 = "/aws/service/eks/optimized-ami/%[2]v/amazon-linux-2-arm64/%[1]v/image_id"
AL2023_x86_64_STANDARD = "/aws/service/eks/optimized-ami/%[2]v/amazon-linux-2023/x86_64/standard/%[1]v/image_id"
AL2023_ARM_64_STANDARD = "/aws/service/eks/optimized-ami/%[2]v/amazon-linux-2023/arm64/standard/%[1]v/image_id"
BOTTLEROCKET_x86_64 = "/aws/service/bottlerocket/aws-k8s-%[2]v/x86_64/%[1]v/image_id"
BOTTLEROCKET_ARM_64 = "/aws/service/bottlerocket/aws-k8s-%[2]v/arm64/%[1]v/image_id"
BOTTLEROCKET_x86_64_NVIDIA = "/aws/service/bottlerocket/aws-k8s-%[2]v-nvidia/x86_64/%[1]v/image_id"
BOTTLEROCKET_ARM_64_NVIDIA = "/aws/service/bottlerocket/aws-k8s-%[2]v-nvidia/arm64/%[1]v/image_id"
WINDOWS_CORE_2019_x86_64 = "/aws/service/ami-windows-latest/Windows_Server-2019-English-Core-EKS_Optimized-%[2]v/image_id"
WINDOWS_FULL_2019_x86_64 = "/aws/service/ami-windows-latest/Windows_Server-2019-English-Full-EKS_Optimized-%[2]v/image_id"
WINDOWS_CORE_2022_x86_64 = "/aws/service/ami-windows-latest/Windows_Server-2022-English-Core-EKS_Optimized-%[2]v/image_id"
WINDOWS_FULL_2022_x86_64 = "/aws/service/ami-windows-latest/Windows_Server-2022-English-Full-EKS_Optimized-%[2]v/image_id"
}

release_version_parts = concat(split("-", try(var.ami_release_version[0], "")), ["", ""])
amazon_linux_ami_name_release_part = try(join(".", slice(split(".", local.release_version_parts[0]), 0, 2)), "")
# AMI Public SSM Parameter specifiers?
# Release versions for AL2 and AL2023 are from https://github.com/awslabs/amazon-eks-ami/releases
# Amazon Linux Release Version: 1.29.0-20240213
# AL2
# AMI name: amazon-eks-node-1.29-v20240117
# AMI SSM param: /aws/service/eks/optimized-ami/1.29/amazon-linux-2/amazon-eks-node-1.29-v20240117/image_id
# AL2023
# AMI name: amazon-eks-node-al2023-x86_64-standard-1.29-v20240213
# AMI SSM param: /aws/service/eks/optimized-ami/1.29/amazon-linux-2023/x86_64/standard/amazon-eks-node-al2023-x86_64-standard-1.29-v20240213/image_id
# Specifiers for Bottlerocket are the bare release version (e.g. `1.18.0`) or
# the release version and the first 8 characters of the commit hash (e.g. `1.18.0-7452c37e`). NOTE: GitHub commit hash abbreviations are only 7 characters.
# From:
# Bottlerocket:
# AMI name: bottlerocket-aws-k8s-1.29-nvidia-x86_64-v1.18.0-7452c37e
# AMI SSM param: /aws/service/bottlerocket/aws-k8s-1.26-nvidia/x86_64/1.18.0/image_id # No "v"
# /aws/service/bottlerocket/aws-k8s-1.26-nvidia/x86_64/1.18.0-7452c37e/image_id
# Windows does not allow a specifier for SSM parameters, they only have the latest AMI ID
ami_specifier_amazon_linux = {
AL2_x86_64 = format("amazon-eks-node-%v-v%v", local.amazon_linux_ami_name_release_part, local.release_version_parts[1])
AL2_x86_64_GPU = format("amazon-eks-gpu-node-%v-v%v", local.amazon_linux_ami_name_release_part, local.release_version_parts[1])
AL2_ARM_64 = format("amazon-eks-arm64-node-%v-v%v", local.amazon_linux_ami_name_release_part, local.release_version_parts[1])
AL2023_x86_64_STANDARD = format("amazon-eks-node-al2023-x86_64-standard-%v-v%v", local.amazon_linux_ami_name_release_part, local.release_version_parts[1])
AL2023_ARM_64_STANDARD = format("amazon-eks-node-al2023-arm64-standard-%v-v%v", local.amazon_linux_ami_name_release_part, local.release_version_parts[1])
}

ami_kind = split("_", var.ami_type)[0] != "WINDOWS" ? split("_", var.ami_type)[0] : format("WINDOWS_%s_%s", split("_", var.ami_type)[1], split("_", var.ami_type)[2])

ami_format = {
# amazon-eks{arch_label}-node-{ami_kubernetes_version}-v{ami_version}
# e.g. amazon-eks-arm64-node-1.21-v20211013
AL2 = "amazon-eks%s-node-%s"
# bottlerocket-aws-k8s-{ami_kubernetes_version}-{arch_label}-v{ami_version}
# e.g. bottlerocket-aws-k8s-1.21-x86_64-v1[2].0-ccf1b754
BOTTLEROCKET = "bottlerocket-aws-k8s-%s-%s-%s"
# Windows_Server-2019-English-Core-EKS_Optimized-{ami_kubernetes_version}-{ami_version}
# e.g. Windows_Server-2019-English-Core-EKS_Optimized-1.23-2022.11.08
WINDOWS_CORE_2019 = "Windows_Server-2019-English-Core-EKS_Optimized-%s-%s"
WINDOWS_FULL_2019 = "Windows_Server-2019-English-Full-EKS_Optimized-%s-%s"
WINDOWS_CORE_2022 = "Windows_Server-2022-English-Core-EKS_Optimized-%s-%s"
WINDOWS_FULL_2022 = "Windows_Server-2022-English-Full-EKS_Optimized-%s-%s"
ami_specifier = length(var.ami_release_version) == 0 ? (local.ami_os == "BOTTLEROCKET" ? "latest" : "recommended") : (
lookup(local.ami_specifier_amazon_linux, var.ami_type, var.ami_release_version[0])
)

# As usual, Windows is difficult.
is_window_version = local.ami_os == "WINDOWS" && local.ami_specifier != "recommended"

windows_name_base = {
WINDOWS_CORE_2019_x86_64 = "Windows_Server-2019-English-Core-EKS_Optimized"
WINDOWS_FULL_2019_x86_64 = "Windows_Server-2019-English-Full-EKS_Optimized"
WINDOWS_CORE_2022_x86_64 = "Windows_Server-2022-English-Core-EKS_Optimized"
WINDOWS_FULL_2022_x86_64 = "Windows_Server-2022-English-Full-EKS_Optimized"
}

# Kubernetes version priority (first one to be set wins)
# 1. prefix of var.ami_release_version
# 2. var.kubernetes_version
# 3. data.eks_cluster.this.kubernetes_version
need_cluster_kubernetes_version = local.enabled ? local.need_ami_id && length(var.kubernetes_version) == 0 : false

use_cluster_kubernetes_version = local.need_cluster_kubernetes_version && (local.ami_kind == "BOTTLEROCKET" || length(var.ami_release_version) == 0)

ami_kubernetes_version = local.need_ami_id ? (local.use_cluster_kubernetes_version ? data.aws_eks_cluster.this[0].version :
regex("^(\\d+\\.\\d+)", coalesce(local.ami_kind == "AL2" ? try(var.ami_release_version[0], null) : null, try(var.kubernetes_version[0], null)))[0]
) : ""

# if ami_release_version is provided
ami_version_regex = local.need_ami_id ? {
# if ami_release_version = "1.21-20211013"
# insert the letter v prior to the ami_version so it becomes 1.21-v20211013
# if not, use the kubernetes version
AL2 = (length(var.ami_release_version) == 1 ? replace(var.ami_release_version[0], "/^(\\d+\\.\\d+)\\.\\d+-(\\d+)$/", "$1-v$2") : "${local.ami_kubernetes_version}-*"),
# if ami_release_version = "1.2.0-ccf1b754"
# prefix the ami release version with the letter v
# if not, use an asterisk
BOTTLEROCKET = (length(var.ami_release_version) == 1 ? format("v%s", var.ami_release_version[0]) : "*"),
WINDOWS_CORE_2019 = (length(var.ami_release_version) == 1 ? format("%s", var.ami_release_version[0]) : "*"),
WINDOWS_FULL_2019 = (length(var.ami_release_version) == 1 ? format("%s", var.ami_release_version[0]) : "*"),
WINDOWS_CORE_2022 = (length(var.ami_release_version) == 1 ? format("%s", var.ami_release_version[0]) : "*"),
WINDOWS_FULL_2022 = (length(var.ami_release_version) == 1 ? format("%s", var.ami_release_version[0]) : "*"),
} : {}

ami_regex = local.need_ami_id ? {
AL2 = format(local.ami_format["AL2"], local.arch_label_map[var.ami_type], local.ami_version_regex[local.ami_kind]),
BOTTLEROCKET = format(local.ami_format["BOTTLEROCKET"], local.ami_kubernetes_version, local.arch_label_map[var.ami_type], local.ami_version_regex[local.ami_kind]),
WINDOWS_CORE_2019 = format(local.ami_format["WINDOWS_CORE_2019"], local.ami_kubernetes_version, local.ami_version_regex[local.ami_kind]),
WINDOWS_FULL_2019 = format(local.ami_format["WINDOWS_FULL_2019"], local.ami_kubernetes_version, local.ami_version_regex[local.ami_kind]),
WINDOWS_CORE_2022 = format(local.ami_format["WINDOWS_CORE_2022"], local.ami_kubernetes_version, local.ami_version_regex[local.ami_kind]),
WINDOWS_FULL_2022 = format(local.ami_format["WINDOWS_FULL_2022"], local.ami_kubernetes_version, local.ami_version_regex[local.ami_kind]),
} : {}
# We do not really need to compute all the names, but it makes debugging easier if we do.
ami_name_windows = { for k, v in local.windows_name_base : k => format("%s-%s", v, try(var.ami_release_version[0], "")) }

fetched_ami_id = try(local.is_window_version ? data.aws_ami.windows_ami[0].image_id : data.aws_ssm_parameter.ami_id[0].insecure_value, "")
ami_id = local.given_ami_id ? var.ami_image_id[0] : local.fetched_ami_id
}

data "aws_ami" "selected" {
count = local.enabled && local.need_ami_id ? 1 : 0
data "aws_ssm_parameter" "ami_id" {
count = local.need_to_get_ami_id && !local.is_window_version ? 1 : 0

most_recent = true
name_regex = local.ami_regex[local.ami_kind]
name = format(local.ami_ssm_format[var.ami_type], local.ami_specifier, local.resolved_kubernetes_version)

lifecycle {
precondition {
condition = var.ami_type != "CUSTOM"
error_message = "The AMI ID must be supplied when AMI type is \"CUSTOM\"."
}
}
}

data "aws_ami" "windows_ami" {
count = local.need_to_get_ami_id && local.is_window_version ? 1 : 0

owners = ["amazon"]
filter {
name = "name"
values = [local.ami_name_windows[var.ami_type]]
}
}

Loading

0 comments on commit e9f908c

Please sign in to comment.