【小ネタ】AWS SAMでLambda関数を作成する場合はCloudWatch LogsのLog Groupも同時に作った方がいいという話

2018.03.19

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

こんにちは、中山です。

現在私が関わっている案件ではサーバーレスアプリケーションをAWS SAMとCloudFormationで構成管理しています。API GatewayやLambda関数などのアプリケーションレイヤをAWS SAMで、IAMやS3、DynamoDBなどのインフラレイヤをCloudFormationでといった具合に使い分けてます。この理由については以下のエントリの「AWS SAMの責任範囲を考える」を参照してください。

AWS SAM/CircleCI/LocalStackを利用した実践的なCI/CD – ClassmethodサーバーレスAdvent Calendar 2017 #serverless #adventcalendar #reinvent

今回はLambda関数をAWS SAMで構成管理する場合に少し気をつけた方がいい点についてご紹介したいと思います。先に結論を書くと、 特別な理由がない限りAWS SAMでLambda関数を管理する場合はCloudWatch LogsのLog Groupも同時に定義した方がよい と思っています。

なぜLog Groupも管理すべきなのか

まずAWS SAMによるLambda関数の作成過程について説明します。

AWS SAMでLambda関数を作成するにはAWS::Serverless::Functionリソースを利用します。例えば、以下のようにAWS SAMテンプレートにリソースを定義することによって、Lambda関数を作成できます。

---
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Test

Resources:
  TestFunc:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: test-func
      CodeUri: src/handlers/test_func
      Handler: index.handler
      Runtime: python3.6
      AutoPublishAlias: live
      Timeout: 10
      MemorySize: 128

このテンプレートからスタックを作成してみると分かりますが、 TestFunc 論理リソースで作成されたLambda関数のログが出力されるLog Groupは作成されません。Lambda関数が一度でも実行されると /aws/lambda/<Lambda関数名> へログが出力されます(適切なポリシーがアタッチされたIAM Roleがひも付いていれば)。

実際に確認してみましょう。

# スタックを作成した直後
$ aws logs describe-log-groups \
  --log-group-name-prefix '/aws/lambda/test-func'
{
    "logGroups": []
}
# Lambda関数を一度実行した後
$ aws logs describe-log-groups \
  --log-group-name-prefix '/aws/lambda/test-func'
{
    "logGroups": [
        {
            "logGroupName": "/aws/lambda/test-func",
            "creationTime": 1521268025290,
            "metricFilterCount": 0,
            "arn": "arn:aws:logs:ap-northeast-1:************:log-group:/aws/lambda/test-func:*",
            "storedBytes": 0
        }
    ]
}

この動作によって何が問題になるのでしょうか。私は以下の点に問題があると思っています。

1. Lambda関数のLog Groupが存在する前提のテンプレートになっているとスタックの作成に失敗する可能性がある

例えば、Lambda関数のログをAmazon Elasticsearchやログ管理用SaaSなどに転送したい場合、Log Groupにサブスクリプションの設定が必要になります。もしこの設定もAWS SAMもしくはCloudFormationで実施したい場合、まだLog Groupが存在していないと「そんなLog Groupは存在しない」といったエラーによってスタックの作成に失敗してしまいます。仮にこの部分は手動で作成したとしても、一度Lambda関数を実行させる必要があるので、デプロイのフロー的に利便性が低くなってしまうのではないでしょうか。

2. Lambda関数が作成するLog Groupはリテンションが無期限になっている

Log Groupは保存されたログの容量によって料金が請求されます。無期限になっているとログが出力される毎にAWS利用料金が高くなってしまうため、ログを無期限で保存したいユーザ以外にとって適切な設定ではないはずです。もちろん後から変更することも可能ですがこの部分だけ手動で、というのはあまり便利な状況ではないでしょう。

3. AWS SAMによって作成されたスタックを削除した場合Log Groupが残り続ける

Lambda関数によって作成されたLog GroupはAWS SAMの管理外のため、スタックを削除しても当然残り続けます。不要なLog GroupはAWS利用費がかさむだけなのでスタックの作成と同時に削除された方が便利でしょう。逆に、スタックの削除と同時にLog Groupが消されてしまっては困る場合DeletionPolicyを使えばOKです。

4. そもそもLog GroupはLambda関数には必須のAWSリソースなのでそれも含めてテンプレートを作成した方がよい

理由3とも関連しますが、そもそもLog GroupがLambda関数にとって必須なのであればテンプレートで管理すべきです。テンプレートで管理することにより組み込み関数による参照や、クロススタック参照などの機能が使えます。

また、サーバーレスアプリケーションの構成管理として有名なServerless Frameworkの場合、 serverless.yml にLambda関数を定義するとデフォルトでLog Groupも作成されるよう変更されました。以前は cfLogs オプションを明示的に設定する必要がありましたが、現在は変わっているようです。ドキュメントに主な理由が記載されているため引用します。

By default, the framework will create LogGroups for your Lambdas. This makes it easy to clean up your log groups in the case you remove your service, and make the lambda IAM permissions much more specific and secure.

じゃあどうするか

初めに記載したとおり、Lambda関数の定義と同時にAWS::Logs::LogGroupによってLog Groupを作りましょう。

使い方は簡単です。例えば、リテンションを14日にしたい場合以下のようにテンプレートを修正すればOKです。

---
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Test

Resources:
  TestFunc:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: test-func
      CodeUri: src/handlers/test_func
      Handler: index.handler
      Runtime: python3.6
      AutoPublishAlias: live
      Timeout: 10
      MemorySize: 128
      
  TestFuncLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /aws/lambda/${TestFunc}
      RetentionInDays: 14

AWS::Serverless::Function リソースの論理リソース名を Ref または Sub で参照するとLambda関数名を取得できます。また、 AWS::Logs::LogGroup リソースの LogGroupName プロパティが /aws/lambda/<Lambda関数名> になっていれば、Lambda関数はそのLog Groupへログを出力してくれます。

一点だけ注意点を。すでにデプロイされたLambda関数によってLog Groupが作成されている場合、当然スタックの作成に失敗します。AWS SAMによって作成しようとしているLog Groupがすでに存在しているからです。その場合は、一度手動でLog Groupを削除した上でスタックを作成してください。ログを残しておきたい場合は事前にバックアップが必要な点もお忘れなく。

まとめ

いかがだったでしょうか。

AWS SAMによってLambda関数を管理する際のちょっとしたTipsをご紹介しました。今後も引き続きこういった情報をご紹介できればと思っています。

本エントリがみなさんの参考になれば幸いに思います。