目的

アプリケーション構築で初めてSAMをデプロイしたので知識の整理として

前提

  • AWSコンソール上でLambdaを作成済み
  • ローカルでのpython環境がある(今回SAM化するLambdaがpython3.9のため)

AWS SAMの概要

AWS公式ドキュメントより抜粋(AWS Serverless Application Modelとは)

AWS Serverless Application Model (AWS SAM) は、Infrastructure as Code (IaC) を使用してサーバーレスアプリケーションを構築するためのオープンソースフレームワークです。

要するにSAMって?

  • AWS CloudFormationの拡張機能
  • CloudFormationよりも短い記述でテンプレートを定義できて迅速に構築することが可能
  • テンプレートとLambdaのプログラムを一括管理できる
  • Terraformに近いイメージでローカルでの修正、デプロイができる

今回のSAM化対象

ConfigルールにてOwnerタグのついていない違反リソースを検知
違反リソースの情報を取得しCloudTrailより作成者名(ARN、UserName)を特定
③CloudTrailから取得した情報を元に違反リソースにOwner:作成者名タグを付与
④結果をCloudWatach Logsに出力
⑤EventBridgeにて定期実行を設定

※ Lambdaの作成自体は説明しません

下記の機能をまるっと権限周り含めて自動でデプロイするスタックを作成する

Config:違反検知
Lambda:tag付与
CloudTrail:リソース作成者特定
EventBridge:Lambdaの自動定期実行
CloudWatach Logs:結果出力

SAM化の流れ

初めてのSAMなので今回はsamコマンドでAWS Quick Start Template を選択して必要なファイルを生成してから、個別に修正する方針とします。

Lambda作成

トリガーや権限なども含めLambdaを作成(今回はpython3.9で作成)

S3バケット作成

名前以外デフォルトで作成
このバケットにSAM化時のファイルやソースコードが保存され、CloudFormation実行時にスタックを作成している

SAM導入準備

ローカルのpython環境をLambdaのバージョンと揃える

pyenv global 3.9.10

AWS SAM CLIインストール
(SAMはCLIで操作するので下記をインストール。mac環境だと以下手順となる)

brew tap aws/tap
brew install aws-sam-cli

SAMのインストール確認

sam --version

作業用のディレクトリ作成(ディレクトリの場所と名前はなんでもいい)

mkdir autotag-sam
cd autotag-sam

SAMテンプレートを実装
sam initを実行すると対話形式でテンプレートの選択が開始される。
今回はHello World Exampleテンプレートを選択し、余分なファイルを削除修正していく

sam init で作られるテンプレートは現時点だと runtime python 3.9 がデフォルトで指定されるようなので、runtime を指定したい場合は init 時に –runtime オプションを指定

sam init

sam init実行時の対話形式 一部抜粋

Which template source would you like to use?
1 - AWS Quick Start Templates
2 - Custom Template Location
Choice: 1 #AWS Quick Start Templatesを選択
Choose an AWS Quick Start application template
1 - Hello World Example
2 - Data processing
3 - Hello World Example with Powertools for AWS Lambda
4 - Multi-step workflow
5 - Scheduled task
6 - Standalone function
7 - Serverless API
8 - Infrastructure event management
9 - Lambda Response Streaming
10 - Serverless Connector Hello World Example
11 - Multi-step workflow with Connectors
12 - GraphQLApi Hello World Example
13 - Full Stack
14 - Lambda EFS example
15 - DynamoDB Example
16 - Machine Learning
Template: 1 #Hello World Exampleを選択
Project name [sam-app]: auto-tag-lambda # 作成するSAMの名前を決定

-----------------------
Generating application:
-----------------------
Name: auto-tag-lambda
Runtime: python3.9
Architectures: x86_64
Dependency Manager: pip
Application Template: hello-world
Output Directory: .
Configuration file: auto-tag-lambda/samconfig.toml

Next steps can be found in the README file at auto-tag-lambda/README.md

公式テンプレートの中身

$ tree
.
├── README.md
├── __init__.py
├── events
│   └── event.json
├── hello_world
│   ├── __init__.py
│   ├── app.py
│   └── requirements.txt
├── samconfig.toml
├── template.yaml
└── tests
    ├── __init__.py
    ├── integration
    │   ├── __init__.py
    │   └── test_api_gateway.py
    ├── requirements.txt
    └── unit
        ├── __init__.py
        └── test_handler.py

