カスタムリソースで複数エンドポイントをSNSトピックに設定する

カスタムリソースで複数エンドポイントをSNSトピックに設定する
この記事をシェアする

はじめに

こんにちは、スカイアーチHRソリューションズのharaです。

今回はAWS公式ドキュメントを参考に、SAMでCloudFormation カスタムリソースを使用して

複数のメールエンドポイントをSNSトピックに登録していきたいと思います。

前提

カスタムリソースとは

CloudFormationのリソースタイプに提供されていないリソースを作成することができます。

作成時は他リソースと同じテンプレート内に定義をすることが可能です。

カスタムリソースの作成にはカスタムリソースプロバイダ (CloudFormationからのリクエストを処理/応答するサービストークン) が必要となります。

カスタムリソースは以下の流れで作成されます。

1.CloudFormationファイル作成

 テンプレート内にカスタムリソース含む作成するリソースの定義とサービストークンを指定します。

2.サービストークンへのリクエスト実行

 CloudFormationを実行し、指定したサービストークンへリクエストを行います。

 サービストークンはリクエストを処理し、事前署名されたURLへSUCCESSまたはFAILEDの応答を返します。

3.CloudFormationへの応答返却

 応答がSUCCESSの場合はStackが完了、応答がないまたはFAILEDの場合はStackは失敗します。

 なお、応答がない場合はCREATE_IN_PROGRESSからステータスが1時間動かないため注意が必要です。

 ※その後削除を試行した場合もDELETE_IN_PROGRESSからステータスが1時間動かず、

  最終的にStackの削除は失敗しますので注意してください。

SAMとは

AWS SAM CLIを利用し、CloudFormationの拡張テンプレートでサーバレスアプリケーションを作成できます。

今回はCloud9の環境でSAMを使用してカスタムリソースを作成していきたいと思います。

作成手順

Cloud9環境作成

※必要に応じて、事前にVPC関連の環境を作成しておきます。

AWS Management ConsoleのCloud9ページから、環境を作成していきます。

[ 環境を作成 ] 画面にて、必須項目を入力して画面下部の [ 作成 ] を押下します。

作成が完了するとCloud9の画面が自動的に立ち上がります。

※立ち上がらない場合はCloud9 IDEのリンクを開きます。

事前準備

念のためインストールされている AWS SAM CLI をアップデートしておきます。

$ sam --version
SAM CLI, version 1.72.0

$ wget https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zip
$ unzip aws-sam-cli-linux-x86_64.zip -d sam-installation
$ sudo ./sam-installation/install --update

$ /usr/local/bin/sam --version
SAM CLI, version 1.97.0

※2023年9月時点の最新バージョンです。

また、テンプレートファイルを以下AWS公式ドキュメントから取得します。

$ wget https://docs.aws.amazon.com/ja_jp/prescriptive-guidance/latest/patterns/samples/p-attach/ba49dc99-c3e1-4c33-a40d-30ef48191720/attachments/attachment.zip
$ unzip attachment.zip
$ unzip custom-resource-sns-endpoints.zip

解凍したcustom-resource-sns-endopointsディレクトリ配下のtemplate.yamlを編集します。

変更箇所は以下となります。

・7行目のメールアドレス

・11行目のRuntime

・64行目以降のSNSトピック名

AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"
Parameters:
  pSNSNotificationsEmail:
    Description: "Email address for SNS notifications"
    Type: CommaDelimitedList
    Default: "xxxx@gmail.com,yyyy@gmail.com"

Globals:
  Function:
      Runtime: python3.9
      Handler: lambda_function.lambda_handler

Resources:
  ######## LAMBDA #########
  rSNSTopicSubscriptionLambda:
    Type: "AWS::Serverless::Function"
    Properties:
      CodeUri: ./lambda/topic/src
      FunctionName: !Join ['-', ["cr", "sns", "topic", "endpoints", "subscription"]]
      Description: "Subscribes multiple endpoints to an SNS topic."
      Role: !GetAtt rSNSTopicSubscriptionRole.Arn

  ######## IAM #########
  rSNSTopicSubscriptionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
        Version: '2012-10-17'
      Path: /
      Policies:
        -
          PolicyName: root
          PolicyDocument:
            Statement:
              - Action:
                  - logs:CreateLogGroup
                  - logs:DescribeLogGroups
                  - logs:CreateLogStream
                  - logs:DescribeLogStreams
                  - logs:PutLogEvents
                Effect: Allow
                Resource:
                  - !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:*'
                Sid: LogAccessPolicy
              - Action:
                  - sns:Unsubscribe
                  - sns:Subscribe
                Effect: Allow
                Resource: '*'
                Sid: SNSSubscription
            Version: '2012-10-17'

  ######## SNS #########
  rSNSTopic:
    Type: 'AWS::SNS::Topic'
    Properties:
      TopicName: 'hara-custom-topic'
  rSNSTopicPolicy:
    Type: 'AWS::SNS::TopicPolicy'
    Properties:
      PolicyDocument:
        Id: 'hara-custom-topic'
        Version: '2012-10-17'
        Statement:
        - Sid: 'hara-custom-topic-notifications'
          Effect: Allow
          Principal:
            Service:
              - 'cloudwatch.amazonaws.com'
              - 'cloudtrail.amazonaws.com'
          Action: 'sns:Publish'
          Resource: !Ref rSNSTopic
      Topics:
      - !Ref rSNSTopic
  rSNSTopicSubscription:
    Type: Custom::SNSSubscription
    Properties:
      ServiceToken: !GetAtt rSNSTopicSubscriptionLambda.Arn
      TopicArn: !Ref rSNSTopic
      SubscriptionEndpoints: !Ref pSNSNotificationsEmail
      SubscriptionProtocol: 'email'

