初期構築の時だけ使用するリソース(NATゲートウェイ)をCFNカスタムリソースで削除する
例えばECSを作成する際に、Dockerhubからイメージをプルする場合、インターネットにアクセスできる必要があります。
ECSクラスターをプライベートサブネットに設置する場合、パブリックサブネットにNATゲートウェイを配置し、インターネットへのルートを用意します。
しかし例えば検証環境ですと、一度イメージのプルが完了すると、NATゲートウェイを使用する用途がなくなり、不要なリソースが動作し続けることになる場合もあります。
そこで本ページでは、CloudFormationカスタムリソースを使用することによって、使用済みのリソース(NATゲートウェイ等)を削除することを目指します。
構築する環境
FargateタイプのECSを作成し、プライベートサブネットに配置します。
Dockerhubから取得した最新のNginxイメージからコンテナを作成します。
DockerHubからNginxの公式イメージを取得するために、パブリックサブネットにNATゲートウェイを配置します。
ECSの構築が完了後は、NATゲートウェイやインターネットゲートウェイは不要となります。
これらを削除するLambda関数を作成し、これをCloudFormationカスタムリソースに関連づけます。
CloudFormationスタック作成時に、この関数を実行するように設定します。
EC2インスタンスを作成します。
コンテナにアクセスするためのクライアントして使用します。
CloudFormationテンプレートファイル
上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。
https://github.com/awstut-an-r/awstut-fa/tree/main/071
テンプレートファイルのポイント解説
今回の目的はCloudFormationカスタムリソースを使って、不要なリソースを削除することです。
具体的には以下の4つのリソースを削除します。
- NATゲートウェイ
- インターネットゲートウェイ
- ECS用サブネット(プライベートサブネット)に関連づけたルートテーブル
- NATゲートウェイ用サブネット(パブリックサブネット)に関連づけたルートテーブル
カスタムリソース
Resources:
CustomResource:
Type: Custom::CustomResource
Properties:
ServiceToken: !GetAtt CustomResourceLambdaFunction.Arn
Code language: YAML (yaml)
特別な設定は不要です。
ServiceTokenプロパティに後述のLambda関数のARNを設定し、関数をカスタムリソースに関連づけます。
CloudFormationカスタムリソースの基本的な事項は、以下のページをご確認ください。
Lambda関数
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)
Lambda関数で実行するコードをインライン表記で定義します。
詳細につきましては、以下のページをご確認ください。
Environmentプロパティで関数に渡すことができる環境変数を定義できます。
具体的には、削除対象のNATゲートウェイやインターネットゲートウェイ、2つのルートテーブルのIDです
以下が実行するコードです。
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)
cfnresponseモジュールを使用して、関数をLambda-backedカスタムリソースとして実装します。
コードの内容をまとめますと、以下の通りです。
- CloudFormationテンプレートで定義した環境変数を、os.environにアクセスして取得する。
- Boto3でEC2用クライアントオブジェクトを作成する。
- delete_routeメソッドで、プライベートサブネット用ルートテーブルから、NATゲートウェイ向けのデフォルトルートを削除する。
- delete_routeメソッドで、パブリックサブネット用ルートテーブルから、インターネットゲートウェイ向けのデフォルトルートを削除する。
- delete_nat_gatewayメソッドで、NATゲートウェイを削除する。
- NATゲートウェイを削除してから60秒待機した後で、release_addressメソッドで、NATゲートウェイにアタッチしていたElastic IPアドレスをリリースする。
- detach_internet_gatewayメソッドで、インターネットゲートウェイをVPCからデタッチする。
- delete_internet_gatewayメソッドで、インターネットゲートウェイを削除する。
項番6で60秒待機する理由ですが、NATゲートウェイの削除が完了を待つための処理です。
削除に十分な時間待機しない場合、以下のエラーが発生しますのでご注意ください。
An error occurred (AuthFailure) when calling the ReleaseAddress operation: You do not have permission to access the specified resource.
Elastic IPアドレスをリリースする理由ですが、VPCとの関連付けを完全に解消するためです。
VPC に関連付けられたパブリック IP アドレスまたは Elastic IP アドレスを持つリソースがある場合、インターネットゲートウェイをデタッチすることはできません。
インターネットゲートウェイを使用してインターネットに接続する
Lambda関数用IAMロール
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)
AWS管理ポリシーであるAWSLambdaVPCAccessExecutionRoleに加えて、4つのリソースを削除・デタッチするための権限を付与します。
Elastic IPアドレスをリリースための権限設定にご注意ください。
Resourceプロパティに指定する値は「*」です。
仮にこのプロパティにElastic IPアドレスのARNを指定したとしても、権限エラーが発生しますのでご注意ください。
(参考)タスク定義
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)
Imageプロパティに「nginx:latest」を指定して、Dockerhubから最新版のNginxのイメージからコンテナを作成するように設定します。
これでNATゲートウェイ経由でインターネットにアクセスし、Dockerhubからイメージをプルする動作を発生させます。
環境構築
CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。
CloudFormationスタックを作成し、スタック内のリソースを確認する
CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。
各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。
- ECSクラスター:fa-071-cluster
- ECSサービス:fa-071-service
- EC2インスタンス:i-090a6eb7047448e04
- NATゲートウェイ:nat-0cbbcb99b52367f68
- インターネットゲートウェイ:igw-079babd830354ac3a
- Elastic IPアドレス:54.248.104.49
- プライベートサブネットに関連づけたルートテーブル:rtb-06e6f97bd2f575051
- パブリックサブネットに関連づけたルートテーブル:rtb-039ff9202847ec4a5
作成されたリソースをAWS Management Consoleから確認します。
まずCloudFormationスタックを確認します。
削除対象のリソースが確かに作成されていることがわかります。
次にCloudFormationカスタムリソースの作成状況を確認します。
こちらも確かに作成されていることがわかります。
つまり一度作成されたNATゲートウェイ等が、このカスタムリソースによって削除されたということになります。
ECSタスクを確認します。
作成されたタスクに付与されたプライベートアドレスが「10.0.3.171」ということがわかります。
動作確認
準備が整いましたので、状況を確認します。
リソースの状況確認
まずCloudFormationカスタムリソースに関連づけられたLambda関数の実行ログを確認します。
確かに各種リソース削除が正常に実行されたことがわかります。
続いて2つのルートテーブルのルーティング情報を確認します。
確かに2つのルートテーブルからデフォルトルートが削除されています。
次にNATゲートウェイの状況を確認します。
Statusが「Deleted」となっています。
確かにNATゲートウェイが削除されていることがわかります。
NATゲートウェイにアタッチしていたElastic IPアドレスを確認します。
こちらもリリース済みであることが確認できます。
最後にインターネットゲートウェイの状況を確認します。
既に削除済みであることがわかります。
コンテナにアクセス
EC2インスタンスにアクセスし、コンテナの動作を確認します。
EC2インスタンスへのアクセスはSSM Session Managerを使用します。
% aws ssm start-session --target i-090a6eb7047448e04
Starting session with SessionId: root-03345556116251505
sh-4.2$
Code language: Bash (bash)
SSM Session Managerの詳細につきましては、以下のページをご確認ください。
curlコマンドを使って、タスク内のコンテナにアクセスします。
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)
正常にアクセスすることができました。
確かにNginxコンテナが動作しています。
つまりECSの初期構築時は、NATゲートウェイを通じてインターネットにアクセスして、DockerhubからNginxイメージを取得したということです。
そして構築が完了後にCloudFormationカスタムリソースによって、NATゲートウェイ等が削除されたということです。
まとめ
CloudFormationカスタムリソースを使用することによって、使用済みのリソース(NATゲートウェイ等)を削除する方法を確認しました。