(2)見守りProjectの設計(名前の設定)の最後に出てきた./template/template.yamlの説明です。
「(4)cliでAWS IoTに新しいモノを登録し証明書を発行」で行うモノの登録と証明書の発行以外の全てAWSリソースをSAMで実装します。
前稿まで以上に理解が浅い部分で、見逃せない間違いがありましたらご指摘頂けると幸いです。検証を繰り返してブラッシュアップは継続します。
実行環境
今回はS3、CloudFront、Lambda、CloudWatchDashboard、IoTRule、IAM policy、IAM Roleをsam cliで実装しますが、IAM userが実行に必要なポリシーをREADMEに案内してあります。
随所でcliがーと言いつつ以下はAWS IAMのマネジメントコンソールよりIAM userにアタッチしました。
AWS管理ポリシー
IAM userにAWSIoTFullAccessをアクセス権限追加します。
ユーザー定義の管理ポリシー
/Monitoring-system-with-Raspberry-Pi-and-AWS/user policyに保存してある8つのポリシーテキストを使ってAWS IAMコンソールより自分カスタムのポリシーを作成してIAM userに権限を追加します。
ファイル中の{your accountID}の部分は自分のアカウントIDに置き換えます。
また、複数の試作で使い回しているため若干不要な権限が含まれている可能性があります。流用されることがありましたら自己責任でお願いいたします。
/home/user/Monitoring-system-with-Raspberry-Pi-and-AWS
・
┣ user policy
┃ ┣ SAM-Changeset-Create-Policy
┃ ┣ SAM-CloudWatch-Custom-Dashboard-Create-policy
┃ ┣ SAM-Cloudformation-Access-Policy
┃ ┣ SAM-Cloudfront-Create-Policy
┃ ┣ SAM-Lambda-Create-Policy
┃ ┣ SAM-List-Resource-Groups-Policy
┃ ┣ SAM-Loggroups-Create-Policy
┃ ┗ SAM-S3-Dev-Policy
┃
CloudFormationのCount Macroテンプレートのdeploy
ラズパイの数に応じてIoT Topic Ruleを作成する為にCloudFormationのCountというMacro機能を利用するのでこちら(aws-cloudformation-templates/CloudFormation/MacrosExamples/Count)を参考に予めdeployしておきます。
AWS Serverless Application Model(SAM)
AWS Serverless Application Model(SAM)というCloudformationの拡張機能を利用してAWSリソースをラズパイのコマンドラインから実装します。
SAMは7種類のリソースを対象にしていますが、それ以外のCloudFrormation templateのbuildとdeployにも対応しているので一括実装に利用します。
template.yaml
template.yamlはCloudFormationでAWSリソースを実装する為の定義ファイルとなります。以下にtemplateの各セクションを説明します。
FormatVersion
AWS CloudFormation テンプレートのバージョンです。SAMのsample templateの標準設定を使用します。
AWSTemplateFormatVersion: '2010-09-09'
Transform
AWS サーバーレス アプリケーション モデル (AWS SAM) のバージョンと予めdeployしたCount macroを使うことを明示します。SAMのバージョンはsample templateの標準設定を使用します。
Transform:
- AWS::Serverless-2016-10-31
- Count
Globals
今回Lambda関数は一つだけですが、runtime等を変更するのにyamlの先頭部分に出しておいたほうが便利なのでFunctionから分離しました。
Globals:
Function:
Runtime: python3.9
Timeout: 15
MemorySize: 128
Architectures:
- arm64
Parameters
実行時 (スタックを作成または更新するとき) にテンプレートに渡す値です。リソースから参照することが出来ます。
(2)見守りProjectの設計(名前の設定)で出力したtemplate_head.txtに相当します。このリポジトリーを使って異なるProjectを作る際にtemplate.yamlの中で書き換えるのはこのParametersとDashboardです。前述したとおりtemplate.yamlはパラメーターの対話入力後に自動出力されます。
(2)で例に示したラズパイ4台を設置するMyLivingというProjectのParametersを再掲します。
####################################
Parameters:
############Customizable############
ProjectName:
Type: String
Default: MyLiving
Place:
Type: String
Default: CatBedNo1,CatBedNo2,CatBedNo3,CatBedNo4
NumPlace:
Type: Number
Default: 4
OrgBucketName:
Type: String
Default: neko-mimamori
NameTag:
Type: String
Default: MyLiving
############# Fixed #############
EventPrefix:
Type: String
Default: 'emr'
#################################
Conditions
【2022.10.26削除】
IoT Topic RuleはCount macroを使ったループ処理、CloudWatch Dashboardはset_parameters.py中のfor文でのループ処理で、それぞれの定義文を作成する仕様に変更したためConditionsの定義が不要になりました。
リソース作成の制御、または特定のリソースプロパティに値を割り当てるかどうかを制御する条件を定義します。if文で条件分岐させるのと同様の使い方をしています。
Conditionsセクションを使った理由は、IoTRuleとDashBoardの二つのリソースの数をラズパイ台数に合わせて変える必要があるからです。
Parametersセクションのデフォルト値の有無を分岐条件に使い、IoTRuleとDashBoardの数をラズパイの数に合わせました。
IoTRuleの数を決める条件は以下です。IoTRule1からIoTRule4のResourcesを1つずつ個別に記述しておいて、ParameterのDefaultがブランクでなければConditionsでリソースを有効にします。
CloudWatchDashboardはIoTRuleとリソースの定義方法が異なり、筆者の理解(2022.08.18時点)ではラズパイ1台の時のDashboard、2台の時の・・・4台の時のDashBoardというような定義しか出来ませんでした。(count macroを使えば出来そうですが夏休みの宿題完成せず)
そこでConditionの与え方もラズパイ1台の時のDashboard・・・ラズパイ4台の時のDashboardという分岐をする為に以下のようになりました。
Resources
OriginS3Bucket
イベント録画したmp4ファイルの保存先とCloudFrontのオリジンになります。
OriginS3Bucket:
Type: AWS::S3::Bucket
DeletionPolicy: Retain
Properties:
BucketName: !Sub ${OrgBucketName}
PublicAccessBlockConfiguration:
BlockPublicAcls: True
BlockPublicPolicy: True
IgnorePublicAcls: True
RestrictPublicBuckets: True
OriginS3BucketPolicy
CloudFrontのOriginAccessIdentity(OAI)だけGetObjectを許可するBucket policyです。この10連投書いている途中で今後はOACを使いましようと言うリリースがありました、今後はこちらを使いたいと思います。
OriginS3BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref OriginS3Bucket
PolicyDocument:
Statement:
- Action: s3:GetObject
Effect: Allow
Resource: !Sub arn:aws:s3:::${OriginS3Bucket}/*
Principal:
AWS: !Sub arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${CloudFrontOriginAccessIdentity}
CloudFrontDistribution
CloudFrontを使う目的はOAIを介したアクセス制限とhttpsリダイレクトです。見られて問題のあるデータは搬送しないのですが一応標準的な仕組みづくりの練習として設定してあります。目的がCDN利用ではないのでキャッシュを無効にしています。
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: true
HttpVersion: http2
Origins:
- Id: S3Origin
DomainName: !Sub "${OriginS3Bucket}.s3.${AWS::Region}.amazonaws.com"
S3OriginConfig:
OriginAccessIdentity: !Sub origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}
DefaultRootObject: index.html
Comment: !Sub ${AWS::StackName} distribution
DefaultCacheBehavior:
TargetOriginId: S3Origin
ForwardedValues:
QueryString: false
ViewerProtocolPolicy: redirect-to-https
AllowedMethods:
- GET
- HEAD
DefaultTTL: 0
MaxTTL: 0
MinTTL: 0
IPV6Enabled: false
CloudFrontOriginAccessIdentity
S3BucketがGetObjectを許可するリソースです。
CloudFrontOriginAccessIdentity:
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: !Ref AWS::StackName
LambdaFunction
録画したmp4ファイルがS3にuploadされたら、mp4のファイル名を01.mp4に書き換えて「モノの名前=ラズパイの設置場所」ごとのプレフィックスに移動します。CloudFrontが呼び出すindex.htmlではプレフィクスとファイル名を固定しているため、uploadしたファイルの名前をLambda関数でリネームしています。
Lambda関数(app.py)は(9)S3にuploadした映像確認用web pageで紹介します。
LambdaFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: function/
FunctionName: !Sub ${ProjectName}Func
Handler: app.lambda_handler
Environment:
Variables:
ORG_BACKET: !Sub ${OrgBucketName}
Events:
S3Event:
Type: S3
Properties:
Bucket: !Ref OriginS3Bucket
Events: s3:ObjectCreated:*
Filter:
S3Key:
Rules:
- Name: prefix
Value: !Sub ${EventPrefix}
Role: !GetAtt LambdaFunctionRole.Arn
Tags:
Name: !Sub ${NameTag}
LambdaFunctionLogGroup
Lambda関数のlog groupです。7日経ったら消します。
LambdaFunctionLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /aws/lambda/${LambdaFunction}
RetentionInDays: 7
LambdaFunctionRole
Lambda関数にS3 ObjectのGet、Put、Deleteとログの吐き出しを許可します。
LambdaFunctionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action: "sts:AssumeRole"
Principal:
Service: lambda.amazonaws.com
Policies:
- PolicyName: !Sub ${ProjectName}FuncPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
Resource:
- "arn:aws:logs:*:*:*"
- Effect: "Allow"
Action:
- "s3:GetObject"
- "s3:PutObject"
- "s3:DeleteObject"
Resource:
- !Sub "arn:aws:s3:::*"
IoTRuleActionRole
AWS IoTにcloudwatchにMetricDataを出力するアクションを許可します。
IoTRuleActionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service:
- "iot.amazonaws.com"
Action:
- "sts:AssumeRole"
RoleName: !Sub ${ProjectName}IoTRuleActionRole
Policies:
- PolicyName: !Sub ${ProjectName}CustomMetricsPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action: "cloudwatch:PutMetricData"
Resource: "*"
IoTRule
【2022.10.26修正】
set_parameters.pyで入力されて予めParametersのNumPlaceに渡された数だけCountループを回してラズパイの数だけIoTRuleを定義します。
場所名=モノの名前はParametersのPlaceにラズパイの数だけ格納されるのでListとして参照します。
ラズパイを最大4台設置する為に予めIoTRule1からIoTRule4の4つのルールを定義しておきます。
ラズパイが1台ならIoTRule1だけを有効にして、ラズパイが2台ならIoTRule1とIoTRule2を有効にして、という具合にConditionsでリソースの有効/無効を決めます。
IoTRule:
Type: AWS::IoT::TopicRule
Properties:
RuleName: !Join ["", [!Select ["%d", !Split [",", !Sub ",${Place}"]], "IoTRule"]]
TopicRulePayload:
Actions:
- CloudwatchMetric:
MetricName: detect_count
MetricNamespace: !Join ["", [!Select ["%d", !Split [",", !Sub ",${Place}"]], "/count"]]
MetricUnit: None
MetricValue: ${detect_count}
RoleArn: !GetAtt IoTRuleActionRole.Arn
AwsIotSqlVersion: "2016-03-23"
Description: String
RuleDisabled: false
Sql: !Join ["", ["SELECT * FROM '", !Select ["%d", !Split [",", !Sub ",${Place}"]], "/count'"]]
Tags:
- Key: Name
Value: !Sub ${NameTag}
Count: !Ref NumPlace
CloudWatchDashboard
【2022.10.26修正】
set_parameters.pyの中のforループでラズパイの数だけCloudWatch Dashboard定義文を出力する仕様に変更しました。Count macroを使ったyaml中でのループ処理を検討しましたが解決方法を見つけることが出来ませんでした。
DashboardBodyの定義がjsonになり(これ以外分からなかった)、ラズパイ1台の時、ラズパイ4台の時というリソースを単位化して使い回しが出来ない定義になりました。
本当はIoTルールのようにDashboard1、Dashboard2・・・と定義して連結する又はfor文の中でテンプレートを増殖させたかったので近いうちにmacro等に挑戦したいです。
DashboardBodyのjsonの生成方法は「CloudFormationを利用してCloudWatchダッシュボードを設置してみた」等を参考にさせて頂きました。
CloudWatchDashboard1:
Condition: OnePlace
Type: AWS::CloudWatch::Dashboard
Properties:
DashboardName: !Sub ${ProjectName}Dashboard
DashboardBody: !Sub
'{
"widgets": [
{
"height": 6,
"width": 12,
"y": 0,
"x": 0,
"type": "metric",
"properties": {
"metrics": [
[
"${Place1}/count",
"detect_count"
]
],
"view": "timeSeries",
"stacked": false,
"region": "ap-northeast-1",
"title": "${Place1}",
"period": 60,
"stat": "Sum"
}
}
]
}'
・
・
・
CloudWatchDashboard4:
Condition: FourPlaces
Type: AWS::CloudWatch::Dashboard
Properties:
DashboardName: !Sub ${ProjectName}Dashboard
DashboardBody: !Sub |
{
"widgets": [
{
"height": 6,
"width": 12,
"y": 0,
"x": 0,
"type": "metric",
"properties": {
"metrics": [
[
"${Place1}/count",
"detect_count"
]
],
"view": "timeSeries",
"stacked": false,
"region": "ap-northeast-1",
"title": "${Place1}",
"period": 60,
"stat": "Sum"
}
},
・
・
・
},
{
"height": 6,
"width": 12,
"y": 6,
"x": 12,
"type": "metric",
"properties": {
"metrics": [
[
"${Place4}/count",
"detect_count"
]
],
"view": "timeSeries",
"stacked": false,
"region": "ap-northeast-1",
"title": "${Place4}",
"period": 60,
"stat": "Sum"
}
}
]
}
Outputs
DomainName:
CloudFrontのドメイン名をcfnのマネコンまたはコマンドラインから参照できるようにOutputしました。
Outputs:
DomainName:
Value: !GetAtt CloudFrontDistribution.DomainName
sam build/deploy
ラズパイのコマンドラインでtemplate.yamlが保存されているdirectoryに移動して実行します。
cd ./template
buildを実行します。
sam build
このリポジトリーのtemplate.yamlではリソース名(例:FunctionName(Lambda関数名))を自分で指定してますのでdeployの際に--capabilitiesオプションでCAPABILITY_NAMED_IAMを指定する必要があります。
stack名もProject名と同じにして統一しておきます。
sam deploy --guided --capabilities CAPABILITY_NAMED_IAM --stack-name MyLiving
Parametersで事前に定義してある内容はそのままリターンします。
最後に[Y/N]で聞いてくる部分は全てYを入力します。
最後の2行はそのままリターンします。
Setting default arguments for 'sam deploy' =========================================
Stack Name [MyLiving]:
AWS Region [ap-northeast-1]:
Parameter ProjectName [MyLiving]:
Parameter Place1 [CatBed1]:
Parameter Place2 [CatBed2]:
Parameter Place3 [CatBed3]:
Parameter Place4 [CatBed4]:
Parameter OrgBucketName [neko-mimamori]:
Parameter NameTag [MyLiving]:
Parameter EventPrefix [emr]:
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy Confirm changes before deploy [Y/n]: Y
#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]: Y
#Preserves the state of previously provisioned resources when an operation fails Disable rollback [Y/n]: Y
Save arguments to configuration file [Y/n]: Y
SAM configuration file [samconfig.toml]:
SAM configuration environment [default]:
CloudFormationのマネジメントコンソールからStatus、リソース、Output等を確認します。
次回
(9) S3にuploadした映像の確認用web pageに続きます。
追記
修正 2022.10.26
IoT Topic RuleはCount macroを使ったループ処理、CloudWatch Dashboardはset_parameters.py中のfor文でのループ処理で、それぞれの定義文を作成する仕様に変更しました。Conditionsの定義が不要になりました。