概要
ECSタスクの異常終了などの死活監視をしているとき、その結果をメトリクス(グラフ)として保存しておくと、ダッシュボード上で異常終了した履歴が分かって、さらにそこから簡単にアラーム発火もできて便利だなと思い、作ってみました。
前提
ECSはFargateで構築しています。(が、基本的にはon EC2も変わらないかと思います。)
手法
CloudWatch Logsのメトリクスフィルター機能で、カスタムメトリクスを作成します。
そこで、メトリクスフィルターでフィルタリングするためには、ECSタスクの異常終了情報をCloudWatch Logsに転送する必要があります。
そのため、EventBridgeでECSタスクの異常終了イベントをCloudWatch Logsに転送します。
EventBridgeから直接SNSトピックに飛ばして死活監視をするのはよくありますが、今回はメトリクスとして残したいのでCloudWatch Logsを挟んでいます。
機能・サービス説明
メトリクスフィルター
メトリクスフィルターとは、CloudWatch Logsから特定の検索条件に一致するログの件数などをカスタムメトリクスに落とし込む機能です。
カスタムメトリクス
カスタムメトリクスとは、デフォルトでAWSが提供するメトリクスとは別に、ユーザーが自由に作成できるメトリクス(グラフ)になります。
EventBridge
上記の手法項目の通り、メトリクスフィルターはCloudWatch Logsのロググループをもとに作成されるため、CloudWatch LogsにECSの異常終了情報を保管するロググループがなければいけません。
しかし、ECSの異常終了情報というのは基本的には「イベント」として発生するものであり、ログがCloudWatch Logsに貯まるものではありません。
そこで、EventBridgeによって、ECSのイベントをCloudWatch Logsに転送するようにします。
コード
※GitHubにもあります。
AWSTemplateFormatVersion: "2010-09-09" Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: "ECSClusterArn" Parameters: - ECSClusterArn - Label: default: "ECSMetricNamespace" Parameters: - ECSMetricNamespace Parameters: ECSClusterArn: Type: String ECSMetricNamespace: Type: String Resources: ResourcePolicyForEventsToLogs: Type: AWS::Logs::ResourcePolicy Properties: PolicyName: "ECSEvents-ToLogs-ResourcePolicy" PolicyDocument: !Sub - | { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "events.amazonaws.com" }, "Action": [ "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "${CloudWatchLogsLogGroupArn}" } ] } - CloudWatchLogsLogGroupArn: !GetAtt ECSEventsLogGroup.Arn ECSEventsLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: /aws/events/ECSEvents ECSEventsRule: Type: AWS::Events::Rule DependsOn: ResourcePolicyForEventsToLogs Properties: Name: ECSEventsToLogs Description: "Transfer ECSEvents to CloudWatch Logs" State: ENABLED EventPattern: { "source": ["aws.ecs"] } Targets: - Arn: !GetAtt ECSEventsLogGroup.Arn Id: !Sub "ECSEventsLogGroup" TaskExitEventsFilter: Type: AWS::Logs::MetricFilter Properties: FilterPattern: !Sub - '{ ($.detail.clusterArn = "${ECSClusterArn}") && ($.detail-type = "ECS Task State Change") && ($.detail.desiredStatus = "STOPPED") && ( ($.detail.stoppedReason = "Essential container in task exited") || ($.detail.stoppedReason = "Task failed container health checks") || ($.detail.stoppedReason.prefix = "Task failed ELB health checks") ) }' - ECSClusterArn: !Ref ECSClusterArn LogGroupName: !Ref ECSEventsLogGroup MetricTransformations: - MetricName: "ECS-TaskExitEvents" MetricNamespace: !Ref ECSMetricNamespace MetricValue: "1" TaskExitEventsAlarm: Type: AWS::CloudWatch::Alarm Properties: AlarmName: ECS-TaskExitEvents-Alarm ActionsEnabled: true AlarmActions: - !Ref SNSTopic MetricName: "ECS-TaskExitEvents" Namespace: !Ref ECSMetricNamespace Statistic: "Sum" Period: 300 EvaluationPeriods: 1 DatapointsToAlarm: 1 Threshold: 1 ComparisonOperator: "GreaterThanOrEqualToThreshold" TreatMissingData: "notBreaching"
解説
EventBridgeでのログ転送
こちらは以下が対象になります。
リソースポリシーがCloudWatch Logsのロググループに与えられていないと、EventBridgeからログの転送ができません。
そのためロググループに付与するリソースポリシーを定義し、EventBridgeの「ルール」でイベントをログに転送するパターンとターゲットのログを設定します。
こちらの構成の細かい解説や注意点などは以下記事にて説明しております。 go-to-k.hatenablog.com
ここでは、全イベントのログをCloudWatch Logsに転送し、そこからメトリクスフィルターでフィルタリングをしてカスタムメトリクスを作成します。
メトリクスフィルター
- AWS::Logs::MetricFilter
今回の肝はこちらです。
TaskExitEventsFilter: Type: AWS::Logs::MetricFilter Properties: FilterPattern: !Sub - '{ ($.detail.clusterArn = "${ECSClusterArn}") && ($.detail-type = "ECS Task State Change") && ($.detail.desiredStatus = "STOPPED") && ( ($.detail.stoppedReason = "Essential container in task exited") || ($.detail.stoppedReason = "Task failed container health checks") || ($.detail.stoppedReason.prefix = "Task failed ELB health checks") ) }' - ECSClusterArn: !Ref ECSClusterArn LogGroupName: !Ref ECSEventsLogGroup MetricTransformations: - MetricName: "ECS-TaskExitEvents" MetricNamespace: !Ref ECSMetricNamespace MetricValue: "1"
FilterPattern
に、ECSの死活監視として、検知したいパターンを記述しています。
FilterPattern: !Sub - '{ ($.detail.clusterArn = "${ECSClusterArn}") && ($.detail-type = "ECS Task State Change") && ($.detail.desiredStatus = "STOPPED") && ( ($.detail.stoppedReason = "Essential container in task exited") || ($.detail.stoppedReason = "Task failed container health checks") || ($.detail.stoppedReason.prefix = "Task failed ELB health checks") ) }'
イベントはJSON形式で保存されますが、「$.キー」というような記法で特定のキーの値を参照することができます。
フィルタリングするイベントのパターンとしては、具体的には以下のような内容です。
detail.clusterArn
$.detail.clusterArn = "${ECSClusterArn}"
detail-type
$.detail-type = "ECS Task State Change"
ECS Task State Change
に絞ることでタスクの状態変更のみを対象にします。
detail.desiredStatus
$.detail.desiredStatus = "STOPPED"
- タスクの状態変更のうち、「停止(状態)」のみを対象にします。
detail.stoppedReason
正常終了は検知せず、異常終了のみを検知したいため、以下の条件に絞っています。
$.detail.stoppedReason = "Essential container in task exited"
- 「essential パラメータが true のコンテナが終了またはクラッシュしたためタスクが失敗」した
$.detail.stoppedReason = "Task failed container health checks"
- 「タスクがコンテナレベルのヘルスチェックに失敗」した
$.detail.stoppedReason.prefix = "Task failed ELB health checks"
- 「ALB のヘルスチェックでタスクが失敗」した
実はこのパターンの部分は、AWS公式の「Amazon Web Services ブログ」の記事「Amazon EventBridge を利用した Amazon Elastic Container Service Anomaly Detector」より参考にさせていただいています。
こちらをご覧になった方がわかりやすく、また色々な検知をされているソリューションになっています。
MetricTransformations
では、メトリクス(グラフ)としての情報を設定します。
MetricTransformations: - MetricName: "ECS-TaskExitEvents" MetricNamespace: !Ref ECSMetricNamespace MetricValue: "1"
MetricName
、MetricNamespace
に関しては、メトリクスフィルターと合わせてください。
MetricValue
は、1にしておくことで、フィルターに一致したときに1ずつカウントするというものです。基本的には1で良いかと思います。
CloudWatchアラーム
- AWS::CloudWatch::Alarm
メトリクスフィルターから簡単にアラームも発火できるため、ついでに書きました。
メトリクスフィルターで作成したメトリクスの名前と名前空間を指定して設定します。
あくまでついでなので、細かい設定の説明は省略します。
TaskExitEventsAlarm: Type: AWS::CloudWatch::Alarm Properties: AlarmName: ECS-TaskExitEvents-Alarm ActionsEnabled: true AlarmActions: - !Ref SNSTopic MetricName: "ECS-TaskExitEvents" Namespace: !Ref ECSMetricNamespace Statistic: "Sum" Period: 300 EvaluationPeriods: 1 DatapointsToAlarm: 1 Threshold: 1 ComparisonOperator: "GreaterThanOrEqualToThreshold" TreatMissingData: "notBreaching"
補足
ECSタスクの異常終了情報をメトリクスフィルターによってカスタムメトリクスにすることで、グラフとして表示したりできるようになりました。
しかし、基本的にはそんなに異常終了しないと思うので、このグラフは線にはならず点になるかと思います。
いわゆるグラフというと折線グラフを想像するかと思いますが、これが折れ線グラフになってしまうと、しょっちゅう落ちているということになります。
むしろそうでない、点どころか、データポイントがないのが安心なので、「期待外れな方が良いグラフ」になってしまいました・・・笑
まあとにかくメトリクスにしておくことで、普段見るダッシュボードにグラフを追加して眺めることができるので、現状の状況や過去のアラーム状況を追いやすくなり意外と便利です。
最後に
元を辿るとCloudWatch LogsのリソースポリシーがCloudFormationに対応したことを知って、それ関係の記事を書いていたら色々と派生する内容が思い浮かんできて、つい色々関連した記事を書いてしまいました。
連続で投稿が似た内容になってしまうのはご愛嬌で。。。