Skip to content

Commit

Permalink
Refactor for clarity (cloudposse#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nuru authored Sep 14, 2020
1 parent 863d4ab commit cab5114
Show file tree
Hide file tree
Showing 7 changed files with 253 additions and 227 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,14 +202,15 @@ Available targets:
| attributes | Additional attributes (e.g. `1`) | `list(string)` | `[]` | no |
| before\_cluster\_joining\_userdata | Additional `bash` commands to execute on each worker node before joining the EKS cluster (before executing the `bootstrap.sh` script). For more info, see https://kubedex.com/90-days-of-aws-eks-in-production | `string` | `""` | no |
| bootstrap\_additional\_options | Additional options to bootstrap.sh. DO NOT include `--kubelet-additional-args`, use `kubelet_additional_args` var instead. | `string` | `""` | no |
| cluster\_autoscaler\_enabled | Set true to label the node group so that the [Kubernetes Cluster Autoscaler](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md#auto-discovery-setup) will discover and autoscale it | `bool` | `null` | no |
| cluster\_name | The name of the EKS cluster | `string` | n/a | yes |
| context | Single object for setting entire context at once.<br>See description of individual variables for details.<br>Leave string and numeric variables as `null` to use default value.<br>Individual variable settings (non-null) override settings in context object,<br>except for attributes, tags, and additional\_tag\_map, which are merged. | <pre>object({<br> enabled = bool<br> namespace = string<br> environment = string<br> stage = string<br> name = string<br> delimiter = string<br> attributes = list(string)<br> tags = map(string)<br> additional_tag_map = map(string)<br> regex_replace_chars = string<br> label_order = list(string)<br> id_length_limit = number<br> })</pre> | <pre>{<br> "additional_tag_map": {},<br> "attributes": [],<br> "delimiter": null,<br> "enabled": true,<br> "environment": null,<br> "id_length_limit": null,<br> "label_order": [],<br> "name": null,<br> "namespace": null,<br> "regex_replace_chars": null,<br> "stage": null,<br> "tags": {}<br>}</pre> | no |
| create\_before\_destroy | Set true in order to create the new node group before destroying the old one.<br>If false, the old node group will be destroyed first, causing downtime.<br>Changing this setting will always cause node group to be replaced. | `bool` | `false` | no |
| delimiter | Delimiter to be used between `namespace`, `environment`, `stage`, `name` and `attributes`.<br>Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no |
| desired\_size | Initial desired number of worker nodes (external changes ignored) | `number` | n/a | yes |
| disk\_size | Disk size in GiB for worker nodes. Defaults to 20. Ignored it `launch_template_id` is supplied.<br>Terraform will only perform drift detection if a configuration value is provided. | `number` | `20` | no |
| ec2\_ssh\_key | SSH key pair name to use to access the worker nodes | `string` | `null` | no |
| enable\_cluster\_autoscaler | Set true to allow Kubernetes Cluster Auto Scaler to scale the node group | `bool` | `false` | no |
| enable\_cluster\_autoscaler | (Deprecated, use `cluster_autoscaler_enabled`) Set true to allow Kubernetes Cluster Auto Scaler to scale the node group | `bool` | `null` | no |
| enabled | Set to false to prevent the module from creating any resources | `bool` | `null` | no |
| environment | Environment, e.g. 'uw2', 'us-west-2', OR 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no |
| existing\_workers\_role\_policy\_arns | List of existing policy ARNs that will be attached to the workers default role on creation | `list(string)` | `[]` | no |
Expand All @@ -235,6 +236,7 @@ Available targets:
| subnet\_ids | A list of subnet IDs to launch resources in | `list(string)` | n/a | yes |
| tags | Additional tags (e.g. `map('BusinessUnit','XYZ')` | `map(string)` | `{}` | no |
| userdata\_override\_base64 | Many features of this module rely on the `bootstrap.sh` provided with Amazon Linux, and this module<br>may generate "user data" that expects to find that script. If you want to use an AMI that is not<br>compatible with the Amazon Linux `bootstrap.sh` initialization, then use `userdata_override_base64` to provide<br>your own (Base64 encoded) user data. Use "" to prevent any user data from being set.<br><br>Setting `userdata_override_base64` disables `kubernetes_taints`, `kubelet_additional_options`,<br>`before_cluster_joining_userdata`, `after_cluster_joining_userdata`, and `bootstrap_additional_options`. | `string` | `null` | no |
| worker\_role\_autoscale\_iam\_enabled | If true, the worker IAM role will be authorized to perform autoscaling operations. Not recommended.<br>Use [EKS IAM role for cluster autoscaler service account](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) instead. | `bool` | `false` | no |

## Outputs

Expand Down
4 changes: 3 additions & 1 deletion docs/terraform.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@
| attributes | Additional attributes (e.g. `1`) | `list(string)` | `[]` | no |
| before\_cluster\_joining\_userdata | Additional `bash` commands to execute on each worker node before joining the EKS cluster (before executing the `bootstrap.sh` script). For more info, see https://kubedex.com/90-days-of-aws-eks-in-production | `string` | `""` | no |
| bootstrap\_additional\_options | Additional options to bootstrap.sh. DO NOT include `--kubelet-additional-args`, use `kubelet_additional_args` var instead. | `string` | `""` | no |
| cluster\_autoscaler\_enabled | Set true to label the node group so that the [Kubernetes Cluster Autoscaler](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md#auto-discovery-setup) will discover and autoscale it | `bool` | `null` | no |
| cluster\_name | The name of the EKS cluster | `string` | n/a | yes |
| context | Single object for setting entire context at once.<br>See description of individual variables for details.<br>Leave string and numeric variables as `null` to use default value.<br>Individual variable settings (non-null) override settings in context object,<br>except for attributes, tags, and additional\_tag\_map, which are merged. | <pre>object({<br> enabled = bool<br> namespace = string<br> environment = string<br> stage = string<br> name = string<br> delimiter = string<br> attributes = list(string)<br> tags = map(string)<br> additional_tag_map = map(string)<br> regex_replace_chars = string<br> label_order = list(string)<br> id_length_limit = number<br> })</pre> | <pre>{<br> "additional_tag_map": {},<br> "attributes": [],<br> "delimiter": null,<br> "enabled": true,<br> "environment": null,<br> "id_length_limit": null,<br> "label_order": [],<br> "name": null,<br> "namespace": null,<br> "regex_replace_chars": null,<br> "stage": null,<br> "tags": {}<br>}</pre> | no |
| create\_before\_destroy | Set true in order to create the new node group before destroying the old one.<br>If false, the old node group will be destroyed first, causing downtime.<br>Changing this setting will always cause node group to be replaced. | `bool` | `false` | no |
| delimiter | Delimiter to be used between `namespace`, `environment`, `stage`, `name` and `attributes`.<br>Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no |
| desired\_size | Initial desired number of worker nodes (external changes ignored) | `number` | n/a | yes |
| disk\_size | Disk size in GiB for worker nodes. Defaults to 20. Ignored it `launch_template_id` is supplied.<br>Terraform will only perform drift detection if a configuration value is provided. | `number` | `20` | no |
| ec2\_ssh\_key | SSH key pair name to use to access the worker nodes | `string` | `null` | no |
| enable\_cluster\_autoscaler | Set true to allow Kubernetes Cluster Auto Scaler to scale the node group | `bool` | `false` | no |
| enable\_cluster\_autoscaler | (Deprecated, use `cluster_autoscaler_enabled`) Set true to allow Kubernetes Cluster Auto Scaler to scale the node group | `bool` | `null` | no |
| enabled | Set to false to prevent the module from creating any resources | `bool` | `null` | no |
| environment | Environment, e.g. 'uw2', 'us-west-2', OR 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no |
| existing\_workers\_role\_policy\_arns | List of existing policy ARNs that will be attached to the workers default role on creation | `list(string)` | `[]` | no |
Expand All @@ -61,6 +62,7 @@
| subnet\_ids | A list of subnet IDs to launch resources in | `list(string)` | n/a | yes |
| tags | Additional tags (e.g. `map('BusinessUnit','XYZ')` | `map(string)` | `{}` | no |
| userdata\_override\_base64 | Many features of this module rely on the `bootstrap.sh` provided with Amazon Linux, and this module<br>may generate "user data" that expects to find that script. If you want to use an AMI that is not<br>compatible with the Amazon Linux `bootstrap.sh` initialization, then use `userdata_override_base64` to provide<br>your own (Base64 encoded) user data. Use "" to prevent any user data from being set.<br><br>Setting `userdata_override_base64` disables `kubernetes_taints`, `kubelet_additional_options`,<br>`before_cluster_joining_userdata`, `after_cluster_joining_userdata`, and `bootstrap_additional_options`. | `string` | `null` | no |
| worker\_role\_autoscale\_iam\_enabled | If true, the worker IAM role will be authorized to perform autoscaling operations. Not recommended.<br>Use [EKS IAM role for cluster autoscaler service account](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) instead. | `bool` | `false` | no |

## Outputs

Expand Down
85 changes: 85 additions & 0 deletions iam.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
locals {
aws_policy_prefix = format("arn:%s:iam::aws:policy", join("", data.aws_partition.current.*.partition))
}

data "aws_partition" "current" {
count = local.enabled ? 1 : 0
}

data "aws_iam_policy_document" "assume_role" {
count = local.enabled ? 1 : 0

statement {
effect = "Allow"
actions = ["sts:AssumeRole"]

principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}

data "aws_iam_policy_document" "amazon_eks_worker_node_autoscale_policy" {
count = (local.enabled && var.worker_role_autoscale_iam_enabled) ? 1 : 0
statement {
sid = "AllowToScaleEKSNodeGroupAutoScalingGroup"

actions = [
"autoscaling:DescribeAutoScalingGroups",
"autoscaling:DescribeAutoScalingInstances",
"autoscaling:DescribeLaunchConfigurations",
"autoscaling:DescribeTags",
"autoscaling:SetDesiredCapacity",
"autoscaling:TerminateInstanceInAutoScalingGroup",
"ec2:DescribeLaunchTemplateVersions"
]

resources = [
"*"
]
}
}

resource "aws_iam_policy" "amazon_eks_worker_node_autoscale_policy" {
count = (local.enabled && var.worker_role_autoscale_iam_enabled) ? 1 : 0
name = "${module.label.id}-autoscale"
policy = join("", data.aws_iam_policy_document.amazon_eks_worker_node_autoscale_policy.*.json)
}

resource "aws_iam_role" "default" {
count = local.enabled ? 1 : 0
name = module.label.id
assume_role_policy = join("", data.aws_iam_policy_document.assume_role.*.json)
tags = module.label.tags
}

resource "aws_iam_role_policy_attachment" "amazon_eks_worker_node_policy" {
count = local.enabled ? 1 : 0
policy_arn = format("%s/%s", local.aws_policy_prefix, "AmazonEKSWorkerNodePolicy")
role = join("", aws_iam_role.default.*.name)
}

resource "aws_iam_role_policy_attachment" "amazon_eks_worker_node_autoscale_policy" {
count = (local.enabled && var.worker_role_autoscale_iam_enabled) ? 1 : 0
policy_arn = join("", aws_iam_policy.amazon_eks_worker_node_autoscale_policy.*.arn)
role = join("", aws_iam_role.default.*.name)
}

resource "aws_iam_role_policy_attachment" "amazon_eks_cni_policy" {
count = local.enabled ? 1 : 0
policy_arn = format("%s/%s", local.aws_policy_prefix, "AmazonEKS_CNI_Policy")
role = join("", aws_iam_role.default.*.name)
}

resource "aws_iam_role_policy_attachment" "amazon_ec2_container_registry_read_only" {
count = local.enabled ? 1 : 0
policy_arn = format("%s/%s", local.aws_policy_prefix, "AmazonEC2ContainerRegistryReadOnly")
role = join("", aws_iam_role.default.*.name)
}

resource "aws_iam_role_policy_attachment" "existing_policies_for_eks_workers_role" {
for_each = local.enabled ? toset(var.existing_workers_role_policy_arns) : []
policy_arn = each.value
role = join("", aws_iam_role.default.*.name)
}
104 changes: 104 additions & 0 deletions launch-template.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
locals {
# The heavy use of the ternary operator `? :` is because it is one of the few ways to avoid
# evaluating expressions. The unused expression is not evaluated and so it does not have to be valid.
# This allows us to refer to resources that are only conditionally created and avoid creating
# dependencies on them that would not be avoided by using expressions like `join("",expr)`.
#
# We use this pattern with enabled for every boolean that begins with `need_` even though
# it is sometimes redundant, to ensure that ever `need_` is false and every dependent
# expression is not evaluated when enabled is false. Avoiding expression evaluations
# is also why, even for boolean expressions, we use
# local.enabled ? expression : false
# rather than
# local.enabled && expression
#
# The expression
# length(compact([var.launch_template_version])) > 0
# is a shorter way of accomplishing the same test as
# var.launch_template_version != null && var.launch_template_version != ""
# and as an idiom has the added benefit of being extensible:
# length(compact([x, y])) > 0
# is the same as
# x != null && x != "" && y != null && y != ""

configured_launch_template_name = var.launch_template_name == null ? "" : var.launch_template_name
configured_launch_template_version = length(local.configured_launch_template_name) > 0 && length(compact([var.launch_template_version])) > 0 ? var.launch_template_version : ""

generate_launch_template = local.enabled ? local.features_require_launch_template && length(local.configured_launch_template_name) == 0 : false
use_launch_template = local.enabled ? local.features_require_launch_template || length(local.configured_launch_template_name) > 0 : false

launch_template_id = local.use_launch_template ? (length(local.configured_launch_template_name) > 0 ? data.aws_launch_template.this[0].id : aws_launch_template.default[0].id) : ""
launch_template_version = local.use_launch_template ? (
length(local.configured_launch_template_version) > 0 ? local.configured_launch_template_version :
(
length(local.configured_launch_template_name) > 0 ? data.aws_launch_template.this[0].latest_version : aws_launch_template.default[0].latest_version
)
) : ""

launch_template_ami = length(local.configured_ami_image_id) == 0 ? (local.features_require_ami ? data.aws_ami.selected[0].image_id : "") : local.configured_ami_image_id

launch_template_vpc_security_group_ids = (
local.need_remote_access_sg ?
concat(data.aws_eks_cluster.this[0].vpc_config[*].cluster_security_group_id, aws_security_group.remote_access.*.id) : []
)

# launch_template_key = join(":", coalescelist(local.launch_template_vpc_security_group_ids, ["closed"]))
}

resource "aws_launch_template" "default" {
# We'll use this default if we aren't provided with a launch template during invocation
# We need to generate a new launch template every time the security group list changes
# so that we can detach the network interfaces from the security groups that we no
# longer need, so that the security groups can then be deleted.

# As a workaround for https://github.com/hashicorp/terraform/issues/26166 we
# always create a launch template. Commented out code will be restored when the bug is fixed.
count = local.enabled ? 1 : 0
#count = (local.enabled && local.generate_launch_template) ? 1 : 0
#for_each = (local.enabled && local.generate_launch_template) ? toset([local.launch_template_key]) : toset([])

block_device_mappings {
device_name = "/dev/xvda"

ebs {
volume_size = var.disk_size
}
}

name_prefix = module.label.id
update_default_version = true

instance_type = var.instance_types[0]
image_id = local.launch_template_ami == "" ? null : local.launch_template_ami
key_name = local.have_ssh_key ? var.ec2_ssh_key : null

dynamic "tag_specifications" {
for_each = var.resources_to_tag
content {
resource_type = tag_specifications.value
tags = local.node_tags
}
}

# See https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html
# and https://docs.aws.amazon.com/eks/latest/userguide/launch-templates.html
# Note in particular:
# If any containers that you deploy to the node group use the Instance Metadata Service Version 2,
# then make sure to set the Metadata response hop limit to 2 in your launch template.
metadata_options {
http_put_response_hop_limit = 2
# Despite being documented as "Optional", `http_endpoint` is required when `http_put_response_hop_limit` is set.
# We set it to the default setting of "enabled".
http_endpoint = "enabled"
}

vpc_security_group_ids = local.launch_template_vpc_security_group_ids
user_data = local.userdata
tags = local.node_group_tags
}

data "aws_launch_template" "this" {
count = local.enabled && length(local.configured_launch_template_name) > 0 ? 1 : 0

name = local.configured_launch_template_name
}
Loading

0 comments on commit cab5114

Please sign in to comment.