Deleting NAT Gateway used only during initial build with CFN custom resource

TOC

Delete resource (NAT gateway) used only for initial build with CFN custom resource

For example, when creating an ECS, you need to be able to access the Internet if you want to pull images from Dockerhub.
If the ECS cluster is set up on a private subnet, a NAT gateway is placed on the public subnet to provide a route to the Internet.
However, in a verification environment, for example, once the image pull is complete, the NAT gateway may no longer be used and unnecessary resources may continue to act.

Therefore, this page aims to remove the used resources (e.g. NAT gateway) by using CloudFormation custom resource.

Environment

Diagram of delete NAT Gateay used only during initial build with CFN Custom Resource.

Create a Fargate type ECS and place it on a private subnet.
Create a container from the latest Nginx image obtained from Dockerhub.

Place a NAT gateway on the public subnet to retrieve the official Nginx image from DockerHub.

After the ECS build is complete, the NAT gateway and Internet gateway are no longer needed.
Create a Lambda function to remove them and associate this with a CloudFormation custom resource.
Set this function to run when the CloudFormation stack is created.

Create an EC2 instance.
Use it as a client to access the container.

CloudFormation template files

Build the above configuration with CloudFormation.
The CloudFormation templates are located at the following URL

https://github.com/awstut-an-r/awstut-fa/tree/main/071

Explanation of key points of the template files

The purpose of this project is to remove unnecessary resources using CloudFormation custom resource.
Specifically, we will remove the following four resources

  • NAT gateway
  • Internet Gateway
  • Route table associated with the subnet for ECS (private subnet)
  • Route tables associated with subnets for NAT gateways (public subnets)

Custom Resource

Resources:
  CustomResource:
    Type: Custom::CustomResource
    Properties:
      ServiceToken: !GetAtt CustomResourceLambdaFunction.Arn
Code language: YAML (yaml)

No special configuration is required.
Set the ARN of the Lambda function described below to the ServiceToken property and associate the function with the custom resource.

For basic information on CloudFormation custom resource, please refer to the following page

あわせて読みたい
Introduction to CloudFormation Custom Resources 【Configuration to check behavior of CloudFormation Custom resources】 One of the features of CloudFormation is custom resources. Custom resources enable you...

Lambda Function

Resources:
  CustomResourceLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Architectures:
        - !Ref Architecture
      Environment:
        Variables:
          CONTAINER_ROUTE_TABLE: !Ref ContainerRouteTable
          EIP_ALLOCATION_ID: !Ref EIPAllocationId
          INTERNET_GATEWAY: !Ref IGW
          NAT_GATEWAY: !Ref NATGateway
          PUBLIC_ROUTE_TABLE: !Ref PublicRouteTable
          REGION: !Ref AWS::Region
          VPC: !Ref VPC
      Code:
        ZipFile: |
          ...
      FunctionName: !Sub "${Prefix}-custom-resource-lambda-function"
      Handler: !Ref Handler
      Runtime: !Ref Runtime
      Role: !GetAtt CustomResourceLambdaFunctionRole.Arn
      Timeout: !Ref Timeout
Code language: YAML (yaml)

Define the code to be executed by the Lambda function in inline notation.
For more information, please refer to the following page

あわせて読みたい
3 parterns to create Lambda with CloudFormation (S3/Inline/Container) 【Creating Lambda with CloudFormation】 When creating a Lambda with CloudFormation, there are three main patterns as follows. Uploading the code to an S3 buc...

The Environment property allows you to define environment variables that can be passed to the function.
Specifically, these are the IDs of the NAT gateway, the Internet gateway, and the two route tables to be deleted.

Here is the code to run

import boto3
import cfnresponse
import os
import time

