100個あっても大丈夫!Cloud Watch Logs ロググループの保持期間をLambdaで一括変更する
コンバンハ、千葉(幸)です。
Cloud Watch Logsのロググループには、ログの保持期間を定義する「保持設定(Retention settings)」というパラメータがあります。
保持設定は、CloudWatch Logs にログイベントを保持する期間を指定するために使用できます。期限切れのログイベントは自動的に削除されます。メトリクスフィルターと同様に、保持設定はロググループに割り当てられ、ロググループに割り当てられた保持期間はそのログストリームに適用されます。 Amazon CloudWatch Logs の概念
デフォルトでは「期限なし」が設定されているため、明示的に削除しない限りロググループ内のログは保持され続けます。S3に置いておくよりもお値段が少し張るので、きちんと必要な保持期間のみに抑えておきたいものですが、マネジメントコンソールから手動で変更していくのはちょっと面倒です。
「マネジメントコンソールからできないならスクリプトを作ればいいじゃない」ということで、ちょっと楽にしてくれるLambda関数を作りました。
目次
- 目次
- マネジメントコンソールではロググループ保持設定の一括変更ができなくて面倒くさい!
- Lambdaで一括変更しよう
- nextTokenを意識する
- 動作確認
- CloudFormationもあるよ!
- 終わりに
マネジメントコンソールではロググループ保持設定の一括変更ができなくて面倒くさい!
先に、マネジメントコンソールから設定変更する時の手順を確認しておきましょう。なお、2020年3月時点で最新のデザインのコンソール画面で試しています。
グループの一覧画面からスタートです。ここで保持設定の値は見れるものの、この画面からは変更することはできません。ロググループの詳細画面に遷移します。
アクションから「保持設定の編集」を押下します。
期間を選んで、「Confirm」という文字列を入力して「Save」を押下します。保持期間を変更すると過去のログは消去されるため、うっかり消さないようにこのようなプロセスを踏んでいるようです。
これでようやくひとつ設定変更ができました。
これを繰り返すのはなかなか骨が折れますね。手動ではやりたくない作業です。
Lambdaで一括変更しよう
ということで出来上がったのがこちらのLambda関数用のコードです。 ランタイムpython3.7で動作確認済みです。
Lambdaの環境変数で、保持設定の期間を指定するようにしています。キーRetentionDays
の値として、保持期間の日数を指定してください。
import boto3 import os Days=os.environ['RetentionDays'] # Possible values are: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, and 3653. def lambda_handler(event, context): logs_client = boto3.client('logs') response = logs_client.describe_log_groups() group_list = response['logGroups'] while 'nextToken' in response: response = logs_client.describe_log_groups(nextToken=response['nextToken']) add_groups = response['logGroups'] group_list.extend(add_groups) for log_group in group_list: print(log_group['logGroupName']) result = logs_client.put_retention_policy( logGroupName=log_group['logGroupName'], retentionInDays=int(Days) ) code = result['ResponseMetadata']['HTTPStatusCode'] print('HTTPStatusCode is ' + str(code))
すべてのロググループに対して一括で同じ保持設定を指定するようになっています。ロググループごとに保持設定を打ち分けしたい場合は、カスタマイズが必要です。(本稿の対象外です。)
nextTokenを意識する
上記のコードの中身は至ってシンプルなのですが、一点考慮すべき存在として、nextToken
の存在があります。
保持設定を変更する対象のロググループをリスト化するために先頭でdescribe_log_groups()
を実行しているのですが、ここで取得できるグループ数の上限は50になっています。
Valid Range: Minimum value of 1. Maximum value of 50.
API_DescribeLogGroups_RequestSyntax
対象に51個以上のロググループがあった場合、レスポンスの中に含まれるnextToken
を指定して再度describe_log_groups()
することで前回の続きから取得できるようになっています。
レスポンスにおいてnextToken
が含まれる場合は、以下のような形式で渡されます。値には、ロググループ名と同一の文字列がセットされています。
{ 'logGroups': [ { 'logGroupName': 'string', 'creationTime': 123, 'retentionInDays': 123, 'metricFilterCount': 123, 'arn': 'string', 'storedBytes': 123, 'kmsKeyId': 'string' }, ], 'nextToken': 'string' }
CloudWatchLogs.Client.describe_log_groups
すべてのロググループをdescribeした場合には、レスポンスにはキーnextToken
自体が存在しません。辞書型のレスポンスの中からキーnextToken
の有無を確認し、存在する限り繰り返し実行するという処理を以下の部分で実現しています。
while 'nextToken' in response: response = logs_client.describe_log_groups(nextToken=response['nextToken']) add_groups = response['logGroups'] group_list.extend(add_groups)
動作確認
きちんと意図通りに動くかを確認しておきましょう。
以下のようなコードをLambdaで実行し、Cloud Watch Logsグループを101個作成します。
import boto3 def lambda_handler(event, context): logs_client = boto3.client('logs') num = 0 while num < 100: responce = logs_client.create_log_group( logGroupName='DummyLogGroup-' + str(num) ) num += 1
1回のdescribe_log_groups
で取得できるロググループ数が50であり、nextToken
を取得する処理が2回入るので、これをうまく回せれば問題ないでしょう。
上記のコードを実行して作成されたロググループがこちら。保持設定はいずれもデフォルトの「失効しない」になっています。
一括変更Lambdaを実行すると以下の通り。すべてのロググループの保持設定が、Lambdaの環境変数で指定した日数になっています。
一括変更Lambdaの実行ログは以下のような形に。ロググループの名称とHTTPステータスコードを出力するようにしていますが、不要であればコードからprint()
部を削ってください。
手元の環境では、ロググループ106個に対して実行して、使用メモリ78MB、実行時間およそ23秒でした。
CloudFormationもあるよ!
上記のコードを載せたLambda関数を作成するCloudFormationのコードを作成しました。1から作るよりはだいぶ楽かと思いますので、ぜひご活用ください。
CloudFormationスタックのパラメータとして渡すようにしている値は以下の通りです。
論理ID | 概要 |
---|---|
LambdaMemorySize | Lambda関数の割り当てメモリ(MB) |
LambdaTimeout | Lambda関数のタイムアウト(秒) |
LogGroupPutRetentionDays | ロググループの保持設定(日間) |
特記事項としては以下のあたりです。
- VPC外Lambdaとして作成される
- Lambdaに割り当てるロールには、AWS管理ポリシー
CloudWatchLogsFullAccess
をアタッチするようにしている - 同時実行数の予約なし
AWSTemplateFormatVersion: '2010-09-09' Description: "Create Lambda" # ------------------------------------------------------------# # Input Parameters # ------------------------------------------------------------# Metadata: AWS::CloudFormation::Interface: ParameterGroups: Parameters: - LambdaMemorySize - LambdaTimeout - LogGroupPutRetentionDays Parameters: LambdaMemorySize: Description: 128 ~ 3008. The value must be a multiple of 64. Type: String Default: 128 LambdaTimeout: Description: The maximum allowed value is 900 seconds. Type: String Default: 60 LogGroupPutRetentionDays: Description: Possible values are 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, and 3653. Type: String Default: 14 Resources: # ------------------------------------------------------------# # IAM Role # ------------------------------------------------------------# LamnbdaFunctionRole: Type: "AWS::IAM::Role" Properties: RoleName: "Lambda-LogGroupPutRetentionPolicy-Role" AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: "lambda.amazonaws.com" Action: "sts:AssumeRole" Path: "/" ManagedPolicyArns: - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess # ------------------------------------------------------------# # Lambda # ------------------------------------------------------------# LamnbdaFunction: Type: "AWS::Lambda::Function" Properties: FunctionName: "LogGroupPutRetentionPolicy" Handler: "index.lambda_handler" Role: !GetAtt [ LamnbdaFunctionRole, Arn ] Environment: Variables: RetentionDays : !Ref LogGroupPutRetentionDays MemorySize: !Ref LambdaMemorySize Timeout: !Ref LambdaTimeout Code: ZipFile: | import boto3 import os Days=os.environ['RetentionDays'] # Possible values are: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, and 3653. def lambda_handler(event, context): logs_client = boto3.client('logs') response = logs_client.describe_log_groups() group_list = response['logGroups'] while 'nextToken' in response: response = logs_client.describe_log_groups(nextToken=response['nextToken']) add_groups = response['logGroups'] group_list.extend(add_groups) for log_group in group_list: print(log_group['logGroupName']) result = logs_client.put_retention_policy( logGroupName=log_group['logGroupName'], retentionInDays=int(Days) ) code = result['ResponseMetadata']['HTTPStatusCode'] print('HTTPStatusCode is ' + str(code)) Runtime: "python3.7" # ------------------------------------------------------------# # Output # ------------------------------------------------------------#
CloudFormationをカスタマイズしたい場合は、以下を参考にどうぞ。
終わりに
マネジメントコンソールから一括で変更できてもおかしくないと思うのですが、現状は対応していません。一手間必要ですが、なるべく楽な方法で対応しましょう。
RDSやLambdaのログなど、気づけばロググループが増えていることもあるので、定期的に一括変更Lambdaを実行して保持設定をコントロールするのもよいでしょう。
今回のコード作成時には以下の記事を参考にさせていただきました。本稿のコードではすべてのロググループに同じ保持設定を割り当てるような仕様になっていますが、ロググループのプレフィックスごとに保持設定を変えたい、という場合には以下の記事が参考になります。
以上、千葉(幸)がお送りしました。