RareJob Tech Blog

レアジョブのエンジニア・デザイナーによる技術ブログです

AWS SAM で Serverless な環境を構築する

どうも、DevOps チームの うすい です。
トトロも鬼滅の刃 無限列車編も見たことがありません。

今回新しいシステムを aws-sam-cli を用いて構築したので簡単にですがそれらの内容を記述したいと思います。AWS SAM 自体の説明は割愛します。

私のマシンの aws cli などのバージョンは下記となります。

$ aws --version
aws-cli/1.16.79 Python/3.7.1 Darwin/19.6.0 botocore/1.12.69

$ sam --version
SAM CLI, version 1.6.2

余談ですが aws-sam-cli のバージョンアップの速度はすごいですね。

AWS SAM では CloudFormation っぽいテンプレートファイルと samconfig.toml ファイルを使用します。samconfig.toml はsam deploy --guided時に生成することもできますが今回は作成しておきます。
あまり情報は見かけませんが、samconfig.toml で ステージング環境 / 本番環境 といった環境に応じたパラメータが適用されるようにします。雰囲気は下記です。

version = 0.1

[default.build.parameters]
profile = "dev"
debug = true
skip_pull_image = false
use_container = true

[default.deploy.parameters]
stack_name = "hogehoge-dev"
s3_bucket = "aws-sam-cli-managed-default-samclisourcebucket-dev"
s3_prefix = "hogehoge"
region = "ap-northeast-1"
profile = "dev"
confirm_changeset = false
capabilities = "CAPABILITY_IAM"
parameter_overrides = "Env=\"dev\""

[stg.build.parameters]
profile = "stg"
debug = true
skip_pull_image = false
use_container = true

[stg.deploy.parameters]
stack_name = "hogehoge-stg"
s3_bucket = "aws-sam-cli-managed-default-samclisourcebucket-stg"
s3_prefix = "hogehoge-stg"
region = "ap-northeast-1"
profile = "stg"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"
parameter_overrides = "Env=\"stg\""

[prd.build.parameters]
profile = "prd"
debug = false
skip_pull_image = false
use_container = true

[prd.deploy.parameters]
stack_name = "hogehoge-prd"
s3_bucket = "aws-sam-cli-managed-default-samclisourcebucket-prd"
s3_prefix = "hogehoge-prd"
region = "ap-northeast-1"
profile = "prd"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"
parameter_overrides = "Env=\"prd\""

一番最後の[prd.deploy.parameters]を例にして説明すると、この部分は[環境.コマンド.aws-sam-cliに渡すパラメータ]となります。このセクションで各パラメータを設定しておくことで、コマンド実行が楽になります(後述)。また、profileAWS CLI の config と同じものを記載してください。parameter_overridesで環境名でEnvパラメータを上書き指定しています。

それでは API Gateway と Lambda Authorizer と DynamoDB を用いた雰囲気tempalte.yamlをご覧ください。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  hoge serverless service

Globals:
  Function:
    Timeout: 3
    Runtime: python3.8
    Environment:
      Variables:
        ENV: !Ref Env
        HOGE_API_HOST: !Ref HogeApiHost
    Layers:
      - !Ref MyLayer
    VpcConfig:
      SecurityGroupIds: !FindInMap [ SecurityGroup, !Ref Env, SecurityGroupIds ]
      SubnetIds: !FindInMap [ SubnetId, !Ref Env, SubnetIds ]        

Parameters:
  Env:
    Type: String
    AllowedValues:
      - prd
      - stg
      - dev
    Default: dev
  HogeApiHost:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /hoge/api/host

Conditions:
  IsDev: !Equals [ !Ref Env, dev ]

Mappings:
  SecurityGroup:
    prd:
      SecurityGroupIds:
        - sg-hoge-prd
    stg:
      SecurityGroupIds: 
        - sg-hoge-stg
    dev:
      SecurityGroupIds:
        - sg-hoge-dev
  SubnetId:
    prd:
      SubnetIds:
        - hoge-prd1
        - hoge-prd2
    stg:
      SubnetIds:
        - hoge-stg1
        - hoge-stg2
    dev:
      SubnetIds:
        - hoge-dev1
        - hoge-dev2

