AWS SAM 環境別にデプロイ

## はじめに
Lamdaの稼働環境ごとに異なる設定値を与えてデプロイしたくなりました。
AWS-SAMのテンプレートを使用して、環境ごとに設定を出し分けてみたので皆様にも共有いたします。

この記事ではAWS-SAMにフォーカスを当てております。
ただ、AWS-SAM自体がAWS CloudFormationのサービスをベースとしているので、そちらも併せてお調べいただくとより一層AWS-SAMに対して理解が深まると思います。

https://aws.amazon.com/jp/cloudformation/resources/templates/

Lambda歴がまだ浅いので非効率なやり方をしているかもしれません。何かお気づきの点・気になる点ございましたらご指摘いただけると幸いです！

さらに良い方法をご存じの方がおられましたら、教えていただけますと嬉しいです😃！！

## どんな記事？
Lambda関数のデプロイ環境別（ステージングや本番など）に、異なる設定値を与えたい場面ってありませんか？この記事では、AWS-SAMのテンプレート（template.yaml）を用いた設定値の出し分けについて解説しています。なお、テンプレートの記述形式はYAMLを採用しています。

## とりあえず結論。　SAMテンプレート記述例

### 想定状況
Lambda関数のデプロイ先が3つあり、環境ごとに異なる環境変数やVPC、Roleなどの設定を付与したい。

- 開発環境（dev）
- ステージング環境（stg）
- 本番環境（prd）

### 実行コマンド
```sh
sam <sub-command> --parameter-overrides Env=dev
```

### Mappingsと!FindInMapを使う方法
```template.yaml
Resources:
  TestFunction:
    Type: AWS::Serverless::Function
    Properties:
      Role: !FindInMap [Role, !Ref Env, Role]
      VpcConfig:
        SubnetIds: !FindInMap [VpcConfig, !Ref Env, SubnetIds]
        SecurityGroupIds: !FindInMap [VpcConfig, !Ref Env, SecurityGroupIds]


Mappings:
  Role:
    dev:
      Role: arn:aws:iam::000000000000:role/test-function-role
    stg:
      Role: arn:aws:iam::111111111111:role/test-function-role
    prd:
      Role: arn:aws:iam::222222222222:role/test-function-role

  VpcConfig:
    dev:
      SecurityGroupIds:
        - sg-xxxxxxxx
      SubnetIds:
        - subnet-xxxxxxxxxxxxxxxxx
    stg:
      SecurityGroupIds:
        - sg-xxxxxxxxxxxxxxxxx
      SubnetIds:
        - subnet-xxxxxxxxxxxxxxxxx
        - subnet-xxxxxxxxxxxxxxxxx
    prd:
      SecurityGroupIds:
        - sg-xxxxxxxxxxxxxxxxx
      SubnetIds:
        - subnet-xxxxxxxxxxxxxxxxx
        - subnet-xxxxxxxxxxxxxxxxx
        
Parameters:
  Env:
    Type: String
    Default: dev
    AllowedValues: [dev, stg, prd]
```

### Conditionsと!Ifを使う方法
```template.yaml
Resources:
  TestFunction:
    Type: AWS::Serverless::Function
    Properties:       
      Role: !If [IsDev, 'arn:aws:iam::000000000000:role/test-function-role',
            !If [IsStg, 'arn:aws:iam::111111111111:role/test-function-role',
            !If [IsPrd, 'arn:aws:iam::222222222222:role/test-function-role', !Ref AWS::NoValue]]]

Conditions:
  IsDev: !Equals [!Ref Env, dev]
  IsStg: !Equals [!Ref Env, stg]
  IsPrd: !Equals [!Ref Env, prd]

Parameters:
  Env:
    Type: String
    Default: dev
    AllowedValues: [dev, stg, prd]
```

### Conditionsと!GetAttを使う方法
```template.yaml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31

Resources:

  RoleDev:
    Type: AWS::IAM::Role
    Condition: IsDev
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action: "sts:AssumeRole"
            Principal:
              Service: lambda.amazonaws.com

  RoleStg:
    Type: AWS::IAM::Role
    Condition: IsStg
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action: "sts:AssumeRole"
            Principal:
              Service: lambda.amazonaws.com

  RolePrd:
    Type: AWS::IAM::Role
    Condition: IsPrd
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action: "sts:AssumeRole"
            Principal:
              Service: lambda.amazonaws.com

  TestFunction:
    Type: AWS::Serverless::Function
    Properties:
      Role: !If [IsDev, !GetAtt RoleDev.Arn,
            !If [IsStg, !GetAtt RoleStg.Arn,
            !If [IsPrd, !GetAtt RolePrd.Arn, !Ref AWS::NoValue]]]

Conditions:
  IsDev: !Equals [!Ref Env, dev]
  IsStg: !Equals [!Ref Env, stg]
  IsPrd: !Equals [!Ref Env, prd]

Parameters:
  Env:
    Type: String
    Default: dev
    AllowedValues: [dev, stg, prd]

```

## 補足

