Skip to content
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
136 changes: 136 additions & 0 deletions _user_data/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# The `cluster_service_cidr` is required when `create == true`
# This is a hacky way to make that logic work, otherwise Terraform always wants a value
# and supplying any old value like `""` or `null` is not valid and will silently
# fail to join nodes to the cluster
resource "null_resource" "validate_cluster_service_cidr" {
lifecycle {
precondition {
# The length 6 is currently arbitrary, but it's a safe bet that the CIDR will be longer than that
# The main point is that a value needs to be provided when `create = true`
condition = var.create ? length(var.cluster_service_cidr) > 6 : true
error_message = "`cluster_service_cidr` is required when `create = true`."
}
}
}

locals {
is_al2 = startswith(var.ami_type, "AL2_")
is_al2023 = startswith(var.ami_type, "AL2023_")

# Converts AMI type into user data template path
ami_type_to_user_data_path = {
AL2_ARM_64 = "${path.module}/templates/al2_user_data.tpl"
AL2_x86_64 = "${path.module}/templates/al2_user_data.tpl"
AL2_x86_64_GPU = "${path.module}/templates/al2_user_data.tpl"

AL2023_x86_64_STANDARD = "${path.module}/templates/al2023_user_data.tpl"
AL2023_ARM_64_STANDARD = "${path.module}/templates/al2023_user_data.tpl"
AL2023_x86_64_NEURON = "${path.module}/templates/al2023_user_data.tpl"
AL2023_x86_64_NVIDIA = "${path.module}/templates/al2023_user_data.tpl"
AL2023_ARM_64_NVIDIA = "${path.module}/templates/al2023_user_data.tpl"

BOTTLEROCKET_ARM_64 = "${path.module}/templates/bottlerocket_user_data.tpl"
BOTTLEROCKET_x86_64 = "${path.module}/templates/bottlerocket_user_data.tpl"
BOTTLEROCKET_ARM_64_FIPS = "${path.module}/templates/bottlerocket_user_data.tpl"
BOTTLEROCKET_x86_64_FIPS = "${path.module}/templates/bottlerocket_user_data.tpl"
BOTTLEROCKET_ARM_64_NVIDIA = "${path.module}/templates/bottlerocket_user_data.tpl"
BOTTLEROCKET_x86_64_NVIDIA = "${path.module}/templates/bottlerocket_user_data.tpl"

WINDOWS_CORE_2019_x86_64 = "${path.module}/templates/windows_user_data.tpl"
WINDOWS_FULL_2019_x86_64 = "${path.module}/templates/windows_user_data.tpl"
WINDOWS_CORE_2022_x86_64 = "${path.module}/templates/windows_user_data.tpl"
WINDOWS_FULL_2022_x86_64 = "${path.module}/templates/windows_user_data.tpl"

CUSTOM = var.user_data_template_path
}
user_data_path = coalesce(var.user_data_template_path, local.ami_type_to_user_data_path[var.ami_type])

cluster_dns_ips = flatten(concat([try(cidrhost(var.cluster_service_cidr, 10), "")], var.additional_cluster_dns_ips))

user_data = var.create ? base64encode(templatefile(local.user_data_path,
{
# https://docs.aws.amazon.com/eks/latest/userguide/launch-templates.html#launch-template-custom-ami
enable_bootstrap_user_data = var.enable_bootstrap_user_data

# Required to bootstrap node
cluster_name = var.cluster_name
cluster_endpoint = var.cluster_endpoint
cluster_auth_base64 = var.cluster_auth_base64

cluster_service_cidr = var.cluster_service_cidr
cluster_ip_family = var.cluster_ip_family

# Bottlerocket
cluster_dns_ips = "[${join(", ", formatlist("\"%s\"", local.cluster_dns_ips))}]"

# Optional
bootstrap_extra_args = var.bootstrap_extra_args
pre_bootstrap_user_data = var.pre_bootstrap_user_data
post_bootstrap_user_data = var.post_bootstrap_user_data
}
)) : ""

user_data_type_to_rendered = try(coalesce(
local.is_al2 ? try(data.cloudinit_config.al2_eks_managed_node_group[0].rendered, local.user_data) : null,
local.is_al2023 ? try(data.cloudinit_config.al2023_eks_managed_node_group[0].rendered, local.user_data) : null,
local.user_data,
), "")
}

# https://github.com/aws/containers-roadmap/issues/596#issuecomment-675097667
# Managed node group data must in MIME multi-part archive format,
# as by default, EKS will merge the bootstrapping command required for nodes to join the
# cluster with your user data. If you use a custom AMI in your launch template,
# this merging will NOT happen and you are responsible for nodes joining the cluster.
# See docs for more details -> https://docs.aws.amazon.com/eks/latest/userguide/launch-templates.html#launch-template-user-data

