こんにちは。
前回は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様様です。
投稿者プロフィール
-
学んだことをアウトプットしていきます!
好きなこと:音楽鑑賞🎵 / ドライブ🚗 / サウナ🧖
最新の投稿
- 【LINE】2024年12月28日【LINE】Notify APIからMessaging APIへ移行する③
- 【LINE】2024年11月24日【LINE】Notify APIからMessaging APIへ移行する②
- 【LINE】2024年11月17日【LINE】Notify APIからMessaging APIへ移行する①
- 【RaspberryPi】2024年11月10日【RaspberryPi】一酸化炭素濃度の警報装置を作ってみた①