【LINE】Notify APIからMessaging APIへ移行する③

こんにちは。

前回はAWSマネコンでリソースを1つずつ用意して構築できたので、今回はSAM(IaC)で構築していきます。

インフラをコードで管理できるということは、Git管理できるということです。そして、同じ環境をマネコンでポチポチする手順を作らなくてよくなります。dev環境でうまくいく手順をstgやprod環境に反映していくと思いますが、ソワソワしながらダブルチェックとかしなくていいんです。めっちゃ画期的ですよね⭐

ただ、自分のイメージとしては、マネコンで一通り構築できたことが前提となると思うので、実際にやっていることは前回の記事をご参照ください!

では、SAMにしていきます。

1. テンプレートの最初に必要なモノ

パラメータを定義できるようにしておきます。

最終的に下記になりましたが、必要に応じて追加していく形です。

パラメータの値はGit管理しちゃダメなので、デプロイはShell Scriptで.envファイルを読み込んで行います。

AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"

Parameters:
    # アプリ名
    AppName:
        Type: "String"

    # パラメータストア名 - ユーザーID
    ParameterStoreNameUserId:
        Type: "String"

    # パラメータストア名 - チャンネルアクセストークン
    ParameterStoreNameChannelAccessToken:
        Type: "String"

    # ドメイン
    Domain:
        Type: "String"

    # APIのURL
    ApiUrl:
        Type: "String"

    # ARN - ACMのSSL証明書 - 東京
    ArnAcmSslCertficateTokyo:
        Type: "String"

    # SNS - 通知先メールアドレス
    Email:
        Type: "String"

2. CloudWatchロググループの作成

小難しい設定はないので、これだけです。

CloudWatchLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
        # ロググループ名 ParametersのAppNameの値が設定されます。
        LogGroupName: !Ref "AppName"
        # 保持を1週間に設定
        RetentionInDays: 7

3. SNS

SNSはトピックとサブスクリプションが必要なので、設定を記載していきます。

# トピック
SNSTopic:
    Type: AWS::SNS::Topic
    Properties:
        # 表示名
        DisplayName: !Ref "AppName"

# サブスクリプション
SNSSubscription:
    Type: AWS::SNS::Subscription
    Properties:
        # トピックのARN
        TopicArn: !Ref "SNSTopic"
        # プロトコル。メール通知は「email」
        Protocol: "email"
        # メール通知先。自分のメアドが入る
        Endpoint: !Ref "Email"

4. Lambda

Lambda関数に加え、IAMロールとIAMポリシーの設定も行います。

おそらく、Pythonコードは別のリポジトリにして、CodeUriはParametersで設定するのが正しいような気がしてますが、Lambda関数は1つだけのシンプルな構成なので共存してもらいます💡

Lambda:
    Type: "AWS::Serverless::Function"
    Properties:
        # Pythonコードのパス
        CodeUri: "./src/"
        # 関数名
        FunctionName: !Ref "AppName"
        # IAMロールのARN
        Role: !GetAtt "IamRole.Arn"
        # Python最新の3.13を採用
        Runtime: "python3.13"
        # app.py
        Handler: "app.lambda_handler"
        # 環境変数
        Environment:
            Variables:
                APP_NAME: !Ref "AppName"
                PARAMETER_STORE_NAME_USER_ID: !Ref "ParameterStoreNameUserId"
                PARAMETER_STPRE_NAME_CHANNEL_ACCESS_TOKEN: !Ref "ParameterStoreNameChannelAccessToken"
                SNS_TOPIC_ARN: !Ref "SNSTopic"
        # アーキテクチャ
        Architectures:
            - "arm64"
        # タイムアウト(秒)
        Timeout: 30
        # イベント
        Events:
            # 後述のAPI Gatewayの設定
            GetApi:
                Type: "Api"
                Properties:
                    Path: "/"
                    Method: "post"
                    RestApiId: !Ref "ApiGateway"

# IAMロール
IamRole:
    Type: "AWS::IAM::Role"
    Properties:
        RoleName: !Ref "AppName"
        AssumeRolePolicyDocument:
            Version: "2012-10-17"
            Statement:
                # Lambda用のロール
                - Effect: "Allow"
                  Principal:
                      Service: "lambda.amazonaws.com"
                  Action: "sts:AssumeRole"

# IAMポリシー
IamPolicy:
    Type: "AWS::IAM::Policy"
    Properties:
        PolicyName: !Ref "AppName"
        PolicyDocument:
            Version: "2012-10-17"
            Statement:
                # パラメータストアから取得を許可
                - Effect: "Allow"
                  Action:
                      - "ssm:GetParameters"
                  Resource:
                      - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter${ParameterStoreNameChannelAccessToken}"
                      - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter${ParameterStoreNameUserId}"
                # CloudWatchへのログ書き込みを許可
                - Effect: "Allow"
                  Action:
                      - logs:DescribeLogStreams
                      - logs:CreateLogStream
                      - logs:PutLogEvents
                  Resource: !GetAtt "CloudWatchLogGroup.Arn"
                # SNSトピックへのメッセージ発行を許可
                - Effect: "Allow"
                  Action:
                      - "sns:Publish"
                  Resource:
                    - !Ref "SNSTopic"
        Roles:
            # IAMロールへの紐づけ
            - !Ref "IamRole"

