From afd37d3bbb1df53f26ae17ade26f15a2a76ea019 Mon Sep 17 00:00:00 2001 From: Chris Wright Date: Mon, 6 Jan 2025 15:17:14 +0000 Subject: [PATCH] Update image ID in launch template with instance refresh * Modifies the Lambda that triggers an instance refresh, to first update the image ID to the latest AMI. This ensures the instances will be using the most up to date AMI * This has been placed within the instance refresh lambda, because it will require an instance refresh anyway to actually update the instances, and allows it to happen at the chosen instance refresh cron * The AutoScaling group's launch template version has also been changed to '$Latest', rather than a specific version so that we don't need to keep that up to date with the latest version --- README.md | 2 + data.tf | 2 +- ...-infrastructure-instance-refresh-lambda.tf | 18 ++++++- ecs-cluster-infrastructure.tf | 2 +- lambdas/ecs-asg-instance-refresh/function.py | 50 +++++++++++++++++++ locals.tf | 1 + policies/ec2-describe-images.json.tpl | 12 +++++ 7 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 policies/ec2-describe-images.json.tpl diff --git a/README.md b/README.md index b9564cc..4507594 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,7 @@ This project creates and manages resources within an AWS account for infrastruct | [aws_iam_policy.ecs_cluster_infrastructure_ecs_asg_diff_metric_ecs_describe_cluster_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.ecs_cluster_infrastructure_ecs_asg_diff_metric_kms_encrypt](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.ecs_cluster_infrastructure_ecs_asg_diff_metric_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.ecs_cluster_infrastructure_instance_refresh_allow_describe_images](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.ecs_cluster_infrastructure_instance_refresh_allow_instance_refresh](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.ecs_cluster_infrastructure_instance_refresh_kms_encrypt](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.ecs_cluster_infrastructure_instance_refresh_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | @@ -236,6 +237,7 @@ This project creates and manages resources within an AWS account for infrastruct | [aws_iam_role_policy_attachment.ecs_cluster_infrastructure_ecs_asg_diff_metric_asg_describe_asg_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.ecs_cluster_infrastructure_ecs_asg_diff_metric_ecs_describe_cluster_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.ecs_cluster_infrastructure_ecs_asg_diff_metric_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.ecs_cluster_infrastructure_instance_refresh_allow_describe_images](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.ecs_cluster_infrastructure_instance_refresh_allow_instance_refresh](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.ecs_cluster_infrastructure_instance_refresh_kms_encrypt](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.ecs_cluster_infrastructure_instance_refresh_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | diff --git a/data.tf b/data.tf index bace70c..23b4d0c 100644 --- a/data.tf +++ b/data.tf @@ -19,7 +19,7 @@ data "aws_ami" "ecs_cluster_ami" { filter { name = "name" values = [ - "al2023-ami-ecs-hvm-${local.infrastructure_ecs_cluster_ami_version}" + local.infrastructure_ecs_cluster_ami_name_filter ] } diff --git a/ecs-cluster-infrastructure-instance-refresh-lambda.tf b/ecs-cluster-infrastructure-instance-refresh-lambda.tf index 50aac4c..678a201 100644 --- a/ecs-cluster-infrastructure-instance-refresh-lambda.tf +++ b/ecs-cluster-infrastructure-instance-refresh-lambda.tf @@ -57,6 +57,20 @@ resource "aws_iam_role_policy_attachment" "ecs_cluster_infrastructure_instance_r policy_arn = aws_iam_policy.ecs_cluster_infrastructure_instance_refresh_allow_instance_refresh[0].arn } +resource "aws_iam_policy" "ecs_cluster_infrastructure_instance_refresh_allow_describe_images" { + count = local.infrastructure_ecs_cluster_instance_refresh_lambda_schedule_expression != "" ? 1 : 0 + + name = "${local.resource_prefix}-ecs-cluster-infrastructure-instance-refresh-allow-describe-images" + policy = templatefile("${path.root}/policies/ec2-describe-images.json.tpl", {}) +} + +resource "aws_iam_role_policy_attachment" "ecs_cluster_infrastructure_instance_refresh_allow_describe_images" { + count = local.infrastructure_ecs_cluster_instance_refresh_lambda_schedule_expression != "" ? 1 : 0 + + role = aws_iam_role.ecs_cluster_infrastructure_instance_refresh_lambda[0].name + policy_arn = aws_iam_policy.ecs_cluster_infrastructure_instance_refresh_allow_describe_images[0].arn +} + resource "aws_iam_policy" "ecs_cluster_infrastructure_instance_refresh_kms_encrypt" { count = local.infrastructure_ecs_cluster_instance_refresh_lambda_schedule_expression != "" && local.infrastructure_kms_encryption ? 1 : 0 @@ -98,7 +112,9 @@ resource "aws_lambda_function" "ecs_cluster_infrastructure_instance_refresh" { environment { variables = { - asgName = aws_autoscaling_group.infrastructure_ecs_cluster[0].name + asgName = aws_autoscaling_group.infrastructure_ecs_cluster[0].name + launchTemplateName = aws_launch_template.infrastructure_ecs_cluster[0].name + amiVersion = local.infrastructure_ecs_cluster_ami_name_filter } } diff --git a/ecs-cluster-infrastructure.tf b/ecs-cluster-infrastructure.tf index 86f9e90..ec62f0a 100644 --- a/ecs-cluster-infrastructure.tf +++ b/ecs-cluster-infrastructure.tf @@ -176,7 +176,7 @@ resource "aws_autoscaling_group" "infrastructure_ecs_cluster" { launch_template { id = aws_launch_template.infrastructure_ecs_cluster[0].id - version = aws_launch_template.infrastructure_ecs_cluster[0].latest_version + version = "$Latest" } vpc_zone_identifier = local.infrastructure_ecs_cluster_publicly_avaialble ? [ diff --git a/lambdas/ecs-asg-instance-refresh/function.py b/lambdas/ecs-asg-instance-refresh/function.py index be2592a..e10273c 100644 --- a/lambdas/ecs-asg-instance-refresh/function.py +++ b/lambdas/ecs-asg-instance-refresh/function.py @@ -3,9 +3,59 @@ import os asgName = os.environ['asgName'] +launchTemplateName = os.environ['launchTemplateName'] +amiVersion = os.environ['amiVersion'] def lambda_handler(event, context): asgClient = boto3.client('autoscaling') + ec2Client = boto3.client('ec2') + + # Update launch template to use the latest AMI + response = ec2Client.describe_images( + Owners=['amazon'], + Filters=[ + {'Name': 'name', 'Values': [amiVersion]}, + {'Name': 'state', 'Values': ['available']}, + {'Name': 'architecture', 'Values': ['x86_64']} + ] + ) + + images = sorted( + response['Images'], + key=lambda x: x['CreationDate'], + reverse=True + ) + if not images: + raise Exception("No AMIs found!") + + latest_ami_id = images[0]['ImageId'] + print(f"Latest ECS-optimized AMI: {latest_ami_id}") + + try: + template_response = ec2Client.describe_launch_template_versions( + LaunchTemplateName=launchTemplateName, + Versions=["$Latest"] + ) + template_data = template_response['LaunchTemplateVersions'][0]['LaunchTemplateData'] + except Exception as e: + print(f"Error retrieving launch template: {e}") + raise + + template_data['ImageId'] = latest_ami_id + + try: + new_version_response = ec2Client.create_launch_template_version( + LaunchTemplateName=launchTemplateName, + SourceVersion="$Latest", + LaunchTemplateData=template_data + ) + new_version_number = new_version_response['LaunchTemplateVersion']['VersionNumber'] + print(f"Created new version: {new_version_number}") + except Exception as e: + print(f"Error creating new launch template version: {e}") + raise + + # Start instance refresh try: response = asgClient.start_instance_refresh( AutoScalingGroupName=asgName, diff --git a/locals.tf b/locals.tf index 09eb62d..580b938 100644 --- a/locals.tf +++ b/locals.tf @@ -127,6 +127,7 @@ locals { enable_infrastructure_ecs_cluster = var.enable_infrastructure_ecs_cluster && local.infrastructure_vpc infrastructure_ecs_cluster_name = "${local.resource_prefix}-infrastructure" infrastructure_ecs_cluster_ami_version = var.infrastructure_ecs_cluster_ami_version + infrastructure_ecs_cluster_ami_name_filter = "al2023-ami-ecs-hvm-${local.infrastructure_ecs_cluster_ami_version}" infrastructure_ecs_cluster_ebs_docker_storage_volume_device_name = "/dev/xvdcz" infrastructure_ecs_cluster_ebs_docker_storage_volume_size = var.infrastructure_ecs_cluster_ebs_docker_storage_volume_size infrastructure_ecs_cluster_ebs_docker_storage_volume_type = var.infrastructure_ecs_cluster_ebs_docker_storage_volume_type diff --git a/policies/ec2-describe-images.json.tpl b/policies/ec2-describe-images.json.tpl new file mode 100644 index 0000000..d8a8f80 --- /dev/null +++ b/policies/ec2-describe-images.json.tpl @@ -0,0 +1,12 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ec2:DescribeImages" + ], + "Resource": "*" + } + ] +}