LoginSignup
0
0

ラズパイとAWS IoTを使った見守りシステム自作で学んだこと(8)template.yamlで定義するAWSリソースの説明とsam deploy

Last updated at Posted at 2022-08-29

(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の定義が不要になりました。

architecture_w_比較.png

リソース作成の制御、または特定のリソースプロパティに値を割り当てるかどうかを制御する条件を定義します。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等を確認します。
cfn deploy completed.PNG

次回

(9) S3にuploadした映像の確認用web pageに続きます。

追記

修正 2022.10.26

IoT Topic RuleはCount macroを使ったループ処理、CloudWatch Dashboardはset_parameters.py中のfor文でのループ処理で、それぞれの定義文を作成する仕様に変更しました。Conditionsの定義が不要になりました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0