必要なファイルのみに修正

  • hello worldディレクトリはautotag-samに改名
$ tree
.
├── README.md
└── autotag-sam
    ├── app.py            # Lambdaで実行されるソース
    ├── samconfig.toml   #Lambdaの設定が記載されるファイル
    └── template.yaml    # SAMの設定ファイル

削除/修正したファイルの用途説明

  • __init__.pyファイル
    パッケージ内のモジュールをインポートする際に使用。今回はapp.pyが単独のモジュールとして機能するので削除
  • events/ディレクトリ, events/event.jsonファイル
    Lambda関数がトリガーされる時に提供されるイベントの例を記述。今回はローカルでテストはしないので削除
  • hello_world/ディレクトリ , app.pyファイル
    実際にLambdaのコードを記述するディレクトリ。app.pyに作成したLambdaのコードを記載する(別に名前はhello_worldじゃなくていい)
  • test/ディレクトリ
    テスト用のコードを格納するディレクトリ。今回はテストを実施しないため削除
  • unit/ディレクトリ
    ここの関数やメソッドの動作をテストするためのディレクトリ。今回は単体テストも実施しないため削除

残りのファイルを修正していく

SAM移行予定のLambdaを選択し、ダウンロードよりファンクションコードSAMファイルの両方を取得する

autotag-sam配下を置き換える

  • app.pyへダウンロードしたファンクションコード内の記述をコピー&ペースト
  • template.ymlへダウンロードしたAWS SAMファイル内の記述をコピー&ペースト

template.ymlを調整する

修正点(下記以外はそのまま)
Handler: lambda_function.lambda_handler

Handler: app.lambda_handler

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: An AWS Serverless Application Model template describing your function.
Resources:
  mawatanabeautotag:        # リソースの識別名(任意の値を設定)
    Type: AWS::Serverless::Function       # リソースタイプ 作りたいLambdaの構成によって変わる
    Properties:
      CodeUri: .            # Lambdaソースコードの場所 今回はapp.pyと同じ階層にあるためカレントディレクトリを指定
      Description: ''
      MemorySize: 128
      Timeout: 15
      Handler: app.lambda_handler   # Lambdaがコールされた時に、最初に実行される関数 
      Runtime: python3.9
      Architectures:
        - x86_64
      EphemeralStorage:
        Size: 512
      EventInvokeConfig:
        MaximumEventAgeInSeconds: 21600
        MaximumRetryAttempts: 2
        DestinationConfig: {}
      PackageType: Zip
      Policies:
        - Statement:
            - Sid: lambdaconfigrule
              Effect: Allow
              Action:  # 今回のlambdaに必要な権限など
                - cloudtrail:LookupEvents
                - config:GetComplianceDetailsByConfigRule
                - config:GetResourceConfigHistory
                - tag:TagResources
                - events:PutEvents
                - ec2:CreateTags
              Resource: '*'
            - Sid: AllowSendMessageToSlack
              Effect: Allow
              Action:
                - logs:CreateLogGroup
                - logs:CreateLogStream
                - logs:PutLogEvents
              Resource: arn:aws:logs:*:*:*
      SnapStart:
        ApplyOn: None
      Events:
        Schedule1:
          Type: Schedule
          Properties:
            Schedule: cron(0 1 ? * FRI *)
      RuntimeManagementConfig:
        UpdateRuntimeOn: Auto

samconfig.tomlを調整する

version = 0.1

[default]
[default.global.parameters]
stack_name = "auto-tag-test" # CloudFormationに作成されるアプリのスタック名

[default.deploy]
[default.deploy.parameters]
s3_bucket = "auto-tag-sam-bucket" # 作成したS3バケットを指定
s3_prefix = "test"                        # 任意のプレフィックスを指定
confirm_changeset = true # 変更セットを確認した上でデプロイする
capabilities = "CAPABILITY_NAMED_IAM" # 固定値 CloudFormationにスタック作成のIAM権限を与える
disable_rollback = false # デプロイ失敗時にスタックをロールバックさせる
region = "ap-northeast-1"
image_repositories = []
resolve_s3 = false

ビルド

既存Lambdaソースの配置と設定ファイルの調整完了後、下記コマンドにてビルドを行う

sam build