lambda用に、必要なPythonパッケージを一括でインストールするファイルを作成します。

$ touch ./custom-resource-sns-endpoints/lambda/topic/src/requirements.txt

requirements.txtには以下を記載して保存しておきます。

boto3
requests

SAM環境の構築 ( sam build )

custom-resource-sns-endpointsディレクトリに移動し、sam buildします。

sam buildする際はオプションの–use-containerを付与して実行すると、

指定のランタイム (今回だとpython 3.9) のコンテナ環境が立ち上がるため、

ローカルの環境 (Cloud9) にPython3.9が入っていなくても sam build の実行が可能です。

$ cd custom-resource-sns-endpoints/
$ sam build --use-container

        SAM CLI now collects telemetry to better understand customer needs.

        You can OPT OUT and disable telemetry collection by setting the
        environment variable SAM_CLI_TELEMETRY=0 in your shell.
        Thanks for your help!

        Learn More: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-telemetry.html

Starting Build inside a container                                                                                                                                                                                                                   
Building codeuri: /home/ec2-user/environment/custom-resource-sns-endpoints/lambda/topic/src runtime: python3.9 metadata: {} architecture: x86_64 functions: rSNSTopicSubscriptionLambda                                                             

Fetching public.ecr.aws/sam/build-python3.9:latest-x86_64 Docker container image........................................................................................................
Mounting /home/ec2-user/environment/custom-resource-sns-endpoints/lambda/topic/src as /tmp/samcli/source:ro,delegated, inside runtime container                                                                                                     

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Validate SAM template: sam validate
[*] Invoke Function: sam local invoke
[*] Test Function in the Cloud: sam sync --stack-name {{stack-name}} --watch
[*] Deploy: sam deploy --guided
 Running PythonPipBuilder:ResolveDependencies
 Running PythonPipBuilder:CopySource

SAM環境のデプロイ ( sam deploy )

対話形式でデプロイしていきます。

$ sam deploy --guided

Configuring SAM deploy
======================

        Looking for config file [samconfig.toml] :  Not found

        Setting default arguments for 'sam deploy'
        =========================================
        Stack Name [sam-app]: hara-sam-teststack
        AWS Region [ap-northeast-1]: 
        Parameter pSNSNotificationsEmail [xxxx@gmail.com,yyyy@gmail.com]: 
        #Shows you resources changes to be deployed and require a 'Y' to initiate deploy
        Confirm changes before deploy [y/N]: 
        #SAM needs permission to be able to create roles to connect to the resources in your template
        Allow SAM CLI IAM role creation [Y/n]: 
        #Preserves the state of previously provisioned resources when an operation fails
        Disable rollback [y/N]: 
        Save arguments to configuration file [Y/n]: 
        SAM configuration file [samconfig.toml]: 
        SAM configuration environment [default]: 

        Looking for resources needed for deployment:
        Creating the required resources...
Error: Failed to create managed resources: An error occurred (InvalidClientTokenId) when calling the CreateChangeSet operation: The security token included in the request is invalid

デプロイしようとしたらErrorになりました。AWS CLIの認証で弾かれたようです。

以下の方法でCloud9 の認証情報を修正します。

1.Cloud9 の設定 > AWS Settings >Credentials をオフ

2.実行ユーザアカウント (AdministoratorsAccess権限所持) のアクセスキー/シークレットキーをaws configure に設定

再実行してみます。

sam deploy --guided

Configuring SAM deploy
======================

        Looking for config file [samconfig.toml] :  Not found

        Setting default arguments for 'sam deploy'
        =========================================
        Stack Name [sam-app]: hara-sam-teststack
        AWS Region [ap-northeast-1]: 
        Parameter pSNSNotificationsEmail [xxxx@gmail.com,yyyy@gmail.com]: 
        #Shows you resources changes to be deployed and require a 'Y' to initiate deploy
        Confirm changes before deploy [y/N]: 
        #SAM needs permission to be able to create roles to connect to the resources in your template
        Allow SAM CLI IAM role creation [Y/n]: 
        #Preserves the state of previously provisioned resources when an operation fails
        Disable rollback [y/N]: 
        Save arguments to configuration file [Y/n]: 
        SAM configuration file [samconfig.toml]: 
        SAM configuration environment [default]: 

        Looking for resources needed for deployment:

        Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-**********
        A different default S3 bucket can be set in samconfig.toml and auto resolution of buckets turned off by setting resolve_s3=False

        Saved arguments to config file
        Running 'sam deploy' for future deployments will use the parameters saved above.
        The above parameters can be changed by modifying samconfig.toml
        Learn more about samconfig.toml syntax at 
        https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html

        Uploading to hara-sam-teststack/e9f4e5ce66f1c4943082047d9b6ea205  12289766 / 12289766  (100.00%)

        Deploying with following values
        ===============================
        Stack name                   : hara-sam-teststack
        Region                       : ap-northeast-1
        Confirm changeset            : False
        Disable rollback             : False
        Deployment s3 bucket         : aws-sam-cli-managed-default-samclisourcebucket-***********
        Capabilities                 : ["CAPABILITY_IAM"]
        Parameter overrides          : {"pSNSNotificationsEmail": "xxxx@gmail.com,yyyy@gmail.com"}
        Signing Profiles             : {}