Resources:
  MyApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: !Ref Env
      Auth:
        DefaultAuthorizer: TokenAuth
        AddDefaultAuthorizerToCorsPreflight: False
        Authorizers:
          TokenAuth:
            FunctionPayloadType: TOKEN
            FunctionArn: !GetAtt authorizerFunction.Arn
            Identity:
              Header: Authorization
              ReauthorizeEvery: 0

  authorizerFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: functions/authorizer/
      Handler: app.lambda_handler
      Description: API Gateway Lambda Authorizer
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref Hogetable
        - AmazonSSMReadOnlyAccess

  # Lambda Layer
  MyLayer:
    Type: AWS::Serverless::LayerVersion
    Properties:
      LayerName: hogehoge-service
      Description: ""
      ContentUri: service/
      CompatibleRuntimes:
        - python3.8
    Metadata:
      BuildMethod: python3.8

  # DynamoDB
  HogeTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: Hogetable
      AttributeDefinitions: 
        - AttributeName: id
          AttributeType: N
      KeySchema: 
        - AttributeName: id
          KeyType: HASH
      ProvisionedThroughput: !If [IsDev, { "ReadCapacityUnits": 5, "WriteCapacityUnits": 5 }, !Ref AWS::NoValue]
      BillingMode: !If [IsDev, !Ref AWS::NoValue, PAY_PER_REQUEST]

Outputs:
  WebEndpoint:
    Description: "API Gateway endpoint URL"
    Value: !Sub "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/${Env}/"

ほとんど CloudFormation のテンプレートですね。Globals:とか sam 特有に見えますが CloudFormation でも使えるみたいです(未確認)。
雰囲気だけではなんなので、テクニック的なことも書きますと

  • Parameters:のところでType: AWS::SSM::Parameter::Value<String>を使用し Parameter Store の値を取得
  • Conditions:Envdevでないときに DynamoDB のテーブルを Provisioned ではなく OnDemand で作成

といったことをしています。

実際にステージング環境にデプロイするにはまず

$ sam build --config-env stg

とビルドします。samconfig.toml に色々と設定しているのでコマンド自体はシンプルですね。最終的に下記のような出力を確認できます。

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided

Artifacts が出力されているディレクトリの中には CloudFormation のテンプレートも出力されます。
それではデプロイしてみましょう。出力内容は雰囲気です。toml ファイルでconfirm_changeset = trueとしていますので、ChangeSet の確認が途中で入ります。

$ sam deploy --config-env stg
Uploading to hogehoge-stg/xxxxxxxxxxxxxxxxxxxxxxx  5000 / 5000.0  (100.00%)

    Deploying with following values
    ===============================
    Stack name                 : hogehoge-stg
    Region                     : ap-northeast-1
    Confirm changeset          : True
    Deployment s3 bucket       : aws-sam-cli-managed-default-samclisourcebucket-stg
    Capabilities               : ["CAPABILITY_IAM"]
    Parameter overrides        : {'Env': 'stg'}

CloudFormation stack changeset
---------------------------------------------------------------------------------------------------------------------------------------------------------
Operation                              LogicalResourceId                      ResourceType                           Replacement                          
---------------------------------------------------------------------------------------------------------------------------------------------------------
+ Add                                  MyApiDeploymentxxxxxxxxxx              AWS::ApiGateway::Deployment            N/A                                  
+ Add                                  MyApiStage                             AWS::ApiGateway::Stage                 N/A    
---------------------------------------------------------------------------------------------------------------------------------------------------------

Changeset created successfully. arn:aws:cloudformation:hoge

Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: y    <--------------- y を入力すると反映されます

CloudFormation events from changeset
---------------------------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus                         ResourceType                           LogicalResourceId                      ResourceStatusReason                 
---------------------------------------------------------------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS                     AWS::ApiGateway::RestApi               MyApi                                  -                                    
CREATE_IN_PROGRESS                     AWS::ApiGateway::RestApi               MyApi                                  Resource creation Initiated          
CREATE_COMPLETE                        AWS::ApiGateway::RestApi               MyApi                                  -                                    
〜〜 省略 〜〜
---------------------------------------------------------------------------------------------------------------------------------------------------------

CloudFormation outputs from deployed stack
-----------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs                                                                                                                                                   
-----------------------------------------------------------------------------------------------------------------------------------------------------------
Key                 WebEndpoint                                                                                                                           
Description         API Gateway endpoint URL                                                                                                              
Value               https://hogehogehoge.execute-api.ap-northeast-1.amazonaws.com/stg/                                                                      
-----------------------------------------------------------------------------------------------------------------------------------------------------------

と分かりにくいですがResourceStatusの値が変化しながら処理が進み、最後に template.yamlOutputsで指定した値が出力されます。
一番右のReplacementの値に注意しながら運用していこうと思います。