AWSからメンテ通知が来たら5秒でインスタンスを再起動させる方法(Slackメッセージボタンでの事前確認付き)

AWSから不定期に届くメンテ通知や障害メールの対応を、PCやブラウザを使わずにSlackで完結させます。

完成図

できあがったものがこれです。

  • 作成所要時間 約15分 2017-11-20 10_00_16-aws-mentenance.json - FrontOps.png

※定型対応フローが簡単に作れるアプリFrontOps を使っています。

はじめに

ある日、こんな件名のメールが届きます。

  • Amazon EC2 Maintenance - Maintenance
  • [Retirement Notification]

インスタンスが動いている物理サーバをメンテするから、いつまでにインスタンスを移動してください。
インスタンスの状態が異常なので、インスタンスを確認して再起動してください。

といった内容です。
このメール、たまにしか来ないことに加え、英語なこともあり他の迷惑メールや広告メールに紛れて見落とすことがよくありました。

そこで、このメールを受信したらSlackに通知させて見落としを防止することにしました。

またこのメール。メール文中にはインスタンスID の記載しかないので、インスタンス名を確認しないと影響判断ができません。
ただ、インスタンス名を確認するにはいちいちAWSのマネジメントコンソールにログインする必要があって面倒です。

そこでSlack通知の際に、インスタンス名を補足させることにします。

また、このメールの対応としてはインスタンスを再起動するしかありません。
機械的に再起動させてもいいのですが、
再起動前に事前作業が必要なインスタンスもあるので、
事前にSlackのメッセージボタンを使って承認を挟むことにします。

