Lambda作成テンプレートCloudFormation


# はじめに

他のAWSサービスに対して自動操作ができるかなど、試しにLambdaを作ってみる際に、都度IAMロールやログの管理を考慮するのが面倒と感じていました。
なので、必要最小限のリソースをCloudFormationで作ることで、削除時にまとめて消せるようにしました。

# CloudFormation

各部分の解説をし、全体は最後に記載します。

## パラメータ
```
Parameters:
  LambdaFunctionName:
    Type: String
```
`Type: AWS::Lambda::Function`ではFunctionNameは任意ですが、本番使用の関数作成時も想定して、名前を付けられるようにしました。

## ロググループ
対象のLambda関数の出力先ロググループを作成します。

```
  FunctionLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "/aws/lambda/${LambdaFunctionName}"
      RetentionInDays: 3653   # 未指定時は「失効しない」
```
Lambda関数の出力先ロググループは`/aws/lambda/<関数名>`固定のようです。
コンソールから作る際は、関数にアタッチするIAMロールに作成権限を持たせて、Lambda関数から作るようにしているようです。
CloudFormationスタック削除時にロググループを削除するためここに記載していますが、スタックを削除してもログを残したい場合はこの部分を削除して、IAMロールにロググループ作成権限を付けてください。

## IAMロール
### ポリシー
Lambda関数に必要な権限の定義です。

```
      Policies:
        # CloudWatch
        - PolicyName: write-cloudwatchlogs
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              # ロググループを作らない場合は、権限を与えて自動で作るようにする。
              # - Effect: Allow
              #   Action: 'logs:CreateLogGroup'
              #   Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*"
              - Effect: Allow
                Action: 
                  - 'logs:CreateLogStream'
                  - 'logs:PutLogEvents'
                Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${LambdaFunctionName}:*"    
```

- CloudFormationでロググループを作る場合 = スタック削除時にロググループも削除する場合
    - ログストリーム作成とログイベント書き出しの権限が必要です。
- CloudFormationでロググループを作らない場合 = スタック削除しても、ロググループを残す場合
    - 先のロググループ削除の部分を削除します。
    - コメントアウトしている、ロググループ作成権限を有効化します。

その他、他サービスへの権限が必要な場合、ここに追記していく想定です。

## Lambda関数

```
  TargetFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Ref LambdaFunctionName
      Role: !GetAtt FunctionRole.Arn
      Runtime: python3.9
      Handler: index.lambda_handler
      Code:
        ZipFile: !Sub |
          def lambda_handler(event, context):
            print('Created ${LambdaFunctionName}.')
```

「自分の名前をログに出力する」というPythonスクリプトが記載されたLambdaを作成します。

## 全体
以下を1ファイルにして実行すれば動くはずです。

```
AWSTemplateFormatVersion: 2010-09-09
# Lambdaの最小構成を作成するCFn。CloudWatch Logsのロググループも一緒に作成する。
# スタック削除時にロググループを残したい場合は、ロググループの部分を削除し、ロールからlogs:CreateLogGroupを有効化する。

Parameters:
  LambdaFunctionName:
    Type: String

Resources:
  FunctionLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "/aws/lambda/${LambdaFunctionName}"
      RetentionInDays: 3653   # 未指定時は「失効しない」

  FunctionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "for-lambdafunction-${LambdaFunctionName}"
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Path: '/service-role/'
      Policies:
        # CloudWatch
        - PolicyName: write-cloudwatchlogs
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              # ロググループを作らない場合は、権限を与えて自動で作るようにする。
              # - Effect: Allow
              #   Action: 'logs:CreateLogGroup'
              #   Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*"
              - Effect: Allow
                Action: 
                  - 'logs:CreateLogStream'
                  - 'logs:PutLogEvents'
                Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${LambdaFunctionName}:*"    

  TargetFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Ref LambdaFunctionName
      Role: !GetAtt FunctionRole.Arn
      Runtime: python3.9
      Handler: index.lambda_handler
      Code:
        ZipFile: !Sub |
          def lambda_handler(event, context):
            print('Created ${LambdaFunctionName}.')
```
スクリプト部分のみを修正し、他の設定値を変えていないyamlファイルでも、更新してくれました。

# おわりに
最小構成のLambda関数のテンプレートをCloudFormationで作成しました。
試しに作ってみる場合や、関数作成もテンプレート化する際のベースに使っていこうと思います。