container_route_table = os.environ['CONTAINER_ROUTE_TABLE']
eip_allocation_id = os.environ['EIP_ALLOCATION_ID']
internet_gateway = os.environ['INTERNET_GATEWAY']
nat_gateway = os.environ['NAT_GATEWAY']
public_route_table = os.environ['PUBLIC_ROUTE_TABLE']
region = os.environ['REGION']
vpc = os.environ['VPC']

client = boto3.client('ec2', region_name=region)

CREATE = 'Create'
response_data = {}

def lambda_handler(event, context):
  try:
    if event['RequestType'] == CREATE:
      response1 = client.delete_route(
        DestinationCidrBlock='0.0.0.0/0',
        RouteTableId=container_route_table)
      print(response1)

      response2 = client.delete_route(
        DestinationCidrBlock='0.0.0.0/0',
        RouteTableId=public_route_table)
      print(response2)

      response3 = client.delete_nat_gateway(
        NatGatewayId=nat_gateway)
      print(response3)

      time.sleep(60)

      response4 = client.release_address(
        AllocationId=eip_allocation_id)
      print(response4)

      response5 = client.detach_internet_gateway(
        InternetGatewayId=internet_gateway,
        VpcId=vpc)
      print(response5)

      response6 = client.delete_internet_gateway(
        InternetGatewayId=internet_gateway)
      print(response6)

    cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)

  except Exception as e:
    print(e)
    cfnresponse.send(event, context, cfnresponse.FAILED, response_data)
Code language: YAML (yaml)

The cfnresponse module is used to implement the function as a Lambda-backed custom resource.

The code can be summarized as follows:

  1. retrieve the environment variables defined in the CloudFormation template by accessing os.environ.
  2. create a client object for EC2 with Boto3.
  3. Delete the default route for the NAT gateway from the route table for the private subnet using the delete_route method.
  4. Delete the default route for the Internet Gateway from the route table for the public subnet using the delete_route method.
  5. Delete a NAT gateway using the delete_nat_gateway method.
  6. After deleting the NAT gateway and waiting 60 seconds, release the Elastic IP address attached to the NAT gateway using the release_address method.
  7. detach_internet_gateway method to detach the Internet gateway from the VPC.
  8. delete_internet_gateway method to delete the Internet Gateway.

The reason for the 60-second wait in item 6 is to wait for the deletion of the NAT gateway to complete.
Please note that if you do not wait long enough for the deletion, the following error will occur

An error occurred (AuthFailure) when calling the ReleaseAddress operation: You do not have permission to access the specified resource.

The reason for releasing the Elastic IP address is to completely break the association with the VPC.

You can’t detach an internet gateway if the VPC has resources with associated public IP addresses or Elastic IP addresses.

Connect to the internet using an internet gateway

IAM role for Lambda function

Resources:
  CustomResourceLambdaFunctionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service:
                - lambda.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: DeleteTemporaryResourcePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - ec2:DeleteInternetGateway
                  - ec2:DetachInternetGateway
                Resource:
                  - !Sub "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:internet-gateway/${IGW}"
                  - !Sub "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:vpc/${VPC}"
              - Effect: Allow
                Action:
                  - ec2:DeleteRoute
                Resource:
                  - !Sub "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:route-table/${PublicRouteTable}"
                  - !Sub "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:route-table/${ContainerRouteTable}"
              - Effect: Allow
                Action:
                  - ec2:DeleteNatGateway
                Resource:
                  - !Sub "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:natgateway/${NATGateway}"
              - Effect: Allow
                Action:
                  - ec2:ReleaseAddress
                Resource: "*"
                #Resource: !Sub "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:eip/${EIPAllocationId}"
Code language: YAML (yaml)

In addition to AWSLambdaVPCAccessExecutionRole, grant permissions to delete and detach four resources.

Please note the authorization settings for releasing Elastic IP addresses.
The value specified for the Resource property is “*”.
Note that even if you specify the ARN of the Elastic IP address for this property, an authorization error will occur.

(Reference) Task Definition

