前置き・概要
計画メンテナンスなど、特定期間だけアラート発報を抑制したい場合があります。
監視ツールには大抵「ダウンタイム」というアラート発報抑制期間を設定する項目があります。
Zabbix の「メンテナンス期間」であれば「毎日xx時からxx時間」「隔週xx曜日」といった柔軟な指定が可能です。
CloudWatchアラームに「ダウンタイム」的な設定項目はありません。
CloudWatch Events + Lambda (or SSM Automation)を利用すれば似たようなことが出来ますが、
アラームアクションを有効化/無効化するタイミングをUTCタイムゾーンのcron形式で表現しなければならず、
前述の監視ツールに比べると分かりやすさ・柔軟性に欠けます。
今回は以下のようなシステムを構築することで、CloudWatchアラームに分かりやすいダウンタイムを設定します。
- Google カレンダーに予定を作成し、説明欄にCloudWatchアラーム名を記載
- 予定が開始される時、CloudWatchアラームアクションを無効化
- 予定が終了する時、CloudWatchアラームアクションを有効化・アラームステータスをOKに変更
ダウンタイム中にALARM状態に変化した場合、その後でアラームアクションを有効化してもSNSへの通知は行われません。
そこで、アラームアクション有効化後、アラームステータスを一旦「OK」の状態にします。
すると、再度ALARM状態に変化した際にSNSへの通知が行われることになります。
前準備
IFTTT サインアップ
ifttt.com にアクセスし、サインアップしておきます。
IFTTT(イフト)はサービス自動連係ツールです。
今回利用するGoogleカレンダー以外にも、Slack や Facebook も利用可能です。
CloudWatchアラーム 作成
ON/OFF の対象となるCloudWatchアラームを作成しておきましょう。
ちなみに、今回実施するのは「アラームの有効/無効化」ではなく「アラームアクションの有効/無効化」です。
アクションの有効/無効が分かるように項目を表示しておきましょう。
※画像は編集を加えています。
以下のように、有効かどうかが確認できます。
ダウンタイム設定システム 構築
AWS
SSMパラメータ
IFTTT の webhook で API Gateway にPOSTする際、HTTP BODYにAPIキー(的なもの)を含めます。
そのキーとSSMパラメータの値が一致すればCloudWatchアラームアクション無効化/有効化を行います。
認証に使う文字列なので、Secure String (安全な文字列) として作成し、暗号化します。
Secure String は CFn をサポートしていないので手動で作成します。
Lambda 周り
Lambda 関数やIAMロールなど、以下の CFnテンプレートで作成可能です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
--- AWSTemplateFormatVersion: 2010-09-09 Description: "Lambda Function and additinal resoureces for CloudWatch Alarm downtime system." Metadata: "AWS::CloudFormation::Interface": ParameterGroups: - Label: default: "Lambda" Parameters: - NameOfFunction - Label: default: "SSM Parameter Store" Parameters: - NameOfSsmParamForApiKey ParameterLabels: NameOfFunction: default: "Function Name" NameOfSsmParamForApiKey: default: "Parameter Name" # ------------------------------------------------------------# # Input Parameters # ------------------------------------------------------------# Parameters: NameOfFunction: Type: String Default: set-alarm-downtime NameOfSsmParamForApiKey: Type: AWS::SSM::Parameter::Name Resources: # ------------------------------------------------------------# # Disable Alarm Function Resources # ------------------------------------------------------------# # IAM Role RoleForFunction: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: !Sub "${NameOfFunction}-Policy" PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "ssm:GetParameters" - "kms:Decrypt" - "cloudwatch:DisableAlarmActions" - "cloudwatch:EnableAlarmActions" - "cloudwatch:SetAlarmState" Resource: "*" # Lambda Function Function: Type: AWS::Lambda::Function Properties: Code: ZipFile: | import boto3 import json import os ssm = boto3.client('ssm') cloudwatch = boto3.client('cloudwatch') def lambda_handler(event, context): http_body_json = event['body'] http_body_dict = json.loads(http_body_json, strict=False) # get ssm parameter info, including api key ssm_param_info = ssm.get_parameters( Names = [ os.environ['ssm_param'], ], WithDecryption = True ) # if provided api key matches ssm parameter secure string if (ssm_param_info['Parameters'][0]['Value'] == http_body_dict['ApiKey']): alarms = http_body_dict['Alarms'].replace(' ', '').split('\n') event_type = http_body_dict['EventType'] # if provided event type is 'start', then disable alarm action(s) if (event_type == 'start'): response = cloudwatch.disable_alarm_actions( AlarmNames=alarms ) print(response) # if provided event type is 'end' # then enable alarm action(s) and change state of alarm(s) to 'OK' elif (event_type == 'end'): response = cloudwatch.enable_alarm_actions( AlarmNames=alarms ) print(response) for alarm in alarms: response = cloudwatch.set_alarm_state( AlarmName=alarm, StateValue='OK', StateReason='non-alart time ended' ) print(response) # if provided event type is not 'start' and 'end' # then print error message else: print('Error: invalid event type.') # if provided api key does not matche ssm parameter secure string # then print error message else: print('Error: API Key missmatch.') Environment: Variables: ssm_param: !Ref NameOfSsmParamForApiKey FunctionName: !Ref NameOfFunction Handler: index.lambda_handler MemorySize: 128 Role: !GetAtt RoleForFunction.Arn Runtime: python3.7 Timeout: 120 DependsOn: LogGroupForFunction # CloudWatch Logs LogGroup LogGroupForFunction: Type: AWS::Logs::LogGroup Properties: RetentionInDays: 7 LogGroupName: !Sub "/aws/lambda/${NameOfFunction}" |
API Gateway
API Gateway (HTTP API)を作成します。
HTTP API は CFn をサポートしていないので手動作成します。Lambda のコンソールから作成可能です。
API Gateway のエンドポイントを控えておきましょう。
Google カレンダー
新しいカレンダーを作成します。このカレンダーに作成した予定の期間内、CloudWatchアラームアクションが無効化されます。
IFTTT
Googleカレンダー イベント開始時
[Create] でアプレットを作成します。 [This] でトリガー(Google カレンダー)を設定します。Google Calendar を選択します。
[Any event starts] (=カレンダーで予定が開始された時)を選択します。先ほど作成したカレンダーを選択し [Create Trigger] します。
次に [That] でアクション(Webhook)を作成します。
Webhooks を選択します。
[Make a web request] を選択します。次の画面で、いくつか項目を入力して [Create action] を選択します。
URL | https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/default/set-alarm-downtime |
Method | POST |
Content Type | application/json |
Body | { "ApiKey": "xxxxxxxxxx", "EventType": "start", "Alarms": "{{Description}}" } |
- URL は、API Gateway のURLです。
- Body の xxxxxxxxxx はAPIキー(的なもの)です。先ほど作成したSSMパラメータの値を入力します。
- Body の {{Description}} はそのままで大丈夫です。IFTTT で扱える変数で、Google カレンダーの予定の説明欄の内容を表します。
最後にアプレットに名前を付けて完了です。
これで、Google カレンダーで予定が開始された時に API Gateway に HTTP POST されます。
その BODY には json 形式で以下が含まれています。
APIキー(的なもの) | この文字列をSSMパラメータの値と比較し、一致しない場合 Lambda はそこで止まります。 |
イベントタイプ | 予定の「開始」または「終了」を表します。 開始の場合、対象のCloudWatchアラームアクションを無効化します。 終了の場合、アラームアクションを有効化しアラームの状態を「OK」に更新します。 |
予定の説明 | Googleカレンダーの予定の説明欄に記載されている文字列です。 今回はここに、有効化/無効化するCloudWatchアラーム名を記載します。 |
Googleカレンダー イベント終了時
イベント開始時と同じ流れで IFTTT のアプレットを作成します。先ほどと異なるのは以下2点です。
- トリガーとして [Any event ends] (=カレンダーで予定が終了した時) を選択する
- Webhooks の Body は以下のようにする
1 |
{ "ApiKey": "xxxxxxxxxx", "EventType": "<span style="color: #ffff00"><strong>end</strong></span>", "Alarms": "{{Description}}" } |
ダウンタイム設定システムを試す
試す
Googleカレンダーで、以下のような予定を作成します。設定した期間内、アラームが無効化されます。
それまで有効だったアラームアクションが…
カレンダーの予定開始時間になると、無効化されました。
予定終了時間になるとアクションが有効化され、復旧(OK)状態になります。
(実際に復旧したわけではなく、Lambda の set_alarm_state で一時的に状態を変更しています)
しばらくすると、アラームの状態が本来のものになります。
こうすることで、ダウンタイム期間が終了して尚 継続しているアラートも通知されるようになっています。
「ダウンタイム開始前からNG状態だったアラームが、ダウンタイム終了後に通知されてしまう」
というのが難点、というか、通常の監視ツールで利用できるダウンタイムとの違いです。
アラームアクションがなかなか有効化/無効化されない時
Googleカレンダーの予定が開始/終了されてから Webhooks がトリガーされるまで時間がかかる場合があります。
そんな時はアプレットの Settings を開き、
投稿者プロフィール
- 2015年8月入社。弊社はインフラ屋ですが、アプリも作ってみたいです。