### AWS-SAM
AWS-SAMとは、サーバーレス環境を自動構築してくれるAWS公式のツールです。Lambda関数等の設定をテンプレートファイル（YAML or JSON）に記述し、コマンドを実行するだけで、ローカル環境でのLambda関数のテスト実行やデプロイまで出来る優れモノです。

<https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/what-is-sam.html>

<https://aws.amazon.com/jp/blogs/news/aws-hands-on-for-beginners-03/>

### SAMテンプレート
以下でSAMテンプレートの各セクションについて概要的な説明がなされています。SAMテンプレートに初めて触れる方は、先に一読されることをお勧めします。

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

```template.yaml
Transform: AWS::Serverless-2016-10-31　
　SAMテンプレートをCloudFormationテンプレートに変換する。
Globals:
  共通の設定。複数の関数があった場合、それぞれの関数はこの設定を継承する。
Description:
  テンプレートに関するコメント。
Metadata:
  メタデータ。
Parameters:
  テンプレート内で変数として利用する。また入力パラメータで任意な値を与えることができる。
　例えば、以下ではEnvという変数を定義している。この変数はSAM-CLIコマンド実行時に上書くことができる。
  Env:
    Type: String
    Default: dev
    AllowedValues: [dev, stg, prd]
Mappings:
  キーと名前付きの一連の値。
Conditions:
  TrueもしくはFalseだけを返す変数。
Resources:
  使用したいAWSサービス。以下はLambda関数の場合。
  TestFunction:
    Type: AWS::Serverless::Function
Outputs:
  他のスタックに情報を与える、応答として返す、AWS CloudFormationコンソールで表示する出力値を設定。
```

### SAM-CLI設定ファイル
SAMでは、AWS-SAM-CLIコマンドを用いてすべてを操作します。
AWS-SAM-CLIコマンドは、TOML形式の設定ファイルを読み込むことが出来ます。
デフォルトのファイル名はsamconfig.tomlです。

https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html

こんな感じのファイルです。
ここでは、`parameter_overrides=["Env=環境名"]`のようにパラメータに環境名を与えています。今回は、この入力パラメータを利用して環境ごとに設定値を出し分けていきます。また後で出てきます。

```config.toml
version = 0.1

[dev.deploy.parameters]
stack_name = "test-stack"
region = "ap-northeast-1"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"

parameter_overrides = [
    "Env=dev",
]

[dev.local_invoke.parameters]
parameter_overrides = [
    "Env=dev",
]

[stg.deploy.parameters]
stack_name = "test-stack"
region = "ap-northeast-1"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"

parameter_overrides = [
    "Env=stg",
]

[prd.deploy.parameters]
stack_name = "test-stack"
region = "ap-northeast-1"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"

parameter_overrides = [
    "Env=prd",
]
```

### 組み込み関数
SAMのテンプレートには組み込み関数が用意されています。もともとAWS CloudFormationが提供している機能を、SAM用に使いやすく拡張したものがSAMテンプレートです。実行時に動的に値が埋め込まれるので、実行するまで分からない情報は関数で表現しておきましょう。

組み込み関数の構文には、「完全名関数」と「短縮形」での2通り存在しています。これから列挙する関数はすべて短縮形で記載しています。また、記載していない関数もありますので、気になる方は公式のリファレンスをご参照ください。

### 関数

- !FindInMap
  - Mappingsセクションで宣言された2つのレベルのマッピングのキーに対応する値を返す。
- !GetAtt
  - テンプレートのリソースから属性の値を返す。 
- !Ref
  - 指定したパラメータ（文字列）またはリソースの値を返す。
- !Select
  - オブジェクトリストからindexを指定して1つのオブジェクトを返す。
- !Sub
  - 文字列に変数を埋め込むことが出来る。 

### 条件関数

- !And
- !Equals
- !If
- !Not
- !Or

## デプロイコマンド
デプロイ実行時にオプションで環境名を指定します。（＝入力パラメータ）
SAMテンプレートでは、入力パラメータを利用して設定値を出し分けます。

```sh
sam deploy --config-env dev
```
`--config-env`は、SAM-CLI設定ファイルが存在した場合に指定された環境の設定値をコマンドで使用します。
上記のコマンドでは、`dev`環境が指定されたので以下の情報が読み込まれます。

```config.toml
[dev.deploy.parameters]
stack_name = "test-stack"
region = "ap-northeast-1"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"

parameter_overrides = [
    "Env=dev",
]
```

また、SAM-CLI設定ファイルが存在していなくとも、以下のように環境名（＝入力パラメータ）を与えることもできます。

```sh
sam deploy --parameter-overrides Env=dev
```

https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-deploy.html



## 参考文献

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

https://dev.classmethod.jp/articles/cloudformation-tips-focused-on-refs/

https://www.ketancho.net/entry/2018/01/10/080000


:::note warn
この記事の情報は古くなっている可能性がありますので、実務でご使用の際はAWS公式リファレンスをご参照ください。
:::

<https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification.html>