[AWS SAM] [CloudFormation] 環境ごとに設定値を切り替える方法


背景

  • AWS SAM を使って、API Gateway + Lambda のデプロイを自動化しています
  • ステークホルダーや各種制約等々の都合上、複数環境に対してそれぞれ異なる構成を実現する必要がありました
    • [dev|staging|prd] 環境 × [(無印)|r1|r2] 環境 = 合計9環境…!
  • パラメータマッピング条件 などを使うことで実現出来そうなことはすぐに分かりましたが、細かい yaml の書き方などで度々詰まったので、具体的な設定例を共有します

実現したこと

  • パラメータ、マッピング、条件(と組み込み関数)を併用して、「環境ごとに異なる設定を適用」できるようにしました
    • 名前やタグは全環境で異なる値となるようにしたほか、以下の設定は、要件に従い特定環境だけ設定を変えています
環境 dev dev-r1 dev-r2 staging staging-r1 staging-r2 prd prd-r1 prd-r2
アクセス許可IPアドレス 設定A 設定A 設定A 設定B 設定B 設定B 設定C 設定C 設定C
Lambda の段階的デプロイ AllAtOnce AllAtOnce AllAtOnce Linear Linear Linear Linear Linear Linear
Lambda のプロビジョニング しない しない しない する しない しない する しない しない

パラメータ・マッピング・条件の設定

  • template.yaml 冒頭で、環境変数である「パラメータ」と環境ごとの設定である「マッピング」、及びそれらを用いた「条件」を定義します

パラメータ ( Parameters )

  • [dev|staging|prd] 環境を EnvProfile、[(無印)|r1|r2] 環境を SystemType としました
    • 空白は設定できない&分かりにくいので、(無印)環境は general としました
  • これらの値はデプロイ時に samconfig.toml で明示的に指定していますが、指定漏れに備えてデフォルト値は dev など安全なものを設定した方が安心です
template.yaml
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: test for qiita

# 環境変数
Parameters:
  EnvProfile:
    Type: String
    AllowedValues:
    - dev
    - staging
    - prd
    Default: dev
  SystemType:
    Type: String
    AllowedValues:
    - general # 無印
    - r1      # r1 環境
    - r2      # r2 環境
    Default: general

マッピング ( Mappings )

  • 続いて、マッピングを定義します。要は HashMap や連想配列なので、キーを環境名とします
    • EnvProfile である [dev|staging|prd]、SystemType である [general|r1|r2] をキーとして、各環境固有の設定を列挙していきます
    • 例えば「Lambda のデバッグモードは dev では有効だけど、staging と prd では無効」など
template.yaml
Mappings:
  # dev / staging / prod 環境ごとの固有値
  EnvProfileMap:
    dev:
      DeploymentPreferenceType: AllAtOnce
      DebugMode: true
      WhiteList:
       - "111.111.111.111/32"
    staging:
      DeploymentPreferenceType: Linear10PercentEvery1Minute
      DebugMode: false
      WhiteList:
       - "222.222.222.222/32"
    prd:
      DeploymentPreferenceType: Linear10PercentEvery1Minute
      DebugMode: false
      WhiteList:
       - "333.333.333.333/32"

  # 無印 / r1 / r2 環境ごとの固有値
  SystemTypeMap:
    general:
      ApiGwName: test-apigw-for-qiita
      ApiGwAccessLogGroupName: /aws/apigateway/test-apigw-for-qiita/
      LambdaName: lambda-handler-for-qiita
      VariablePassToLambda: <lambda variable on general>
    r1:
      ApiGwName: test-apigw-for-qiita-r1
      ApiGwAccessLogGroupName: /aws/apigateway/test-apigw-for-qiita-r1/
      LambdaName: lambda-handler-for-qiita-r1
      VariablePassToLambda: <lambda variable on r1>
    r2:
      ApiGwName: test-apigw-for-qiita-r2
      ApiGwAccessLogGroupName: /aws/apigateway/test-apigw-for-qiita-r2/
      LambdaName: lambda-handler-for-qiita-r2
      VariablePassToLambda: <lambda variable on r1>

条件 ( Conditions )

  • ここでは以下のような条件を定義しています
    • 複数箇所で使われる判定条件
      • 「dev環境かどうか」、「無印環境かどうか」
    • ちょっと複雑な判定が必要な条件
      • 「Lambda のプロビジョニング環境かどうか」=ここでは「staging 無印環境か prd 無印環境か」
  • 後続のリソース設定でも書けますが、ここで定義しておいた方が可読性が良く&修正しやすくなります
