modules/terraform/aws/eks/main.tf (303 lines of code) (raw):

locals { role = var.eks_config.role eks_cluster_name = "${var.eks_config.eks_name}-${var.run_id}" eks_node_group_map = { for node_group in var.eks_config.eks_managed_node_groups : node_group.name => node_group } eks_config_addons_map = { for addon in var.eks_config.eks_addons : addon.name => addon } eks_nodes_subnets_list = flatten([for node_group in var.eks_config.eks_managed_node_groups : node_group.subnet_names if node_group.subnet_names != null]) karpenter_addons_map = { for addon in [ { name = "vpc-cni", vpc_cni_warm_prefix_target = 1 }, { name = "kube-proxy" }, { name = "coredns" } ] : addon.name => addon if var.eks_config.enable_karpenter } _eks_addons_map = merge(local.karpenter_addons_map, local.eks_config_addons_map) # Set default VPC-CNI settings if addon is present in the config vpc_cni_addon_map = contains(keys(local._eks_addons_map), "vpc-cni") ? { "vpc-cni" = { name = "vpc-cni", policy_arns = ["AmazonEKS_CNI_Policy"], before_compute = true, # ensure the vpc-cni is created and updated before any EC2 instances are created. configuration_values = { env = { # Enable IPv4 prefix delegation to increase the number of available IP addresses on the provisioned EC2 nodes. # This significantly increases number of pods that can be run per node. (see: https://aws.amazon.com/blogs/containers/amazon-vpc-cni-increases-pods-per-node-limits/) # Nodes must be AWS Nitro-based (see: https://docs.aws.amazon.com/ec2/latest/instancetypes/ec2-nitro-instances.html#nitro-instance-types) # Note: we've seen that it also prevents ENIs leak caused the issue: https://github.com/aws/amazon-vpc-cni-k8s/issues/608 ENABLE_PREFIX_DELEGATION = "true" # Should set either WARM_PREFIX_TARGET or both MINIMUM_IP_TARGET and WARM_IP_TARGET (see: https://github.com/aws/amazon-vpc-cni-k8s/blob/master/docs/prefix-and-ip-target.md) WARM_PREFIX_TARGET = tostring(local._eks_addons_map["vpc-cni"].vpc_cni_warm_prefix_target) ADDITIONAL_ENI_TAGS = jsonencode(var.tags) } } } } : {} eks_addons_map = merge(local._eks_addons_map, local.vpc_cni_addon_map) # note: the order matters (the later takes precedence) policy_arns = var.eks_config.policy_arns addons_policy_arns = flatten([for addon in local.eks_addons_map : addon.policy_arns if can(addon.policy_arns)]) service_account_map = { for addon in local.eks_addons_map : addon.name => addon.service_account if try(addon.service_account, null) != null } } data "aws_subnets" "subnets" { filter { name = "tag:run_id" values = [var.run_id] } filter { name = "vpc-id" values = [var.vpc_id] } } data "aws_subnet" "subnet_details" { for_each = toset(local.eks_nodes_subnets_list) filter { name = "tag:run_id" values = [var.run_id] } filter { name = "tag:Name" values = [each.value] } } data "aws_iam_policy_document" "assume_role" { statement { effect = "Allow" principals { type = "Service" identifiers = ["eks.amazonaws.com", "ec2.amazonaws.com"] } actions = ["sts:AssumeRole"] } } data "aws_iam_policy_document" "cw_put_metrics" { count = var.eks_config.enable_cni_metrics_helper ? 1 : 0 statement { sid = "VisualEditor0" actions = ["cloudwatch:PutMetricData"] resources = ["*"] } } resource "aws_iam_policy" "cw_policy" { count = var.eks_config.enable_cni_metrics_helper ? 1 : 0 name = "cw-policy-${local.eks_cluster_name}" description = "Grants permission to write metrics to CloudWatch" policy = data.aws_iam_policy_document.cw_put_metrics[0].json } resource "aws_iam_role" "eks_cluster_role" { assume_role_policy = data.aws_iam_policy_document.assume_role.json } resource "aws_iam_role_policy_attachment" "policy_attachments" { for_each = toset(local.policy_arns) policy_arn = "arn:aws:iam::aws:policy/${each.value}" role = aws_iam_role.eks_cluster_role.name } resource "aws_iam_role_policy_attachment" "cw_policy_attachment" { count = var.eks_config.enable_cni_metrics_helper ? 1 : 0 policy_arn = aws_iam_policy.cw_policy[0].arn role = aws_iam_role.eks_cluster_role.name } # Create EKS Cluster resource "aws_eks_cluster" "eks" { name = local.eks_cluster_name role_arn = aws_iam_role.eks_cluster_role.arn vpc_config { subnet_ids = toset(data.aws_subnets.subnets.ids) } access_config { authentication_mode = "API_AND_CONFIG_MAP" bootstrap_cluster_creator_admin_permissions = true } version = var.eks_config.kubernetes_version depends_on = [ aws_iam_role_policy_attachment.policy_attachments ] tags = { "role" = local.role } } # Create OIDC Provider data "tls_certificate" "eks" { url = aws_eks_cluster.eks.identity[0].oidc[0].issuer } resource "aws_iam_openid_connect_provider" "oidc_provider" { client_id_list = ["sts.amazonaws.com"] thumbprint_list = [data.tls_certificate.eks.certificates[0].sha1_fingerprint] url = aws_eks_cluster.eks.identity[0].oidc[0].issuer tags = var.tags depends_on = [data.tls_certificate.eks] } resource "aws_ec2_tag" "cluster_security_group" { for_each = var.tags resource_id = aws_eks_cluster.eks.vpc_config[0].cluster_security_group_id key = each.key value = each.value } resource "aws_launch_template" "launch_template" { for_each = local.eks_node_group_map name = "${local.eks_cluster_name}-${each.value.name}" tag_specifications { resource_type = "instance" tags = var.tags } user_data = var.user_data_path != "" ? filebase64("${var.user_data_path}/${local.role}-userdata.sh") : null network_interfaces { dynamic "ena_srd_specification" { for_each = var.ena_express != null || each.value.ena_express != null ? { "ena_express" : each.value.ena_express } : {} content { ena_srd_enabled = var.ena_express != null ? var.ena_express : each.value.ena_express ena_srd_udp_specification { ena_srd_udp_enabled = var.ena_express != null ? var.ena_express : each.value.ena_express } } } } dynamic "block_device_mappings" { for_each = each.value.block_device_mappings content { device_name = try(block_device_mappings.value.device_name, null) dynamic "ebs" { for_each = try([block_device_mappings.value.ebs], []) content { delete_on_termination = try(ebs.value.delete_on_termination, null) iops = try(ebs.value.iops, null) throughput = try(ebs.value.throughput, null) volume_size = try(ebs.value.volume_size, null) volume_type = try(ebs.value.volume_type, null) } } } } tags = var.tags } resource "aws_eks_node_group" "eks_managed_node_groups" { for_each = local.eks_node_group_map node_group_name = each.value.name cluster_name = aws_eks_cluster.eks.name node_role_arn = aws_iam_role.eks_cluster_role.arn subnet_ids = each.value.subnet_names != null ? toset([for subnet_name in each.value.subnet_names : data.aws_subnet.subnet_details[subnet_name].id]) : toset(data.aws_subnets.subnets.ids) scaling_config { min_size = each.value.min_size max_size = each.value.max_size desired_size = each.value.desired_size } dynamic "taint" { for_each = each.value.taints content { key = taint.value["key"] value = taint.value["value"] effect = taint.value["effect"] } } ami_type = each.value.ami_type instance_types = var.k8s_machine_type != null ? [var.k8s_machine_type] : each.value.instance_types capacity_type = each.value.capacity_type labels = each.value.labels launch_template { id = aws_launch_template.launch_template[each.key].id version = aws_launch_template.launch_template[each.key].latest_version } tags = { "Name" = each.value.name } depends_on = [ aws_eks_cluster.eks, aws_iam_role_policy_attachment.policy_attachments, aws_eks_addon.before_compute ] } module "karpenter" { count = var.eks_config.enable_karpenter ? 1 : 0 source = "./karpenter" cluster_name = aws_eks_cluster.eks.name region = var.region tags = var.tags cluster_iam_role_name = aws_iam_role.eks_cluster_role.name depends_on = [aws_eks_node_group.eks_managed_node_groups] } module "cluster_autoscaler" { count = var.eks_config.enable_cluster_autoscaler ? 1 : 0 source = "./cluster-autoscaler" cluster_name = aws_eks_cluster.eks.name region = var.region tags = var.tags cluster_iam_role_name = aws_iam_role.eks_cluster_role.name cluster_version = var.eks_config.kubernetes_version auto_scaler_profile = var.eks_config.auto_scaler_profile depends_on = [aws_eks_node_group.eks_managed_node_groups] } ################################################################################ # EKS Addons ################################################################################ data "aws_iam_policy_document" "addon_assume_role_policy" { count = length(local.eks_addons_map) != 0 ? 1 : 0 statement { actions = ["sts:AssumeRoleWithWebIdentity"] effect = "Allow" condition { test = "StringLike" variable = "${replace(aws_iam_openid_connect_provider.oidc_provider.url, "https://", "")}:aud" values = ["sts.amazonaws.com"] } dynamic "condition" { for_each = local.service_account_map content { test = "StringLike" variable = "${replace(aws_iam_openid_connect_provider.oidc_provider.url, "https://", "")}:sub" values = ["system:serviceaccount:kube-system:${condition.value}"] } } principals { identifiers = [aws_iam_openid_connect_provider.oidc_provider.arn] type = "Federated" } } depends_on = [aws_iam_openid_connect_provider.oidc_provider] } resource "aws_iam_role" "addon_role" { count = length(local.eks_addons_map) != 0 ? 1 : 0 assume_role_policy = data.aws_iam_policy_document.addon_assume_role_policy[0].json depends_on = [data.aws_iam_policy_document.addon_assume_role_policy] } resource "aws_iam_role_policy_attachment" "addon_policy_attachments" { for_each = toset(local.addons_policy_arns) policy_arn = "arn:aws:iam::aws:policy/${each.value}" role = aws_iam_role.addon_role[0].name depends_on = [aws_iam_role.addon_role] } resource "aws_eks_addon" "addon" { for_each = { for k, v in local.eks_addons_map : k => v if !try(v.before_compute, false) } cluster_name = aws_eks_cluster.eks.name addon_name = each.value.name addon_version = try(each.value.version, null) service_account_role_arn = aws_iam_role.addon_role[0].arn configuration_values = try(each.value.configuration_values, null) != null ? jsonencode(each.value.configuration_values) : null tags = { "Name" = each.value.name } depends_on = [ aws_iam_role_policy_attachment.addon_policy_attachments, aws_eks_node_group.eks_managed_node_groups ] } resource "aws_eks_addon" "before_compute" { for_each = { for k, v in local.eks_addons_map : k => v if try(v.before_compute, false) } cluster_name = aws_eks_cluster.eks.name addon_name = each.value.name addon_version = try(each.value.version, null) service_account_role_arn = aws_iam_role.addon_role[0].arn configuration_values = try(each.value.configuration_values, null) != null ? jsonencode(each.value.configuration_values) : null tags = { "Name" = each.value.name } depends_on = [aws_iam_role_policy_attachment.addon_policy_attachments] } resource "terraform_data" "install_cni_metrics_helper" { count = var.eks_config.enable_cni_metrics_helper ? 1 : 0 provisioner "local-exec" { command = <<EOT #!/bin/bash set -e helm repo add eks https://aws.github.io/eks-charts helm repo update eks aws eks update-kubeconfig --region ${var.region} --name ${local.eks_cluster_name} helm upgrade --install cni-metrics-helper --namespace kube-system eks/cni-metrics-helper \ --set "env.AWS_CLUSTER_ID=${local.eks_cluster_name}" EOT } provisioner "local-exec" { when = destroy command = <<EOT #!/bin/bash set -e helm uninstall cni-metrics-helper --namespace kube-system EOT } depends_on = [aws_eks_cluster.eks] }