Resources:
  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      ContainerDefinitions:
        - Name: !Sub "${Prefix}-container"
          Image: nginx:latest
      Cpu: !Ref TaskCpu
      ExecutionRoleArn: !Ref FargateTaskExecutionRole
      Memory: !Ref TaskMemory
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      TaskRoleArn: !Ref TaskRole
Code language: YAML (yaml)

Set the Image property to “nginx:latest” to create a container from the latest version of the Nginx image from Dockerhub.
This will access the Internet via the NAT gateway and cause the action to pull the image from Dockerhub.

Architecting

Using CloudFormation, we will build this environment and verify the actual behavior.

Create CloudFormation stacks and check resources in stacks

Create a CloudFormation stacks.
Please refer to the following pages for information on how to create stacks and check each stack.

あわせて読みたい
CloudFormation’s nested stack 【How to build an environment with a nested CloudFormation stack】 Examine nested stacks in CloudFormation. CloudFormation allows you to nest stacks. Nested ...

After checking the resources for each stack, information on the main resources created this time is as follows

  • ECS cluster: fa-071-cluster
  • ECS service: fa-071-service
  • EC2 instance: i-090a6eb7047448e04
  • NAT gateway: nat-0cbbcb99b52367f68
  • Internet Gateway: igw-079babd830354ac3a
  • Elastic IP address: 54.248.104.49
  • Route table associated with private subnet: rtb-06e6f97bd2f575051
  • Route table associated with public subnet: rtb-039ff9202847ec4a5

Check the created resource from the AWS Management Console.
First check the CloudFormation stack.

Detail of CloudFormation Stacks 1.

We can see that the resource to be deleted has indeed been created.

Next, check the CloudFormation custom resource creation status.

Detail of CloudFormation Stacks 2.

This also shows that the resource has been created.
This means that the NAT gateway, etc., once created, was deleted by this custom resource.

Check the ECS task.

Detail of ECS 1.

You can see that the private address assigned to the created task is “10.0.3.171”.

Checking Action

Now that everything is ready, let’s check the status.

Checking Resource Status

First, check the execution log of the Lambda function associated with the CloudFormation custom resource.

Detail of CloudFormation Stacks 3.

We can see that the various resource deletions were indeed successfully executed.

Next, check the routing information in the two route tables.

Result of CloudFormation Custom Resource 1.
Result of CloudFormation Custom Resource 2.

We can see that the default route has indeed been removed from the two route tables.

Next, we check the status of the NAT gateway.

Result of CloudFormation Custom Resource 3.

The Status is “Deleted”.
We can see that the NAT gateway has indeed been deleted.

Check the Elastic IP address attached to the NAT gateway.

Result of CloudFormation Custom Resource 4.

We can confirm that it has been released as well.

Finally, we check the status of the Internet gateway.

Result of CloudFormation Custom Resource 5.

We can see that it has already been removed.

Accessing Container

Checking the Operation of the Container by accessing the EC2 instance.

Use SSM Session Manager to access the EC2 instance.

% aws ssm start-session --target i-090a6eb7047448e04

Starting session with SessionId: root-03345556116251505
sh-4.2$Code language: JavaScript (javascript)

For more information on SSM Session Manager, please see the following page

あわせて読みたい
Accessing Linux instance via SSM Session Manager 【Configure Linux instances to be accessed via SSM Session Manager】 We will check a configuration in which an EC2 instance is accessed via SSM Session Manag...

Access the container in the task using the curl command.

sh-4.2$ curl http://10.0.3.171
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
Code language: Bash (bash)

I was able to access the site successfully.
The Nginx container is indeed in action.

This means that during the initial build of the ECS, the Nginx image was obtained from Dockerhub by accessing the Internet through the NAT gateway.
Then, after the build was completed, the NAT gateway and other resources were removed by CloudFormation custom resource.

Summary

We have seen how to remove used resources (e.g. NAT gateways) by using CloudFormation custom resource.

TOC