AWS SAMでAPI Gateway、Lambda、Layersをサクッとデプロイできるようにする

##AWS SAMとは

>AWS サーバーレスアプリケーションモデル (SAM、Serverless Application Model) は、サーバーレスアプリケーション構築用のオープンソースフレームワークです。迅速に記述可能な構文で関数、API、データベース、イベントソースマッピングを表現できます。リソースごとにわずか数行で、任意のアプリケーションを定義して YAML を使用してモデリングできます。

テンプレートのYMALファイルに構成を記載することで、
サーバーレスアプリケーションをまとめて管理できます。

インストール方法はこちら↓

https://aws.amazon.com/jp/serverless/sam/

##テンプレートの構成について

基本的に必須なのは、TransformとResourcesのみ。
Resourcesの論理IDにAPI GatewayやLambdaなどの構成を記載していきます。また論理IDはテンプレート内で一意となる必要があります。
論理名でテンプレートの他の部分のリソースを参照したりできます。

ちなみにこの論理IDは、CloudFormationのイベントの論理IDに紐づきます。
※誤字があったりでデプロイ後、変えるのはやめましょう（戒め）

今回は、LambdaExecutionRoleとWebSecurityGroupも一緒に作成してみます。

以下、yamlファイルです。
なんとなく感じを掴んでもらえればと思います。

```YAML
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: sample API
Globals:
  Api:
    OpenApiVersion: 3.0.2
Resources:
  LambdaExecutionRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: "sts:AssumeRole"
      ManagedPolicyArns:
        - !Sub 'arn:aws:iam::aws:policy/AmazonVPCFullAccess'
  WebSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: "sample API SecurityGroup"
      GroupName: sample-API-SecurityGroup
      VpcId: vpc-XXXXXXXX
  RestApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: dev
      OpenApiVersion: 3.0.2
      DefinitionBody:
        Fn::Transform:
          Name: AWS::Include
          Parameters:
            Location: ./sample-api-dev-swagger-apigateway.yaml # API　Gatewayの構成のパス
  SampleFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: sample-function
      Handler: lambda_function.lambda_handler
      Runtime: python3.8
      Role: !GetAtt LambdaExecutionRole.Arn # Roleを紐付け
      CodeUri: ./sample-function/ # Lambdaのコードのパス
      Description: ''
      MemorySize: 128
      Timeout: 25
      Events:
        Api:
          Type: Api
          Properties:
            RestApiId: !Ref RestApi # RestApiと紐付け
            Path: '/v1/sample-function/{param}'
            Method: GET
      VpcConfig:
        SecurityGroupIds:
          - !Ref WebSecurityGroup # WebSecurityGroupと紐付け
        SubnetIds:
          - subnet-XXXXXXXX
          - subnet-XXXXXXXX
      Environment:
        Variables:
          ID: 1234 # 環境変数その１
          PASS: hogehoge # 環境変数その2
      Tags:
        STAGE: dev
      Layers:
      - Ref: SampleLayers # Layersと紐付け
  SampleLayers:
    Type: AWS::Serverless::LayerVersion
    Properties:
      LayerName: sample-layers
      Description: Sample API Resource Layer
      CompatibleRuntimes: 
      - python3.8
      ContentUri: ./sample-layers # Layersのパス
```

YAMLの短縮記法で論理IDを指定していきます。


OpenApiVersionの記載については、こちらも合わせてどうぞ

https://qiita.com/shimajiri/items/cfcd6bda20e0b6f89d7f

##Makefileでデプロイできるようにする
CI的にもなるべく簡単にデプロイできるようにしたいです。
開発環境と本番環境で、設定値などが分けられる様にもします。

```C
feq ($(ENV), dev)
    PROFILE := sample-api-dev
    STACK_NAME := sample-api-dev-stack
    BUCKET_NAME := sample-api-dev-bucket
    TEMPLATE := template_dev.yaml
    TEMPLATE_OUT := template_dev_output.yaml
else ifeq ($(ENV), prd)
    PROFILE := sample-api-prd
    STACK_NAME := sample-api-prd-stack
    BUCKET_NAME := sample-api-prd-bucket
    TEMPLATE := template_prd.yaml
    TEMPLATE_OUT := template_prd_output.yaml
endif

check:
    @echo Profile: $(PROFILE)
    @echo Stack Name: $(STACK_NAME)
    @echo Bucket Name: $(BUCKET_NAME)
    @echo Template File: $(TEMPLATE)

make_bucket:
    aws s3 mb s3://${BUCKET_NAME} --region ap-northeast-1 --profile ${PROFILE}

encrypt_bucket:
    aws s3api put-bucket-encryption --bucket ${BUCKET_NAME} --server-side-encryption-configuration '{"Rules": [{"ApplyServerSideEncryptionByDefault": {"SSEAlgorithm": "AES256"}}]}'

bucket:	make_bucket encrypt_bucket

get_cert:
    aws s3 --profile ${PROFILE} cp s3://${BUCKET_NAME}/certs/ ./sample-layers/certs/ --recursive

del_cert:
    rm -rf ./sample-layers/certs/

pac:
    aws cloudformation package \
    --template ${TEMPLATE} \
    --output-template-file ${TEMPLATE_OUT} \
    --s3-bucket ${BUCKET_NAME} --profile ${PROFILE} \

deploy:
    aws cloudformation deploy \
    --template-file ${TEMPLATE_OUT} \
    --stack-name  ${STACK_NAME} \
    --capabilities CAPABILITY_NAMED_IAM --profile ${PROFILE} \

delete:
    @echo "Are you sure to delete the stack ${STACK_NAME}? [y/N] " && read ans && [ $${ans:- N} = y ]
    aws cloudformation delete-stack --stack-name ${STACK_NAME} --profile ${PROFILE}

all: check get_cert pac deploy del_cert
```

利用する環境のプロファイルを設定し、
環境ごとに分けられるようにします。

```
$ aws configure --profile sample-api-dev
AWS Access Key ID [None]: アクセスキーID
AWS Secret Access Key [None]: シークレットアクセスキー
Default region name [None]: ap-northeast-1
Default output format [None]: 
```

設定の確認↓

```
$ aws configure list
[sample-api-dev]
aws_access_key_id=アクセスキーID
aws_secret_access_key=シークレットアクセスキー
```

初めのみバケットを作成します。
※2回目以降は重複エラーになります。

```
$ make bucket ENV=dev
```

デプロイします。

```
$ make all ENV=dev
```

```
Profile: sample-api-dev
Stack Name: sample-api-dev-stack
Bucket Name: sample-api-dev-bucket
Template File: template_dev.yaml
Template Output File: template_dev_output.yaml
Are you sure to deploy with these settings? [y/N] 
```

以下で完了

```
Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - sample-api-dev-stack
```

##最終的な構成

```
sample-app
│  .gitignore
│  Makefile
│  README.md
│  sample-api-dev-swagger-apigateway.yaml # 開発用API Gatewayのテンプレート
│  sample-api-prd-swagger-apigateway.yaml # 本番用API Gatewayのテンプレート
│　template_dev_output.yaml # デプロイ時に作成されたアウトプットファイル　S3のロケーションなどが記載される。
│  template_dev.yaml  # 開発用のテンプレート
│  template_prd.yaml  # 本番用のテンプレート
│
├─ sample-function # Lambda関数
│    └─ lambda_function.py
│
└─　sample-layers
```

##参考

https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-specification-template-anatomy.html

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html

https://dev.classmethod.jp/articles/cfn-short-form-in-yaml-syntax/