コマンドを実行する際には、SAMテンプレートファイル(通常はtemplate.yamltemplate.yml)が存在するディレクトリにいる必要がある。

デプロイ

sam deploy

権限含めたリソースのCloudFromationスタックを作成してくれる

$ sam deploy
~~ 略
CloudFormation events from stack operations (refresh every 5.0 seconds)
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus                           ResourceType                             LogicalResourceId                        ResourceStatusReason                   
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS                       AWS::CloudFormation::Stack               auto-tag-test                            User Initiated                         
CREATE_IN_PROGRESS                       AWS::IAM::Role                           mawatanabeautotagRole                    -                                      
CREATE_IN_PROGRESS                       AWS::IAM::Role                           mawatanabeautotagRole                    Resource creation Initiated            
CREATE_COMPLETE                          AWS::IAM::Role                           mawatanabeautotagRole                    -                                      
CREATE_IN_PROGRESS                       AWS::Lambda::Function                    mawatanabeautotag                        -                                      
CREATE_IN_PROGRESS                       AWS::Lambda::Function                    mawatanabeautotag                        Resource creation Initiated            
CREATE_IN_PROGRESS                       AWS::Lambda::Function                    mawatanabeautotag                        Eventual consistency check initiated   
CREATE_IN_PROGRESS                       AWS::Lambda::EventInvokeConfig           mawatanabeautotagEventInvokeConfig       -                                      
CREATE_IN_PROGRESS                       AWS::Events::Rule                        mawatanabeautotagSchedule1               -                                      
CREATE_IN_PROGRESS                       AWS::Lambda::EventInvokeConfig           mawatanabeautotagEventInvokeConfig       Resource creation Initiated            
CREATE_IN_PROGRESS                       AWS::Events::Rule                        mawatanabeautotagSchedule1               Resource creation Initiated            
CREATE_COMPLETE                          AWS::Lambda::EventInvokeConfig           mawatanabeautotagEventInvokeConfig       -                                      
CREATE_COMPLETE                          AWS::Lambda::Function                    mawatanabeautotag                        -                                      
CREATE_IN_PROGRESS                       AWS::Events::Rule                        mawatanabeautotagSchedule1               Eventual consistency check initiated   
CREATE_IN_PROGRESS                       AWS::Lambda::Permission                  mawatanabeautotagSchedule1Permission     -                                      
CREATE_IN_PROGRESS                       AWS::Lambda::Permission                  mawatanabeautotagSchedule1Permission     Resource creation Initiated            
CREATE_COMPLETE                          AWS::Lambda::Permission                  mawatanabeautotagSchedule1Permission     -                                      
CREATE_COMPLETE                          AWS::Events::Rule                        mawatanabeautotagSchedule1               -                                      
CREATE_COMPLETE                          AWS::CloudFormation::Stack               auto-tag-test                            -                                      
-----------------------------------------------------------------------------------------------------------------------------------------------------------------

Successfully created/updated stack - auto-tag-test in ap-northeast-1

デプロイ実施後


CloudFormation

作成されたLambda(関数名がとても長い。。)

作成されるLambdaの命名について


SAMでLambda関数をデプロイするとき、関数名の末尾に自動生成されたランダムな文字列が付与されます。
これはSAMがCloudFormationのスタック内でリソース名が一意になるようにするためのメカニズムです。

解決策


特定の名前を使いたい場合は、SAMテンプレートで FunctionName プロパティにて設定することができる。
下記設定後、再度ビルド、デプロイを実施することで反映することが可能

template.ymlの修正

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: An AWS Serverless Application Model template describing your function.
Resources:
  mawatanabeautotag:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: AutoTagFunction  # ここにFunctionNameを追加
      CodeUri: .
      Description: ''
~~ 略

テンプレート修正後再度デプロイ

まとめ

LambdaをSAM化をしてみて感じたメリットとしては、一度作成してしまえば他のAWSアカウントなどにも複数のリソースを一括で作成、削除できる部分が強みかなと感じました。
SAM化にあたってやっていることもSAM CLIのインストール、テンプレートの調整のみで実装が出来たのでとても簡単でした。Lamdaコードの修正も手動でコンソール上のコードを書き換える必要がなくなるのはぐっど。
今回は小規模な構成のアプリケーションだったので調整部分もあまりなかったですがもっと大規模になってくるとTerraformなど他ツールの方が向いているパターンもありそうです。