1.この記事を書こうと思った背景
複数の AWS Lambda に CloudWatch Alarms で監視しよう!ということがあったので、 Terraform で以下のように dimensions
で監視したい Lambda を列挙するだけだろ!と思いきや、ダメだった。
apply すると、最後に書いた FunctionName
のみがCloudWatch Alarms に適用されてしまった。(以下の場合、my_lambda_function2
)
resource "aws_cloudwatch_metric_alarm" "this" {
metric_name = "Errors"
namespace = "AWS/Lambda"
dimensions = {
"FunctionName" = "my_lambda_function1",
"FunctionName" = "my_lambda_function2"
}
#略
}
※ FunctionName
に *
をつけても「*_lambda_function」などとベタ書きとして認識されてしまってダメ。正規表現っぽく指定することは出来なかった。
dimensions = {
"FunctionName" = "*_lambda_function",
}
さて、じゃあどうしようか?と調べたことを書き留めておく。
なお、上記の dimensions
は以下を参考に指定した。
Choose a dimension.
By Function Name (FunctionName)
– View aggregate metrics for all versions and aliases of a function.By Resource (Resource) – View metrics for a version or alias of a function.
By Executed Version (ExecutedVersion) – View metrics for a combination of alias and version. Use the ExecutedVersion dimension to compare error rates for two versions of a function that are both targets of a weighted alias. Across All Functions (none) – View aggregate metrics for all functions in the current AWS Region.
参考:Working with Lambda function metrics - AWS Lambda
2.前提
- AWS Lambda のエラーを CloudWatch Alarms で検知してから、Slack へ通知が届くまでの大まかな流れは以下のとおり
- AWS Chatbot 経由で Slack に通知するには Amazon SNS や、SNS から Topic を受け取り、パブリッシュする AWS Chatbot や Lambda の設定が必要だが、ここではそれらの設定方法については割愛する
- 以下の記事は、SNS Topic や Slack へ Topic をパブリッシュする Lambda などの設定方法について解説されているので、適宜参照してほしい
3.環境情報
$ terraform version
Terraform v1.1.7
on linux_amd64
+ provider registry.terraform.io/hashicorp/aws v4.9.0
この記事では、複数の AWS Lambda のエラーを CloudWatch Alarms で検知できるように Terraform で定義する方法について、2案紹介する。
4.[案1]metric-alarms-by-multiple-dimensions という CloudWatch のサブモジュールを使う
ひとつめのアイデアは、以下の CloudWatch Alarm モジュールの、 metric-alarms-by-multiple-dimensions
というサブモジュールを使う方法だ。
以下がサンプルコードだ。CloudWatch Alarms のメトリクスの意味、使い方について添えている。
module "metric_alarms" {
source = "terraform-aws-modules/cloudwatch/aws//modules/metric-alarms-by-multiple-dimensions"
version = "~> 3.0"
alarm_description = "all-lambda-functions-alarm"
alarm_name = "all-lambda-functions-alarm"
# 以下を参考に「メトリクスを集計する AWSサービス」を指定する
# https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/aws-services-cloudwatch-metrics.html
namespace = "AWS/Lambda"
# 後述する `aws-cli` で指定できる値を確認している。
metric_name = "Errors"
# メトリクスの統計(集計方法)
# 以下のいずれかを指定し、集計した値と `threshold` とを `comparison_operator` の比較方法で比較する。
# Average | Maximum | Minimum | SampleCount | Sum
statistic = "Maximum"
# 閾値
threshold = 1
# 閾値と比較する回数
evaluation_periods = 1
# `evaluation_periods` 1回あたりの統計が適用される期間(秒単位)
# 有効な値は、10、30、60、および60の任意の倍数。
period = 60
# アラーム状態へ遷移させるか判定する基準
# `period` * `evaluation_periods` のあいだに `datapoints_to_alarm` 回だけ `threshold` を、
# `comparison_operator` で指定する比較方法で比較し、TRUE である場合、アラーム状態へ遷移
# 上記の場合、60秒 * 1 = 60秒のあいだに、閾値である1か、1以上のエラーが検知されたら、アラーム状態へ遷移となる。
datapoints_to_alarm = 1
# `threshold` との比較演算子であり、以下のいずれかを指定。
# GreaterThanThreshold, GreaterThanOrEqualToThreshold, LessThanThreshold, or LessThanOrEqualToThreshold
comparison_operator = "GreaterThanOrEqualToThreshold"
# AWS Lambda の場合、以下を参考に入力。
# https://docs.aws.amazon.com/lambda/latest/dg/monitoring-metrics.html
# ここで「複数の Lambda」を監視するように指定できる!!
dimensions = {
"lambda1" = {
FunctionName = "my_lambda_function1"
},
"lambda2" = {
FunctionName = ""my_lambda_function2"
}
}
# `ALARM state`(ALARM 状態)に移行したら実行される AWSサービスを指定
# ここでは SNS Topic をパブリッシュしている
alarm_actions = [aws_sns_topic.this.arn]
# データの欠損がみられたら、閾値内とする(閾値を超えていない)
treat_missing_data = "notBreaching"
# データの欠損がみられたら、閾値外とする(閾値を超えた)
#treat_missing_data = "Breaching"
# データの欠損については、後述する「6.データが欠損していた場合の対応を考えることが難しい」でも扱う
}
※ サブモジュールの実装を眺めると以下のとおり、dimensions
で指定した値の数だけ for-each で繰り返し aws_cloudwatch_metric_alarm
リソースを作成しているらしく、実際、applyするとリソースが dimensions
で指定した値の数だけ作成された。
サンプルコードのエラー検知ロジックについて
サンプルコードのコメントに書いているが再掲する。
# `period` * `evaluation_periods` のあいだに `datapoints_to_alarm` 回だけ `threshold` を、
# `comparison_operator` で指定する比較方法で比較し、TRUE である場合、アラーム状態へ遷移
# 上記の場合、60秒 * 1 = 60秒のあいだに、閾値である1か、1以上のエラーが検知されたら、アラーム状態へ遷移となる。
metric_name = "Errors"
statistic = "Maximum"
threshold = 1
evaluation_periods = 1
period = 60
datapoints_to_alarm = 1
comparison_operator = "GreaterThanOrEqualToThreshold"
metric_name で指定できる値について
metric_name
で具体的にどういった値を指定すればいいのか?ドキュメントに記載がないように見受けられたので aws-cli
で以下のように取得。
$ aws cloudwatch list-metrics --namespace AWS/Lambda \
> | jq -r '[.Metrics[].MetricName] | unique'
[
"ConcurrentExecutions",
"Duration",
"Errors",
"Invocations",
"Throttles",
"UnreservedConcurrentExecutions"
]
参考:Viewing available metrics - Amazon CloudWatch
output も定義したい場合
output
については、同サブモジュールで定義されている output
にアクセスして値を引っ張りたいので、module.<MODULE NAME>.<OUTPUT NAME>
のように書く。
output "cloudwatch_metric_alarm_arns" {
description = "List of ARNs of the Cloudwatch metric alarm"
value = module.metric_alarms.cloudwatch_metric_alarm_arns
}
output "cloudwatch_metric_alarm_ids" {
description = "List of IDs of the Cloudwatch metric alarm"
value = module.metric_alarms.cloudwatch_metric_alarm_ids
}
参考:Accessing Child Module Outputs | Output Values - Configuration Language | Terraform by HashiCorp
terraform apply を終えた後の様子
以下の画像は、terraform apply
を終えた後の CloudWatch Alarms のコンソール画面だ。FunctionName
に dummy_slack_notify
と Lambda の名前と Namespace
に AWS/Lambda
と表示されていることが確認できる。
Lambda のエラーメッセージが Slack に通知されていることも確認できた!
5.[案2]Function Name を指定せず、Namespace に AWS/Lambda を指定する
ふたつめは、以下の Stack Overflow の記事を参考に Terraform で定義する。
These metrics
don't have any dimensions, just the namespace and metric name,
参考:amazon web services - CloudFormation alarm for multiple Lambdas - Stack Overflow
dimensions
で FunctionName
を定義してしまうと、FunctionName
で定義した Lambda のみが CloudWatch Alarms による監視対象となってしまうので注意!!
resource "aws_cloudwatch_metric_alarm" "this" {
actions_enabled = true
alarm_description = "all-lambda-functions-alarm"
alarm_name = "all-lambda-functions-alarm"
# Namespace で Lambda を指定することは必須!
namespace = "AWS/Lambda"
metric_name = "Errors"
# メトリクスの統計(集計方法)
statistic = "Maximum"
# 閾値
threshold = 1
# 閾値と比較する回数
evaluation_periods = 1
# `evaluation_periods` 1回あたりの統計が適用される期間(秒単位)
period = 60
# アラーム状態へ遷移させるか判定する基準
datapoints_to_alarm = 1
# `threshold` との比較演算子。
comparison_operator = "GreaterThanOrEqualToThreshold"
tags = {}
tags_all = {}
# データの欠損がみられたら、閾値内とする(閾値を超えていない)
treat_missing_data = "notBreaching"
# データの欠損がみられたら、閾値外とする(閾値を超えた)
#treat_missing_data = "Breaching"
# `ALARM state`(ALARM 状態)に移行したら実行される AWSサービスを指定。
# ここでは SNS Topic をパブリッシュしている
alarm_actions = [aws_sns_topic.this.arn]
}
terraform apply を終えた後の様子
以下の画像も同様に、terraform apply
を終えた後の CloudWatch Alarms のコンソール画面だ。こちらは FunctionName
は表示されていない一方で、Namespace
では AWS/Lambda
が確認できる。
6.データが欠損していた場合の対応を考えることが難しい
データが欠損していた場合、安全に倒して通知するか?許容するか?といった判断を閾値に反映させることが難しかった。treat_missing_data
を指定する意義について、自分の考えを整理しておく。
仮に、treat_missing_data
が未指定であると、データが欠損しているために、閾値を超え、監視対象のステータスを ALARM へ遷移させるかどうかという観点での閾値を超えた回数の判定ができなくなってしまう。
以下の AWS のドキュメントを読むと、たとえば、treat_missing_data = notBreaching
とすることで、 データが欠損している分については「閾値内」とみなし判定することができるのではないか?と感じた。
参考:Using Amazon CloudWatch alarms - Amazon CloudWatch
また、以下の検証記事によると欠損データがある場合については以下のように書いてあり、自分でもドキュメントを読んだかぎりでは、明確に「どこまで遡るか」書かれていないように見受けられた。
正確にどこまで遡るかは細かな仕様が書かれていませんので不明
参考:CloudWatch Alarm欠落データの処理を確かめる - Blogaomu
7.サンプルコード
main.tf
module "metric_alarms" {
source = "terraform-aws-modules/cloudwatch/aws//modules/metric-alarms-by-multiple-dimensions"
version = "~> 3.0"
alarm_description = "all-lambda-functions-alarm"
alarm_name = "all-lambda-functions-alarm"
namespace = "AWS/Lambda"
metric_name = "Errors"
statistic = "Maximum"
threshold = 1
evaluation_periods = 1
comparison_operator = "GreaterThanOrEqualToThreshold"
period = 60
# https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-cw-alarm.html
#unit = "Count"
datapoints_to_alarm = 1
dimensions = {
"lambda1" = {
FunctionName = "my_lambda_function1"
},
"lambda2" = {
FunctionName = "my_lambda_function2"
}
}
alarm_actions = [aws_sns_topic.this.arn]
treat_missing_data = "notBreaching"
}
resource "aws_sns_topic" "this" {
application_success_feedback_sample_rate = 0
content_based_deduplication = false
fifo_topic = false
firehose_success_feedback_sample_rate = 0
http_success_feedback_sample_rate = 0
lambda_success_feedback_sample_rate = 0
name = "cloudwatch-alarm-for-chatbot-topic"
sqs_success_feedback_sample_rate = 0
tags = {}
tags_all = {}
}
resource "aws_sns_topic_policy" "this" {
arn = aws_sns_topic.this.arn
policy = data.aws_iam_policy_document.this.json
}
resource "aws_sns_topic_subscription" "this" {
endpoint = "https://global.sns-api.chatbot.amazonaws.com"
protocol = "https"
raw_message_delivery = false
topic_arn = aws_sns_topic.this.arn
}
data "aws_iam_policy_document" "this" {
policy_id = "__default_policy_ID"
statement {
actions = [
"SNS:GetTopicAttributes",
"SNS:SetTopicAttributes",
"SNS:AddPermission",
"SNS:RemovePermission",
"SNS:DeleteTopic",
"SNS:Subscribe",
"SNS:ListSubscriptionsByTopic",
"SNS:Publish",
]
effect = "Allow"
resources = [
aws_sns_topic.this.arn
]
condition {
test = "StringEquals"
values = [var.aws_account_id]
variable = "AWS:SourceOwner"
}
principals {
type = "AWS"
identifiers = ["*"]
}
}
}
output "cloudwatch_metric_alarm_arns" {
description = "List of ARNs of the Cloudwatch metric alarm"
value = module.metric_alarms.cloudwatch_metric_alarm_arns
}
output "cloudwatch_metric_alarm_ids" {
description = "List of IDs of the Cloudwatch metric alarm"
value = module.metric_alarms.cloudwatch_metric_alarm_ids
}
8.参考
- CloudWatch Alarms のドキュメント
- CloudWatch Alarms のメトリクスの意味や使い方について
metric_name
の一覧をaws-cli
コマンドで取得する方法について- CloudWatch Alarm のサブモジュールである、
metric-alarms-by-multiple-dimensions
のドキュメント - 閾値周りを考える上で参考にした記事
- データが欠損していた場合の対応を考える際に参考にした記事