必要なもの

  • FrontOps (https://frontops.exhands.org)
    • 無料アカウントで、アプリをダウンロードして利用することができます
  • AWS CLIが使えてSSHでログインできるサーバ
    • あらかじめaws configureでクレデンシャル等は設定しておきます。
  • 通知先のSlackチャンネル

設定説明

全体フローは冒頭にあげた通りです。以下では各ノードの設定を説明します。

メール定期受信

メールを定期的に受信させます。1分ごとにしました。
2017-11-21 08_15_52-aws-mentenance.json - FrontOps.png

件名でフィルタ

メールの件名は、msg.topicに入ってきます。msg.topicに文字列が含まれるかどうかを設定します。
条件に該当する場合は次のノードに進みます。
条件に該当しない場合はここで処理が終わります。
2017-11-20 23_48_12-aws-mentenance.json - FrontOps.png

インスタンスID取得

メールの本文は、msg.payloadに入ってきます。
ここからJSONATAを使ってインスタンスIDを抽出して、msg.instanceIdに設定します。

$match(payload, /^(i-\w+)/m).groups

2017-11-21 07_41_19-aws-mentenance.json - FrontOps.png

インスタンス名取得

インスタンス名は、ふだん使っているサーバにSSHログインしてAWS CLIで取得します。

取得した結果は、msg.instanceNameに設定します。
コマンド部にはmustacheが使えるので、{{{プロパティ}}}msgオブジェクトのプロパティを展開できます。
ここでは先に取得したインスタンスIDを展開させています。

aws ec2 describe-instances --instance-id {{{instanceId}}} --query 'Reservations[*].Instances[*].[InstanceId,State.Name,Tags[?Key==`Name`].Value]' --output text

2017-11-20 23_48_51-aws-mentenance.json - FrontOps.png

Slack通知

Slackのgeneralチャンネルに

メールの件名(msg.topic)
メールの本文(msg.payload)
インスタンス名(msg.instanceName.stdout)

を通知します。

2017-11-20 23_49_09-aws-mentenance.json - FrontOps.png

再起動実施確認

インスタンスの停止・起動をメッセージボタンで確認します。
よく使うYes/Noの形式なので、情報ウィンドウに表示されているテンプレートをコピペして文言だけ直して使います。

2017-11-20 23_49_28-aws-mentenance.json - FrontOps.png

復唱

どのメッセージボタンが押されたかを復唱させます。この情報はmsg.answerに入ってきます。
これもよくあるパターンなので、情報ウィンドウに表示されているテンプレートをコピペして使います。
2017-11-20 23_49_41-aws-mentenance.json - FrontOps.png

承認結果判定

ボタン操作の結果に応じて分岐を作ります。
2017-11-20 23_49_55-aws-mentenance.json - FrontOps.png

対応終了

2017-11-20 23_50_10-aws-mentenance.json - FrontOps.png

対応開始宣言

2017-11-20 23_50_23-aws-mentenance.json - FrontOps.png

インスタンス停止

インスタンス停止は、AWS CLIでおこないます。
インスタンスを停止させて、ステータスがstoppedになるまで待ちます。
タイムアウトは60秒にしました。
2017-11-20 23_52_15-aws-mentenance.json - FrontOps.png

インスタンス起動

インスタンス起動は、AWS CLIでおこないます。
インスタンスを起動させて、ステータスがrunningになるまで待ちます。
タイムアウトはちょっと長めに90秒にしておきます。
2017-11-21 08_09_04-aws-mentenance.json - FrontOps.png

対応完了報告

対応完了を報告させて終了します。
この前にさらに正常性確認を追加してもいいかもしれません。
2017-11-20 23_51_24-aws-mentenance.json - FrontOps.png

例外処理

タイムアウトやその他エラーが発生した時には、ログに記録を残させると同時にSlackに通知させています。
電話で通知させるパターンもよくあります。

フローデータ

以下をコピーして、[読み込み]>[クリップボード] でフローを取り込むことができます。

[{"id":"67e11027.4cc2c","type":"command","z":"bd84d76c.2d93f8","connectionconfig":"fd7512e2.f22ec","command":"aws ec2 describe-instances --instance-id {{{instanceId}}} --query 'Reservations[*].Instances[*].[InstanceId,State.Name,Tags[?Key==`Name`].Value]' --output text","timer":"15","oldrc":false,"env":"","pty":false,"trim":false,"field":"instanceName","fieldType":"msg","name":"インスタンス名取得","x":190,"y":240,"wires":[["b0addf6c.85c41"]]},{"id":"10686c8e.7ab4d3","type":"e-mail in","z":"bd84d76c.2d93f8","name":"メール定期受信","protocol":"IMAP","server":"imap.gmail.com","useSSL":true,"port":"993","box":"INBOX","disposition":"Read","repeat":"60","x":130,"y":160,"wires":[["20849217.8d4fae"]]},{"id":"20849217.8d4fae","type":"switch","z":"bd84d76c.2d93f8","name":"件名でフィルタ","property":"topic","propertyType":"msg","rules":[{"t":"cont","v":"Amazon EC2 Maintenance - Maintenance","vt":"str"},{"t":"cont","v":"[Retirement Notification]","vt":"str"}],"checkall":"true","outputs":2,"x":310,"y":160,"wires":[["dfb73593.bb2f08"],["dfb73593.bb2f08"]]},{"id":"dfb73593.bb2f08","type":"change","z":"bd84d76c.2d93f8","name":"インスタンスID取得","rules":[{"t":"set","p":"instanceId","pt":"msg","to":"$match(payload, /^(i-\\w+)/m).groups[0]\t","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":510,"y":160,"wires":[["67e11027.4cc2c"]]},{"id":"b0addf6c.85c41","type":"slack-say","z":"bd84d76c.2d93f8","slack":"","channel":"general","msg":"*{{{topic}}}*\n\n```\n{{{payload}}}\n```\n{{{instanceName.stdout}}}","output":"str","gonext":true,"name":"Slack通知","x":380,"y":240,"wires":[["f6e76317.5bd3"]]},{"id":"f6e76317.5bd3","type":"slack-ask","z":"bd84d76c.2d93f8","slack":"","channel":"general","msg":"{\n  \"text\": \"インスタンスを停止・起動していいですか?\",\n  \"attachments\": [\n    {\n      \"title\": \"承認依頼\",\n      \"text\": \"実行してよろしいですか\",\n      \"fallback\": \"このクライアントでは返答できません\",\n      \"callback_id\": \"approve_123\",\n      \"color\": \"#3AA3E3\",\n      \"attachment_type\": \"default\",\n      \"actions\": [\n        {\n          \"name\": \"answer\",\n          \"text\": \"承認\",\n          \"value\": \"yes\",\n          \"type\": \"button\",\n          \"style\": \"primary\"\n        },\n        {\n          \"name\": \"answer\",\n          \"text\": \"否認\",\n          \"value\": \"no\",\n          \"type\": \"button\"\n        }\n      ]\n    }\n  ]\n}","timeout":"30","timeoutUnits":"minutes","name":"再起動実施確認","x":200,"y":300,"wires":[["225facba.2a5cf4"]]},{"id":"225facba.2a5cf4","type":"slack-say","z":"bd84d76c.2d93f8","slack":"","channel":"general","msg":"> {{{payload.original_message.text}}}\n{{{answer.value}}} by {{{payload.user.name}}}","output":"str","gonext":true,"name":"復唱","x":370,"y":300,"wires":[["75b36d.ac831c94"]]},{"id":"75b36d.ac831c94","type":"switch","z":"bd84d76c.2d93f8","name":"承認結果判定","property":"answer.value","propertyType":"msg","rules":[{"t":"eq","v":"no","vt":"str"},{"t":"eq","v":"yes","vt":"str"}],"checkall":"true","outputs":2,"x":540,"y":300,"wires":[["eb240803.79d998"],["351ce544.8e522a"]]},{"id":"3c6efba0.b994e4","type":"catch","z":"bd84d76c.2d93f8","name":"例外処理","scope":["b0addf6c.85c41","75b36d.ac831c94","dfb73593.bb2f08","cd9f1df.a3aede","67e11027.4cc2c","c274bafd.cddfe8","20849217.8d4fae","f6e76317.5bd3","10cc5de3.b72572","eb240803.79d998","351ce544.8e522a","225facba.2a5cf4","b4948d24.29e29"],"x":160,"y":460,"wires":[["a640fbbb.d2ee18","25f25187.2b4cbe","6fdbe105.9ed53"]]},{"id":"eb240803.79d998","type":"slack-say","z":"bd84d76c.2d93f8","slack":"","channel":"general","msg":"後の対応をお願いします。","output":"str","gonext":false,"name":"対応終了","x":800,"y":300,"wires":[]},{"id":"351ce544.8e522a","type":"slack-say","z":"bd84d76c.2d93f8","slack":"","channel":"general","msg":"対応を始めます","output":"str","gonext":true,"name":"対応開始宣言","x":200,"y":360,"wires":[["cd9f1df.a3aede"]]},{"id":"cd9f1df.a3aede","type":"command","z":"bd84d76c.2d93f8","connectionconfig":"fd7512e2.f22ec","command":"aws ec2 stop-instances --instance-id {{{instanceId}}}\naws ec2 wait instance-stopped --instance-id {{{instanceId}}}","timer":"60","oldrc":false,"env":"","pty":false,"trim":false,"field":"payload","fieldType":"msg","name":"インスタンス停止","x":400,"y":360,"wires":[["b4948d24.29e29","c274bafd.cddfe8"]]},{"id":"b4948d24.29e29","type":"slack-say","z":"bd84d76c.2d93f8","slack":"","channel":"general","msg":"インスタンスを停止しました。\nインスタンスを起動します。","output":"str","gonext":false,"name":"途中報告","x":600,"y":400,"wires":[]},{"id":"c274bafd.cddfe8","type":"command","z":"bd84d76c.2d93f8","connectionconfig":"fd7512e2.f22ec","command":" aws ec2 start-instances --instance-id {{{instanceId}}}\naws ec2 wait instance-running --instance-id {{{instanceId}}}","timer":"90","oldrc":false,"env":"","pty":false,"trim":false,"field":"payload","fieldType":"msg","name":"インスタンス起動","x":620,"y":360,"wires":[["10cc5de3.b72572"]]},{"id":"10cc5de3.b72572","type":"slack-say","z":"bd84d76c.2d93f8","slack":"","channel":"general","msg":"インスタンスを起動しました。\n対応が完了しました。","output":"str","gonext":false,"name":"対応完了報告","x":820,"y":360,"wires":[]},{"id":"a640fbbb.d2ee18","type":"slack-say","z":"bd84d76c.2d93f8","slack":"","channel":"general","msg":"異常が発生しました。対応をお願いします。\n{{{error.message}}}\n```\n{{{payload}}}\n```\n","output":"str","gonext":false,"name":"異常発生連絡","x":400,"y":460,"wires":[]},{"id":"25f25187.2b4cbe","type":"debug","z":"bd84d76c.2d93f8","name":"","active":true,"console":"false","complete":"true","x":370,"y":560,"wires":[]},{"id":"6fdbe105.9ed53","type":"file","z":"bd84d76c.2d93f8","name":"ログ追記","filename":"c:\\Temp\\aws-mentenance.log","appendNewline":true,"createDir":true,"overwriteFile":"false","x":377.1000061035156,"y":510.20001220703125,"wires":[]},{"id":"d20cc86f.3c0dd8","type":"comment","z":"bd84d76c.2d93f8","name":"AWSのメンテ通知の対応","info":"","x":140,"y":100,"wires":[]},{"id":"fd7512e2.f22ec","type":"connection","z":"","name":"util server","host":"35.185.201.106","username":"sakazuki"}]

最後に

FrontOps を使うと、複数のツール・ソフトを簡単につなげて制御することができます。
少人数でシステム運用・保守をされている方に是非活用いただきたいアプリです。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.