# EC2 Nodes ==================================================================== # Key pairs -------------------------------------------------------------------- resource "aws_key_pair" "this" { for_each = var.key_pairs key_name = "${var.name_prefix}-${local.region_abbr}-keypair-${each.value.name}" public_key = each.value.public_key tags = merge(var.tags, local.module_tags, { resource-type = "ec2-key-pair" }) } # Instances -------------------------------------------------------------------- resource "aws_instance" "this" { for_each = var.instances ami = each.value.ami_id instance_type = each.value.instance_type subnet_id = each.value.subnet_id vpc_security_group_ids = each.value.security_group_ids iam_instance_profile = each.value.iam_instance_profile key_name = try(aws_key_pair.this[each.value.key_name].key_name, each.value.key_name) associate_public_ip_address = each.value.associate_public_ip ebs_optimized = each.value.ebs_optimized monitoring = each.value.monitoring user_data = each.value.user_data_base64 == null ? each.value.user_data : null user_data_base64 = each.value.user_data_base64 placement_group = each.value.placement_group tenancy = each.value.tenancy private_ip = each.value.primary_private_ip secondary_private_ips = each.value.secondary_private_ips metadata_options { http_endpoint = "enabled" http_tokens = each.value.metadata_http_tokens http_put_response_hop_limit = each.value.metadata_hop_limit } root_block_device { volume_size = each.value.root_volume.size volume_type = each.value.root_volume.type encrypted = true kms_key_id = each.value.root_volume.kms_key_id delete_on_termination = each.value.root_volume.delete_on_termination tags = merge(var.tags, each.value.instance_tags, each.value.root_volume.tags, local.module_tags, { Name = "${local.instance_names[each.key]}-root-vol" resource-type = "ebs-volume" }) } tags = merge(var.tags, each.value.instance_tags, local.module_tags, { Name = local.instance_names[each.key] resource-type = "ec2-instance" }) lifecycle { ignore_changes = [ user_data, user_data_base64, ] postcondition { condition = self.instance_state == "running" error_message = "Instance ${self.tags.Name} did not reach running state after apply." } postcondition { condition = self.primary_network_interface_id != null error_message = "Instance ${self.tags.Name} has no primary network interface — check subnet and security group config." } postcondition { condition = length(each.value.secondary_private_ips) == 0 || alltrue([ for ip in each.value.secondary_private_ips : contains(self.secondary_private_ips, ip) ]) error_message = "Instance ${self.tags.Name} did not receive all requested secondary private IPs." } postcondition { condition = self.metadata_options[0].http_tokens == "required" error_message = "Instance ${self.tags.Name} is not enforcing IMDSv2. metadata_http_tokens must be 'required'." } } } # Data volumes ----------------------------------------------------------------- resource "aws_ebs_volume" "data" { for_each = local.data_volume_map availability_zone = aws_instance.this[each.value.instance_key].availability_zone size = each.value.vol.volume_size type = each.value.vol.volume_type encrypted = true kms_key_id = each.value.vol.kms_key_id iops = each.value.vol.iops throughput = each.value.vol.throughput tags = merge(var.tags, each.value.vol.tags, local.module_tags, { Name = "${local.instance_names[each.value.instance_key]}-data-vol-${format("%02d", each.value.idx + 1)}" resource-type = "ebs-volume" }) } resource "aws_volume_attachment" "data" { for_each = local.data_volume_map device_name = each.value.vol.device_name volume_id = aws_ebs_volume.data[each.key].id instance_id = aws_instance.this[each.value.instance_key].id }