template.yaml
Conditions:
  IsDev : !Equals [!Ref EnvProfile, "dev"]
  IsGeneral : !Equals [!Ref SystemType, "general"]
  # Lambda のプロビジョニング環境:staging 無印 と prd 無印のみ
  IsLambdaProvisioningEnv : !And [ !Not [Condition: IsDev], Condition: IsGeneral ]

リソース設定

  • API Gateway や Lambda といった AWS リソース設定の内、環境固有の設定を適用している部分を抜粋してご紹介します

API Gateway のアクセスログ用ロググループ

  • ロググループ名を SystemTypeMap から SystemType をキーとして取得します
template.yaml
Resources:
  (略)
  ApiGwAccessLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !FindInMap [ SystemTypeMap, !Ref SystemType, ApiGwAccessLogGroupName ]
      RetentionInDays: 180

API Gateway

  • 名前を SystemTypeMap から SystemType をキーとして取得します
  • タグは環境名を付与した形で設定します
    • 例えば dev-r1 環境だと dev-test-apigw-for-qiita-r1 と設定され、判別しやすくなります
  • リソースポリシーによる IP アドレス許可リストを EnvProfileMap から EnvProfile をキーとして取得します
    • aws:SourceIp: を忘れずに
template.yaml
  TestApiGatewayForQiita:
    Type: AWS::Serverless::Api
    (略)
    Properties:
      Name: !FindInMap [ SystemTypeMap, !Ref SystemType, ApiGwName ]
      StageName: stage # ステージ名は全環境固定
      TracingEnabled: true
      OpenApiVersion: 3.0.2
      Tags:
        Name: !Sub ${EnvProfile}-test-apigw-for-qiita-${SystemType}
        Env: !Ref EnvProfile
      (略)
      # 公開 API の仕様
      DefinitionBody:
        (略)
        x-amazon-apigateway-policy:
          Version: "2012-10-17"
          Statement:
            - Effect: Allow
              Principal: "*"
              Action: execute-api:Invoke
              Resource:
                - "execute-api:/*"
              Condition:
                IpAddress:
                  aws:SourceIp: !FindInMap [ EnvProfileMap, !Ref EnvProfile, WhiteList ]

Lambda

  • 関数名を SystemTypeMap から SystemType をキーとして取得します
  • Lambda の段階的なデプロイ設定 DeploymentPreferenceEnvProfileMap から取得します
  • Lambda のプロビジョニング設定は、条件 IsLambdaProvisioningEnv を満たす(環境)ならプロビジョニングを実施するよう設定します
    • 条件を満たさない場合、!Ref "AWS::NoValue" を設定して本プロパティを削除し、プロビジョニングしないようにする必要があります
    • 空白や他の表記だとうまく動かなかったので要注意です
  • Lambda に渡す環境変数として SystemTypeVariablePassToLambdaDebugMode を設定します
  • こちらもタグは環境名を付与した形で設定します
    • 例えば dev-r1 環境だと dev-lambda-handler-for-qiita-r1 と設定されます
template.yaml
  LambdaHandlerForQiita:
    Type: AWS::Serverless::Function
    Dependson: LambdaHandlerForQiitaLogGroup
    Properties:
      FunctionName: !FindInMap [ SystemTypeMap, !Ref SystemType, LambdaName ]
      (略)
      DeploymentPreference:
        Enabled: true
        Type: !FindInMap [ EnvProfileMap, !Ref EnvProfile, DeploymentPreferenceType ]
      ProvisionedConcurrencyConfig:
        !If
          - IsLambdaProvisioningEnv
          - ProvisionedConcurrentExecutions: 100
          - !Ref "AWS::NoValue" # 対象環境以外はプロビジョニングしない
      (略)
      Environment:
        Variables:
          SYSTEM_TYPE: !Sub ${SystemType}
          VARIABLE_PASS_TO_LAMBDA: !FindInMap [ SystemTypeMap, !Ref SystemType, VariablePassToLambda ]
          DEBUG_MODE: !FindInMap [ EnvProfileMap, !Ref EnvProfile, DebugMode]
      Tags:
        Name: !Sub ${EnvProfile}-lambda-handler-for-qiita-${SystemType}
        Env: !Ref EnvProfile

参考情報