CloudWatchLogsで取得したログを加工してEFSに格納するLambda作ってみた



 # はじめに
日次でCloudWatchLogsからログを取得し、加工してEFSに格納する独自スクリプトを動かしてた。
だが以下の点でイケてないスクリプトでたちまち処理が失敗していた。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187693/0257c3e3-e6d1-c1f0-f021-c173d2ddf097.png)
図1:既存構成

:::note alert
こんな感じ
・サービス利用増により、ログ量が莫大し処理が追いつかずにMaintenanceWindowsの**上限値5時間**をオーバしてしまう
・古き良き(?)シェル芸でログを加工しておりコードは煩雑化・処理は非効率で処理が行われてる
:::

 ## 実装方式

上記を解決するにはスクリプトを改修したり、そもそものサーバスペックを上げたりするといった流れになるがログ量は増える一方であったり、この処理のためにスペック上げるのは~~贅沢・贅肉すぎるし~~もったいないという形で以下で実装してみた

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187693/59944a60-33b0-ea13-db9e-eb3830af3bdd.png)
図2:Lambdaを利用した構成

大まかの処理は以下の通り
➀対象のCloudWathLogsを契機にLambda発火
➁Lambda上でログを加工して定期的にファイルに書き込む

Lambdaを採用した理由は

:::note warn
★久々にPython書きたくなった（もう業務で1年ぐらい書いてない）
★サーバレスでEC2の管理が不要になるので結果的にラク
★これぐらいしかパッとー思い浮かばなかった()
:::

ここでは詳細は述べないが最終的な仕組みとしてはこんな感じ。
ログ加工に必要なラベル(Logsに付与する固定的な値)はクラスメソッドに記載して見やすいように（割と趣味の領域)
1日分のログを丸々保存しておきたいので24時跨いだログは新規のファイルに記載するように処理して
まとまった1日分のログはリネームして書き込まれないようにするといった処理が地味にめんどくさくて大変！笑

メインとはずれるので構成図だけ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187693/8c457209-486a-cc4e-ae54-10dfa36d8601.png)
図2:Lambdaを利用した構成(詳細)



## メリ・デメ

:::note info
★ランニングコストが安上がりの可能性大
1,000,000件リクエスト/400,000GB秒は無料となるとサーバレスにすることによって料金に関しては大幅に削減できるんじゃないかな。現状稼働しているEC2のランニングコストは割と高額だからサーバレスはありがたい
＊ただしEFSへの転送量は加味しない(元々から転送してるから)

★サーバレスなので運用管理が不要
スクリプトの処理でCPU・メモリがやばい！なんてザラ。リソース恐怖からの脱却
:::

:::note alert
★Lambda ありきの記載になっているので他クラウドサービスなどでは移植がほぼ不可
★Pythonバージョンに怯える(今回はPython3.6で作成したが、書いてる途中に3.6サポート終了しますみたいなアナウンスがあった)
:::