data "cloudinit_config" "al2_eks_managed_node_group" {
count = var.create && local.is_al2 && var.is_eks_managed_node_group && !var.enable_bootstrap_user_data && var.pre_bootstrap_user_data != "" && var.user_data_template_path == "" ? 1 : 0

base64_encode = true
gzip = false
boundary = "//"

# Prepend to existing user data supplied by AWS EKS
part {
content = var.pre_bootstrap_user_data
content_type = "text/x-shellscript"
}
}

# Scenarios:
#
# 1. Do nothing - provide nothing
# 2. Prepend stuff on EKS MNG (before EKS MNG adds its bit at the end)
# 3. Own all of the stuff on self-MNG or EKS MNG w/ custom AMI

locals {
nodeadm_cloudinit = var.enable_bootstrap_user_data ? concat(
var.cloudinit_pre_nodeadm,
[{
content_type = "application/node.eks.aws"
content = base64decode(local.user_data)
}],
var.cloudinit_post_nodeadm
) : var.cloudinit_pre_nodeadm
}

data "cloudinit_config" "al2023_eks_managed_node_group" {
count = var.create && local.is_al2023 && length(local.nodeadm_cloudinit) > 0 ? 1 : 0

base64_encode = true
gzip = false
boundary = "MIMEBOUNDARY"

dynamic "part" {
# Using the index is fine in this context since any change in user data will be a replacement
for_each = { for i, v in local.nodeadm_cloudinit : i => v }

content {
content = part.value.content
content_type = try(part.value.content_type, null)
filename = try(part.value.filename, null)
merge_type = try(part.value.merge_type, null)
}
}
}
4 changes: 4 additions & 0 deletions _user_data/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
output "user_data" {
description = "Base64 encoded user data rendered for the provided inputs"
value = local.user_data_type_to_rendered
}
11 changes: 11 additions & 0 deletions _user_data/templates/al2023_user_data.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
%{ if enable_bootstrap_user_data ~}
---
apiVersion: node.eks.aws/v1alpha1
kind: NodeConfig
spec:
cluster:
name: ${cluster_name}
apiServerEndpoint: ${cluster_endpoint}
certificateAuthority: ${cluster_auth_base64}
cidr: ${cluster_service_cidr}
%{ endif ~}
12 changes: 12 additions & 0 deletions _user_data/templates/al2_user_data.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
%{ if enable_bootstrap_user_data ~}
#!/bin/bash
set -e
%{ endif ~}
${pre_bootstrap_user_data ~}
%{ if enable_bootstrap_user_data ~}
B64_CLUSTER_CA=${cluster_auth_base64}
API_SERVER_URL=${cluster_endpoint}
/etc/eks/bootstrap.sh ${cluster_name} ${bootstrap_extra_args} --b64-cluster-ca $B64_CLUSTER_CA --apiserver-endpoint $API_SERVER_URL \
--ip-family ${cluster_ip_family} --service-${cluster_ip_family}-cidr ${cluster_service_cidr}
${post_bootstrap_user_data ~}
%{ endif ~}
8 changes: 8 additions & 0 deletions _user_data/templates/bottlerocket_user_data.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
%{ if enable_bootstrap_user_data ~}
[settings.kubernetes]
"cluster-name" = "${cluster_name}"
"api-server" = "${cluster_endpoint}"
"cluster-certificate" = "${cluster_auth_base64}"
"cluster-dns-ip" = ${cluster_dns_ips}
%{ endif ~}
${bootstrap_extra_args ~}
13 changes: 13 additions & 0 deletions _user_data/templates/windows_user_data.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
%{ if enable_bootstrap_user_data ~}
<powershell>
%{ endif ~}
${pre_bootstrap_user_data ~}
%{ if enable_bootstrap_user_data ~}
[string]$EKSBinDir = "$env:ProgramFiles\Amazon\EKS"
[string]$EKSBootstrapScriptName = 'Start-EKSBootstrap.ps1'
[string]$EKSBootstrapScriptFile = "$EKSBinDir\$EKSBootstrapScriptName"
& $EKSBootstrapScriptFile -EKSClusterName ${cluster_name} -APIServerEndpoint ${cluster_endpoint} -Base64ClusterCA ${cluster_auth_base64} ${bootstrap_extra_args} 3>&1 4>&1 5>&1 6>&1
$LastError = if ($?) { 0 } else { $Error[0].Exception.HResult }
${post_bootstrap_user_data ~}
</powershell>
%{ endif ~}
121 changes: 121 additions & 0 deletions _user_data/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
variable "create" {
description = "Determines whether to create user-data or not"
type = bool
default = true
nullable = false
}

