CloudFormationテンプレートのエラーをスタックデプロイ前に検出する

# はじめに
CloudFormationのテンプレートエラーに気づくのは、実際にスタックをデプロイしてからということがよくあります。本記事では、CloudFormationテンプレートの静的解析ツールであるcfn-lintを使用して、デプロイ前にテンプレートエラーを検出する方法について紹介します。

# 環境
cfn-lintをインストールします。

```bash
pip install cfn-lint
```

https://pypi.org/project/cfn-lint/


# 1. エラー検出用のテンプレート検証
簡単なLambdaを構築するテスト用のCloudFormationテンプレートを準備します。
意図的にいくつかのエラー箇所を含めた以下のCloudFormationテンプレートをtemplate.yamlとして保存します。

▼問題箇所3点
1. `Runtime: python3.7` ⇒古いバージョンを指定
2. `Timeout: 1000` ⇒タイムアウトの時間を15分以上に設定
3. testLambdaFunctionに対する `AWS::IAM::Role` の記載なし

```yaml
AWSTemplateFormatVersion: "2010-09-09"

Resources:
  testLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: test-python-function
      Handler: index.lambda_handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: |
          def lambda_handler(event, context):
              return {
                  'statusCode': 200,
                  'body': 'test hello world!'
              }
      Runtime: python3.7 # 古いランタイムバージョンを指定
      MemorySize: 128
      Timeout: 1000 # タイムアウトの上限を超過
      # AWS::IAM::Roleの記載なし
```

# 2. cfn-lintでテンプレートを検証

以下のコマンドで作成したテンプレートを検証してみます。

```
cfn-lint template.yaml
```

コマンドを実行すると下記の内容をが表示されます。

```
E1010 'LambdaExecutionRole' is not one of ['MyLambdaFunction']
template.yaml:9:7

E2531 Runtime 'python3.7' was deprecated on '2023-12-04'. Creation was disabled on '2024-01-09' and update on '2025-02-28'. Please consider updating to 'python3.12'
template.yaml:17:7

E3034 1000 is greater than the maximum of 900
template.yaml:19:7
```

IAMロールがアタッチされていない事、ランタイム、タイムアウトの問題を指摘してくれています。これら3点を修正し再度テンプレートの検証を行います。

:::note info

- VSCode拡張機能の活用
VSCodeを使用している場合、CloudFormation Linter をインストールする事でエラー箇所をソース上に表示してくれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247619/ca463902-ac78-dc8f-30f2-7a72156e3cfa.png)

:::

# 3. エラーの修正

## 3.1 ランタイムの更新

```yaml
Runtime: python3.12  # 最新バージョンに更新
```

## 3.2 タイムアウトの修正

```yaml
Timeout: 300  # 5分に設定
```

## 3.3 IAMロールの追加
```yaml
LambdaExecutionRole:
  Type: AWS::IAM::Role
  Properties:
    AssumeRolePolicyDocument:
      Version: "2012-10-17"
      Statement:
        - Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
          Action: sts:AssumeRole
    ManagedPolicyArns:
      - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
```

修正後の完全なテンプレートは次の通りです。

```yaml
AWSTemplateFormatVersion: "2010-09-09"

Resources:
  testLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: test-python-function
      Handler: index.lambda_handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: |
          def lambda_handler(event, context):
              return {
                  'statusCode': 200,
                  'body': 'test hello world!'
              }
      Runtime: python3.12
      MemorySize: 128
      Timeout: 300

  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
```

修正後のテンプレートを再度検証します

```bash
cfn-lint template.yaml
```

上記のコマンドを実行すると、今度はエラーや警告が表示されなくなったことを確認できました。


# 参考

https://github.com/aws-cloudformation/cfn-lint