AWS

初期構築の時だけ使用するNATゲートウェイをCFNカスタムリソースで削除する

スポンサーリンク
初期構築の時だけ使用するNATゲートウェイをCFNカスタムリソースを使って削除する AWS
スポンサーリンク
スポンサーリンク

初期構築の時だけ使用するリソース(NATゲートウェイ)をCFNカスタムリソースで削除する

例えばECSを作成する際に、Dockerhubからイメージをプルする場合、インターネットにアクセスできる必要があります。
ECSクラスターをプライベートサブネットに設置する場合、パブリックサブネットにNATゲートウェイを配置し、インターネットへのルートを用意します。
しかし例えば検証環境ですと、一度イメージのプルが完了すると、NATゲートウェイを使用する用途がなくなり、不要なリソースが動作し続けることになる場合もあります。

そこで本ページでは、CloudFormationカスタムリソースを使用することによって、使用済みのリソース(NATゲートウェイ等)を削除することを目指します。

構築する環境

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

FargateタイプのECSを作成し、プライベートサブネットに配置します。
Dockerhubから取得した最新のNginxイメージからコンテナを作成します。

DockerHubからNginxの公式イメージを取得するために、パブリックサブネットにNATゲートウェイを配置します。

ECSの構築が完了後は、NATゲートウェイやインターネットゲートウェイは不要となります。
これらを削除するLambda関数を作成し、これをCloudFormationカスタムリソースに関連づけます。
CloudFormationスタック作成時に、この関数を実行するように設定します。

EC2インスタンスを作成します。
コンテナにアクセスするためのクライアントして使用します。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。

awstut-fa/071 at main · awstut-an-r/awstut-fa
Contribute to awstut-an-r/awstut-fa development by creating an account on GitHub.

テンプレートファイルのポイント解説

今回の目的は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カスタムリソースとして実装します。

コードの内容をまとめますと、以下の通りです。

  1. CloudFormationテンプレートで定義した環境変数を、os.environにアクセスして取得する。
  2. Boto3でEC2用クライアントオブジェクトを作成する。
  3. delete_routeメソッドで、プライベートサブネット用ルートテーブルから、NATゲートウェイ向けのデフォルトルートを削除する。
  4. delete_routeメソッドで、パブリックサブネット用ルートテーブルから、インターネットゲートウェイ向けのデフォルトルートを削除する。
  5. delete_nat_gatewayメソッドで、NATゲートウェイを削除する。
  6. NATゲートウェイを削除してから60秒待機した後で、release_addressメソッドで、NATゲートウェイにアタッチしていたElastic IPアドレスをリリースする。
  7. detach_internet_gatewayメソッドで、インターネットゲートウェイをVPCからデタッチする。
  8. 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スタックを確認します。

Detail of CloudFormation Stacks 1.

削除対象のリソースが確かに作成されていることがわかります。

次にCloudFormationカスタムリソースの作成状況を確認します。

Detail of CloudFormation Stacks 2.

こちらも確かに作成されていることがわかります。
つまり一度作成されたNATゲートウェイ等が、このカスタムリソースによって削除されたということになります。

ECSタスクを確認します。

Detail of ECS 1.

作成されたタスクに付与されたプライベートアドレスが「10.0.3.171」ということがわかります。

動作確認

準備が整いましたので、状況を確認します。

リソースの状況確認

まずCloudFormationカスタムリソースに関連づけられたLambda関数の実行ログを確認します。

Detail of CloudFormation Stacks 3.

確かに各種リソース削除が正常に実行されたことがわかります。

続いて2つのルートテーブルのルーティング情報を確認します。

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

確かに2つのルートテーブルからデフォルトルートが削除されています。

次にNATゲートウェイの状況を確認します。

Result of CloudFormation Custom Resource 3.

Statusが「Deleted」となっています。
確かにNATゲートウェイが削除されていることがわかります。

NATゲートウェイにアタッチしていたElastic IPアドレスを確認します。

Result of CloudFormation Custom Resource 4.

こちらもリリース済みであることが確認できます。

最後にインターネットゲートウェイの状況を確認します。

Result of CloudFormation Custom Resource 5.

既に削除済みであることがわかります。

コンテナにアクセス

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ゲートウェイ等)を削除する方法を確認しました。

タイトルとURLをコピーしました