## 目次
  - [CloudWatchLogs→Lambda連携](#CloudWatchLogs→Lambda連携)
  - [Lambda-EFS連携](#Lambda-EFS連携)
  - [Lambda構築](#Lambda構築)
  - [結合テスト](#結合テスト)
  - [感想・所感](#感想・所感)
  - [余談(格納はEFSよりS3よりいいんじゃない？)](#余談格納はEFSよりS3よりいいんじゃない？)
  - [【備考】InstanceへのCloudWatch連携](#【備考】InstanceへのCloudWatch連携)
  - [参考文献](#参考文献)

## CloudWatchLogs→Lambda連携
マネコンで設定する側
設定自体はシンプルです

https://bbh.bz/2019/09/16/how-to-subscription-filter/


## Lambda-EFS連携
<a id="Lambda-EFS連携"></a>

LambdaとEFSとを接続するためにはアクセスポイントを設定するのがポイント(知らんかった)

https://qiita.com/leomaro7/items/cfebe8a566b4aa8ee8e3

## Lambda構築

CloudWatchLogsから取得したログデータは圧縮されているため
Pythonの処理で紐解いていく

https://bbh.bz/2019/09/14/link-cloudwatchlogs-to-lambda/

詳細のコードの内容は割愛するが非常にややこしかったのが日付処理が厄介だったので備忘録として下記に掲載
CloudWatchLogsから取得されたデータはデコード処理を終えた後にgzip解凍を行い、タイムスタンプ値を取得
```cloudwatchlogs_output.json
{
	"messageType": "DATA_MESSAGE",
	"owner": "XXXXXXXXXXXXXXXXXXXXXX",
	"logGroup": "XXXXXXXXXXXXXXXXXX",
	"logStream": "XXXXXXXXXXXXXXXXXXXX",
	"subscriptionFilters": [
		"TEST"
	],
	"logEvents": [
		{
			"id": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
			"timestamp": 1650214747000,
			"message": "Apr 17 16:59:07 XXXXXXXXX ec2-user: 2022-04-16 05:54:17 hogehoge connect"
		}
	]

```

UNIX形式→yyyy-mm-dd xx:xx:xxの値に変換したかったが
どう頑張ってもレンジエラー(値が長すぎる？)ため以下で無理くり解決させた
別の手法あるんじゃないかと思ったけど思い浮かばなかったのでここは関数化させて無理くり解決
```lambda.py
~~
from datetime import datetime, timedelta

#一回文字列として後ろの数字を切り取りしてyyyy-mm-dd xx:xx:xxに変換
time_date = datetime.fromtimestamp(int(str(log['timestamp'])[:-3]))
#ただし、日本時間じゃないので＋9時間に変換 
jp_now = time_date + timedelta(hours=+9)

```





## 結合テスト

➀今回はInstanceからLoggerを飛ばし
➁LogをCloudWatchが受け取り,Lambdaが発火し
➂Lambdaによってログが加工され
➃最終的にEFSに格納されている
一連の処理ができているか確認する

➀InstanceからLoggerを飛ばす
＊連携するログは/var/log/messages
```
[root@hogehoge log]# logger "2022-04-16 05:54:17 hogehoge connect" 
[root@hogehoge log]# grep "hogehoge" /var/log/messages
Apr 17 15:59:01 hogehoge ec2-user: 2022-04-16 05:54:17 hogehoge connect
```

➁CloudWatchにて確認
(Instance→CloudWatch)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187693/a16d9dd9-810f-c202-a053-759287cbae4a.png)

(CloudWatch→Lambda)
Lambdaの発火確認
多少のラグがあるもの前述の通りCloudWatchLogsはある程度まとめてLambdaに転送するので
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187693/4d9211a8-2858-d48d-9432-c41c42633c4b.png)

➂,➃
EFSからは下記で保存が確認できた
```
HOGE,HOGE,1,HOGEHOGE,Apr 17 15:59:01 hogehoge ec2-user: 2022-04-16 05:54:17 hogehoge connect
```

## 感想・所感
たまには手を動かさないとやばいって感じた、、、（いろいろ忘れてる）が
久々に書くと達成感あるなってひしひしと。
なるべくサーバレスで運用するのが色々ラクしかない。マネジードサービス万歳


## 余談(格納はEFSよりS3よりいいんじゃない？)

こちらの記事に欲しい答えすべてが記載されている。（感謝！）

https://dev.classmethod.jp/articles/lambda-support-for-amazon-elastic-file-system/



簡単にいうとCloudWatch - Lambdaとの連携はある程度のリアルタイムを求めているので
そうするとS3のポリシーである「結果整合性を許容する」がネックになるっていう話
例えばデータを長期保存したい場合はS3も候補だけど微量なデータ量ならEFSかなという結論(~~boto3使うの嫌になっただけ~~)

以下抜粋
>結果整合性の回避
S3 を介することでどうしても付きまとう制約は S3 の仕様である「結果整合性を許容する」ことでしたが、EFS はその名のとおりファイルシステムなので結果整合性ではなく必ず最新状態の内容が取得できますし、ファイルロックもされます。S3 の特性でもある結果整合性が許容できずにサーバレスアーキテクチャを見送ったようなシステムでも Lambda & EFS のサポートによって再検討することが出来るかもしれません。
>

## 【備考】InstanceへのCloudWatch連携
メインなお話しではないのでここでは説明は割愛します

https://business.ntt-east.co.jp/content/cloudsolution/column-try-28.html

## 参考文献
【メンテナンスウインドウ】

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/maintenance-windows-schedule-options.html

>メンテナンスウィンドウは、必要に応じて最大 5 時間実行できますが、メンテナンスウィンドウ期間の終了 2 時間前に新しいタスクを開始することはできません。
>