【Terraform】CloudWatchのカスタムメトリクスでログ出力をフィルタリングしてそのアラームをSlackで流したい
背景
コンテナ化に伴い、標準出力によるロギングが一般的になりました。それに伴い、ログストリームからイベントを拾い上げるような監視の仕組みが必要な場面があります。 今回、「標準出力によって吐かれたログからERRORという文字列を検出する」という要件でのアラート設計が必要になったため、特定のキーワードによるログストリームのフィルタリングを行い、キーワードが検出されればSlackに通知する仕組みをTerraformで記述しました。
環境
- Ubuntu20.04
- Terraform 0.13.4 ( tfenv v2.0.0 / tflint v0.18.0)
- aws
概要
log_group, metric_filter, metric_alarmを定義し、alarm_actionをSlack用のSNSトピックにpublish。sns_topicをchatbotでsubscribe。 chatbot用のmoduleがうまく動作しなかったため、SNSトピックまでのパイプラインを構築し、あとでSNSトピックとchatbotを紐付けます。
. ├── aws_cloudwatch_log_group.tf // 今回は使用しない ├── aws_cloudwatch_log_group_variables.tf ├── aws_cloudwatch_log_metric_filter.tf ├── aws_cloudwatch_metric_alarm.tf ├── sns.tf ├── chatbot.tf // moduleを使用しようとしたが動かなったためここだけ手動 ├── provider.tf
実装
まず、aws_cloudwatch_log_group_variables.tfにロググループのリストを定義します。このロググループのリストそれぞれに対してmetric_filterを定義し、さらに、定義したmetric_filterそれぞれに対してaws_cloudwatch_metric_alarm.tfにてアラームを設定し、アラームの通知先をSNSトピックにします。
1、対象となるlog-groupのリストを定義
今回はすでにあるlog_groupを使用します。このcloudwatch_log_groupsにMetric filtersを設定したいlog_groupを指定します。 もちろん、log_group自体もterraformで管理しているのであれば、それに対して依存関係をもたせることで実現できます。
log_groupの数は相当数あることが多いため、for_eachを使用できるよう変数として準備しておきます。 このロググループの集合に対してフィルターとアラームを設定していくことになるため、もし監視対象を増やしたい場合、このリストにロググループを追加していくことで自動的にフィルターとアラームが追加されるようになります。
variable cloudwatch_log_groups { default = [ "/aws/lambda/go-gateway-test", "/aws/lambda/gotest", ] }
2、metric_filterの定義
標準出力によって吐かれたログからERRORという文字列を検出する
こちらは、cloudwatchのMetric filtersによって実現できます。Filter patternに”ERROR”という文字列を設定するだけで可能です。もちろん、Filter patternを柔軟に設定することで、さまざまな通知が可能です。(極論、標準出力されている文字列についてはすべて検出可能であるということです。)注意点として、このFilter patternは大文字小文字を区別します。今回の要件では、すべて大文字の”ERROR”ですから問題ありませんが、”Error”や”error”といった文字列を同時に検出する場合はmetricを複数定義する必要があります。
for_eachで、先程 ここで設定する"name"と"error_alarm"がalarmとの紐付けになるため、前もって名前空間の定義を行っておくといいでしょう。
Metric filters の定義
resource "aws_cloudwatch_log_metric_filter" "error_alarm" { # for_eachを利用するためにsetにする # 本来であればlog_groupのresourceに対して依存関係をもたせる for_each = toset(var.cloudwatch_log_groups) name = "${each.key}/error" pattern = "ERROR" log_group_name = each.key metric_transformation { name = "${each.key}/error" namespace = "error_alarm" value = 1 } }
3、Metric Alarm の定義
今回は、エラーが発生次第アラームを飛ばしたいので、その要件を基に定義していきます。 下記は「”ERROR”という文字列が120秒の間に1回以上発生した場合はnortification-slackにイベントをパブリッシュする」という例です。 1以上ですので、comparison_operatorをGreaterThanOrEqualToThreshold、thresholdを1にします。また、発生数が1以上であれば通知したいので、statisticはSumにします。アラームが発生した際のパブリッシュ対象になるalarm_actionsの宛先は後述するSNSトピックのarnを指定します。
Metric filtersとの紐付けは、metric_nameとnamespaceのセットで行います。metric_nameがあっていてもnamespaceがあっていなければ紐付けができないので注意してください。
その他設定値については下記ドキュメントへ
# metric-filter.tfで定義したカスタムメトリクス用のアラーム resource "aws_cloudwatch_metric_alarm" "test-alarm" { for_each = aws_cloudwatch_log_metric_filter.error_alarm alarm_name = "${each.key}/alarm" comparison_operator = "GreaterThanOrEqualToThreshold" evaluation_periods = "1" metric_name = "${each.key}/error" namespace = "alarm/error" period = "120" statistic = "Sum" threshold = "1" alarm_description = "This metric monitors error occurence" # データ不足時のアクションを指定 insufficient_data_actions = [] # arnを指定すること alarm_actions = [aws_sns_topic.nortification-slack.arn] }
3、SNS Topic の定義
こちらはSlack通知用SNSトピックの定義です。
resource "aws_sns_topic" "nortification-slack" { name = "nortification-slack" }
Chatbotの定義
こちらは画面上でポチポチします。(terraform apply後でないとSNSトピックがないので注意)
生成されたリソース
指定したロググループにフィルターが設定されています。
また、作成したメトリクスにたいしてアラームが設定されてます。
アラームは作成したSNSトピックにパブリッシュされています。
まとめ
大量のロググループに対して画面をポチポチしながらカスタムメトリクスの設定をするのは骨が折れる作業でしたので、一回コード化してしまえばかなり楽だと感じています。
ただし、監視しやすくするほど監視しすぎることが問題になるので、大量にメトリクスとアラームを作るのはやめましょう