5. API Gateway

さて、ここが鬼門です。

API、キー、使用量プランとその紐づけ、カスタムドメインとマッピングを用意します。

※ステージは「v1」でいきます。

# API
ApiGateway:
    Type: "AWS::Serverless::Api"
    Properties:
        Name: !Ref "AppName"
        # リージョンタイプ
        EndpointConfiguration: "REGIONAL"
        # ステージはv1
        StageName: "v1"
        # APIキーはヘッダーで受け取る
        ApiKeySourceType: "HEADER"
        # APIキーを必須にする
        Auth:
            ApiKeyRequired: true

# APIキー
ApiGatewayApiKey:
    Type: "AWS::ApiGateway::ApiKey"
    Properties:
        Name: !Ref "AppName"
        # キーを有効化
        Enabled: true
        # どのAPIのどのステージかを設定
        StageKeys:
            - RestApiId: !Ref ApiGateway
              StageName: "v1"

# 使用量プラン
ApiGatewayUsagePlan:
    Type: "AWS::ApiGateway::UsagePlan"
    Properties:
        UsagePlanName: !Ref "AppName"
        ApiStages:
            # どのAPIのどのステージかを設定
            - ApiId: !Ref ApiGateway
              Stage: "v1"

# APIキーと使用量プランの紐づけ
ApiGatewayUsagePlanKey:
    Type: "AWS::ApiGateway::UsagePlanKey"
    Properties:
        KeyId: !Ref ApiGatewayApiKey
        KeyType: "API_KEY"
        UsagePlanId: !Ref ApiGatewayUsagePlan

# カスタムドメイン
ApiGatewayCustomDomain:
    Type: "AWS::ApiGateway::DomainName"
    Properties:
        # APIのURL設定
        DomainName: !Ref "ApiUrl"
        EndpointConfiguration:
            Types:
                - "REGIONAL"
        # ACMで取得したSSL証明書のARN
        RegionalCertificateArn: !Ref "ArnAcmSslCertficateTokyo"

# APIマッピング
ApiGatewayBasePathMapping:
    Type: "AWS::ApiGateway::BasePathMapping"
    Properties:
        # 今回作成するAPIのv1を設定
        DomainName: !Ref "ApiUrl"
        RestApiId: !Ref "ApiGateway"
        Stage: "v1"

APIマッピングですが、初回はコメントアウトしてください。

APIが作られていない段階でマッピングを作成しようとして、エラーとなってしまいます。

もしかしたら、Outputsを使えば出来上がったAPI GatewayのIDを取得していい感じに作ってくれるのかもですが、自分の中で「APIマッピングは初回だけはコメントアウト」と染みついてしまいました😅

6. Route53

あとはURLを付与するだけです。頑張って準備したAPIに紐づけます。

Route53:
    Type: AWS::Route53::RecordSet
    Properties:
        HostedZoneName: !Sub "${Domain}."
        Name: !Sub "${ApiUrl}."
        Type: A
        AliasTarget:
            DNSName: !GetAtt "ApiGatewayCustomDomain.RegionalDomainName"
            HostedZoneId: !GetAtt "ApiGatewayCustomDomain.RegionalHostedZoneId"

7. ビルド・デプロイ

成果物をWEBサービスとして展開します。

sam build
sam deploy \
    --stack-name (アプリ名) \
    --parameter-overrides \
        AppName=(アプリ名) \
        ParameterStoreNameUserId=(自分のLINEユーザーID) \
        ParameterStoreNameChannelAccessToken=(LINEボットのチャンネルアクセストークン) \
        Domain=(ドメイン) \
        ApiUrl=(エンドポイントURL) \
        ArnAcmSslCertficateTokyo=(ACMで取得したSSL証明書のARN) \
        Email=(自分のメアド)

自分はIDとかトークンとかは、SSMのパラメータストアに置いてるので、AWS CLIで取得するようにしてますが、「.env」にベタ打ちでもいいと思います。ただし、Git管理には含めないように!

8. デプロイ結果の確認

こんな感じの成功メッセージがでればOKです。

APIマッピングのコメントアウトを外して、再度6のビルド・デプロイを行ってください。

一応CloudFormationも見に行きます。

UPDATE_COMPLETEとなってるので、やはりOKです😊

最後の動作チェックです。

curl -X POST \
    -H "Content-Type: application/json" \
    -H "X-API-Key: (API Gatewayで生成したキー)" \
    -d '{"type": "text", "text": "夢じゃない あれもこれも\n今こそ胸を 張りましょう!"}' \
    https://(Route53のドメイン)

胸を張れる瞬間ですね!!

今年の紅白歌合戦はB’zが初出場とのことで楽しみです🎤🎸

9. 最後に

SAMで(自分向け)LINE通知サービスの構築ができました。

よいお年を!

10. 参考

参考にしたサイトはありません。ChatGPT様様です。

投稿者プロフィール

KatoShingo
学んだことをアウトプットしていきます!
好きなこと:音楽鑑賞🎵 / ドライブ🚗 / サウナ🧖

関連記事

  1. 【LINE】Notify APIからMessaging APIへ移行す…

  2. 【LINE】Notify APIからMessaging APIへ移行す…

最近の記事

制作実績一覧

  1. Checkeys