Initiating deployment
=====================

        Uploading to hara-sam-teststack/59088f31845286ffa2f7f02d21f829e4.template  2934 / 2934  (100.00%)


Waiting for changeset to be created..

CloudFormation stack changeset
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Operation                                                    LogicalResourceId                                            ResourceType                                                 Replacement                                                
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Add                                                        rSNSTopicPolicy                                              AWS::SNS::TopicPolicy                                        N/A                                                        
+ Add                                                        rSNSTopicSubscriptionLambda                                  AWS::Lambda::Function                                        N/A                                                        
+ Add                                                        rSNSTopicSubscriptionRole                                    AWS::IAM::Role                                               N/A                                                        
+ Add                                                        rSNSTopicSubscription                                        Custom::SNSSubscription                                      N/A                                                        
+ Add                                                        rSNSTopic                                                    AWS::SNS::Topic                                              N/A                                                        
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:**********:changeSet/samcli-deploy1694275489/e195c545-e468-407d-94f0-1932f2fe2c6f


2023-09-09 16:05:00 - Waiting for stack create/update to complete

CloudFormation events from stack operations (refresh every 5.0 seconds)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus                                               ResourceType                                                 LogicalResourceId                                            ResourceStatusReason                                       
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS                                           AWS::IAM::Role                                               rSNSTopicSubscriptionRole                                    -                                                          
CREATE_IN_PROGRESS                                           AWS::SNS::Topic                                              rSNSTopic                                                    -                                                          
CREATE_IN_PROGRESS                                           AWS::IAM::Role                                               rSNSTopicSubscriptionRole                                    Resource creation Initiated                                
CREATE_IN_PROGRESS                                           AWS::SNS::Topic                                              rSNSTopic                                                    Resource creation Initiated                                
CREATE_COMPLETE                                              AWS::SNS::Topic                                              rSNSTopic                                                    -                                                          
CREATE_IN_PROGRESS                                           AWS::SNS::TopicPolicy                                        rSNSTopicPolicy                                              -                                                          
CREATE_IN_PROGRESS                                           AWS::SNS::TopicPolicy                                        rSNSTopicPolicy                                              Resource creation Initiated                                
CREATE_COMPLETE                                              AWS::SNS::TopicPolicy                                        rSNSTopicPolicy                                              -                                                          
CREATE_COMPLETE                                              AWS::IAM::Role                                               rSNSTopicSubscriptionRole                                    -                                                          
CREATE_IN_PROGRESS                                           AWS::Lambda::Function                                        rSNSTopicSubscriptionLambda                                  -                                                          
CREATE_IN_PROGRESS                                           AWS::Lambda::Function                                        rSNSTopicSubscriptionLambda                                  Resource creation Initiated                                
CREATE_COMPLETE                                              AWS::Lambda::Function                                        rSNSTopicSubscriptionLambda                                  -                                                          
CREATE_IN_PROGRESS                                           Custom::SNSSubscription                                      rSNSTopicSubscription                                        -                                                          
CREATE_IN_PROGRESS                                           Custom::SNSSubscription                                      rSNSTopicSubscription                                        Resource creation Initiated                                
CREATE_COMPLETE                                              Custom::SNSSubscription                                      rSNSTopicSubscription                                        -                                                          
CREATE_COMPLETE                                              AWS::CloudFormation::Stack                                   hara-sam-teststack                                           -                                                          
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Successfully created/updated stack - hara-sam-teststack in ap-northeast-1

うまくいきました。

実行結果

CloudFormationを見ると、作成したStackがCREATE_COMPLETEのステータスで表示されています。

リソースの中に、カスタムリソースプロバイダとして作成したLambdaが存在しています。

LambdaのCloudWatchLogsを見ると、サービストークンが発行されています。

また、SNSにはTemplate.yamlに記載したメールアドレスが登録されました。

おわりに

今回はAWSドキュメントから提供されているファイルを基にカスタムリソースを作成しました。

今回はSAMを使用しましたが、提供されているファイルの中にはより簡単に作成ができるような

スクリプト (deploy.sh) もあるので、そちらを利用しても良いと思います。

本記事が何かの参考になりましたら幸いです。

この記事をシェアする
著者:hara
AWS2年目のエンジニア。前職ではAzure、M365サポート等をやっていました。