variable "ami_type" {
description = "Type of Amazon Machine Image (AMI) associated with the EKS Node Group. See the [AWS documentation](https://docs.aws.amazon.com/eks/latest/APIReference/API_Nodegroup.html#AmazonEKS-Type-Nodegroup-amiType) for valid values"
type = string
default = "AL2023_x86_64_STANDARD"
nullable = false
}

variable "enable_bootstrap_user_data" {
description = "Determines whether the bootstrap configurations are populated within the user data template"
type = bool
default = false
nullable = false
}

variable "is_eks_managed_node_group" {
description = "Determines whether the user data is used on nodes in an EKS managed node group. Used to determine if user data will be appended or not"
type = bool
default = true
nullable = false
}

variable "cluster_name" {
description = "Name of the EKS cluster"
type = string
default = ""
nullable = false
}

variable "cluster_endpoint" {
description = "Endpoint of associated EKS cluster"
type = string
default = ""
nullable = false
}

variable "cluster_auth_base64" {
description = "Base64 encoded CA of associated EKS cluster"
type = string
default = ""
nullable = false
}

variable "cluster_service_cidr" {
description = "The CIDR block (IPv4 or IPv6) used by the cluster to assign Kubernetes service IP addresses. This is derived from the cluster itself"
type = string
default = ""
nullable = false
}

variable "cluster_ip_family" {
description = "The IP family used to assign Kubernetes pod and service addresses. Valid values are `ipv4` (default) and `ipv6`"
type = string
default = "ipv4"
nullable = false
}

variable "additional_cluster_dns_ips" {
description = "Additional DNS IP addresses to use for the cluster. Only used when `ami_type` = `BOTTLEROCKET_*`"
type = list(string)
default = []
nullable = false
}

variable "pre_bootstrap_user_data" {
description = "User data that is injected into the user data script ahead of the EKS bootstrap script. Not used when `ami_type` = `BOTTLEROCKET_*`"
type = string
default = ""
nullable = false
}

variable "post_bootstrap_user_data" {
description = "User data that is appended to the user data script after of the EKS bootstrap script. Not used when `ami_type` = `BOTTLEROCKET_*`"
type = string
default = ""
nullable = false
}

variable "bootstrap_extra_args" {
description = "Additional arguments passed to the bootstrap script. When `ami_type` = `BOTTLEROCKET_*`; these are additional [settings](https://github.com/bottlerocket-os/bottlerocket#settings) that are provided to the Bottlerocket user data"
type = string
default = ""
nullable = false
}

variable "user_data_template_path" {
description = "Path to a local, custom user data template file to use when rendering user data"
type = string
default = ""
nullable = false
}

variable "cloudinit_pre_nodeadm" {
description = "Array of cloud-init document parts that are created before the nodeadm document part"
type = list(object({
content = string
content_type = optional(string)
filename = optional(string)
merge_type = optional(string)
}))
default = []
nullable = false
}

variable "cloudinit_post_nodeadm" {
description = "Array of cloud-init document parts that are created after the nodeadm document part"
type = list(object({
content = string
content_type = optional(string)
filename = optional(string)
merge_type = optional(string)
}))
default = []
nullable = false
}
14 changes: 14 additions & 0 deletions _user_data/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
terraform {
required_version = ">= 1.5.7"

required_providers {
cloudinit = {
source = "hashicorp/cloudinit"
version = ">= 2.0"
}
null = {
source = "hashicorp/null"
version = ">= 3.0"
}
}
}
3 changes: 1 addition & 2 deletions alb.tf
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
module "iam_assumable_role_alb" {
source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc"
version = "6.2.1"
source = "./iam-assumable-role-with-oidc"
create_role = true
role_name = "${var.environment_name}-${local.alb_name}"
provider_url = replace(aws_iam_openid_connect_provider.cluster.url, "https://", "")
Expand Down
3 changes: 1 addition & 2 deletions efs.tf
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# https://docs.aws.amazon.com/eks/latest/userguide/efs-csi.html
module "iam_assumable_role_efs_csi" {
count = var.efs_enabled ? 1 : 0
source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc"
version = "6.2.1"
source = "./iam-assumable-role-with-oidc"
create_role = true
role_name = "${var.environment_name}-AmazonEFSCSIDriverPolicy"
provider_url = replace(aws_iam_openid_connect_provider.cluster.url, "https://", "")
Expand Down
Loading