SlackチャンネルにEC2インスタンスが落ちたことを通知してくれるようにしてみた

#背景
時折、開発環境のEC2インスタンスが勝手に落ちていることがあり、気が付いた人からの問い合わせでその都度手動でEC2インスタンスを再起動していました。
開発者達が開発に集中できるようにするために自動で通知してくれるような仕組みを構築しました。
##実装一覧
- SlackチャンネルにEC2インスタンスがstoppedになったことを通知できるようにする
- stoppedになったら、自動で再起動できるようにする
- 上記の機能を9:00~21:00の間に限定できるようにする

9:00~21:00の間に制限を設けている理由ですが、開発環境ではコスト改善のために21時になったら、自動で開発環境の全てのEC2インスタンスがshutdownされ、翌朝9時になったら自動で起動できるようにしてあります。
なので、仮に上記の機能が21時以降も有効になってしまうと、幾つものEC2インスタンスのステータス通知がSlackチャンネルを埋めるだけでなく、深夜早朝帯も開発環境が起動され続けるという恐れがあります。
##使用ツール
- CloudWatch Event
- Lambda
- Incoming Webhook

##手順
①SlackからIncoming Webhookアプリをインストールする
何かしらのサービス情報をSlackのチャンネルに通知するためにはIncoming WebhookというSlackアプリを活用することで実現可能となります。[公式サイト](https://slack.com/intl/ja-jp/help/articles/115005265063-Slack-%E3%81%A7%E3%81%AE-Incoming-Webhook-%E3%81%AE%E5%88%A9%E7%94%A8)
インストール後、サービス情報を共有したいSlackチャンネルを選択し、WebhookURLを控えておきます。
<img width="500" alt="vGx3klsIw5EqkjY1591324726_1591325055.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/648568/645e6ff4-966a-a8a5-d435-8221a11a9f67.png">
②Lambda関数を作成する
新規でLambda関数を作成し、以下の関数を書きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/648568/bbc2aecd-687b-a4f8-8d8f-c6bb3a2ff34d.png)

```python:lambda_function.py
from __future__ import print_function
from time import strptime, strftime

import os, json, boto3, urllib.request

#GMT表記
before = strptime('00:00:00', '%H:%M:%S')
after = strptime('11:59:00', '%H:%M:%S')
now = strptime(strftime('%H:%M:%S'), '%H:%M:%S')

print(now)

#自動起動/シャットダウンバッチに引っかからないようにJST9:00-JST21:00の間で処理を動かすようにしている
if (now >= before and now <= after):

   #変数eventの中身がCloudWatch Eventから受け取ったEC2インスタンスのstop
   def lambda_handler(event,context):
      
      instances = [ event ]
      region = 'ap-northeast-1'
      ec2 = boto3.client('ec2', region_name=region)
      
      #色付きメッセージ発行
      attachments = {
        'attachments': [{
          # titleのリンクをクリックするとtitle_linkで設定したページへ飛びます。
          'title': 'EC2が停止しました。',
          'title_link': 'https://api.slack.com/docs/message-attachments',
          'color': "warning",
        }]
      }
      
      req = json.dumps(attachments).encode('ascii')
      
      message_color = urllib.request.Request(os.environ['slackUrl'], req )
      
      try:
         response = urllib.request.urlopen(message_color)
         response.read()

      except Exception as e:
         print(e)
   
      #インスタンスの起動停止メッセージ
      message_stop = {
         'text': "<!here> EC2 Instance " + (event) + " stopped"
      }

      data = json.dumps(message_stop).encode('ascii')
   
      req = urllib.request.Request(os.environ['slackUrl'], data )
      try:
          response = urllib.request.urlopen(req)
          response.read()
       
          print("Message posted: %s" % message_stop )
      except Exception as e:
          print(e)

      #停止したインスタンスの再起動
      ec2.start_instances(InstanceIds=instances)
      message_restart = {
         'text': "started your instances: " + str(instances)
      }
      
      data2 = json.dumps(message_restart).encode('ascii')
      
      req = urllib.request.Request(os.environ['slackUrl'], data2 )
      try:
          response = urllib.request.urlopen(req)
          response.read()
       
          print("Message posted: %s" % message_restart )
      except Exception as e:
          print(e)
          
   #自動起動/シャットダウンが走る9時前、21時以降は処理をさせない
else:
   print("out time of function")

```
関数内で未定義の変数slackUrlはLambda側から取得したWebhookURLをセットしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/648568/6453878c-a5da-f814-c489-430aa192144d.png)
③CloudWatch EventでLambda関数が発動するルールを作成する
EC2インスタンスがstoppedになったら先ほど作成したLambda関数を起動できるようにルールを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/648568/d83eeda7-716f-6b04-31fe-fdc364fc47da.png)
この時、Lambda関数へ渡す入力内容を絞ることでSlackチャンネルに表示される内容を見やすいものに加工することができます。
※全表示の場合(一部モザイク処理)
<img width="314" alt="ec2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/648568/11e6ed14-61ba-f541-1286-7eafea82109c.png">
以上で、Slackチャンネルへ通知する手順は完了しました。最後にEC2インスタンスを停止させて、Slackチャンネルへ通知が届き、再起動ができるか確認します。
<img width="179" alt="ec3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/648568/379c94e2-7628-b4b9-991f-b8d3461d2cbb.png">
ちゃんと実装したかったことができるようになりました。
##所感
AWSとSlackの連携方法として、CloudWatch AlarmからLambdaを飛ばしてSlackチャンネルへアラーム内容を連携する方法が一般的で、検索すると多くの記事が出てきてLambda関数にもそれ用のテンプレートがあります。
また、最近AWSとSlackが、提携を発表しそれぞれのサービスの結びつきが今後ますます強くなっていくと思われます。[参考リンク](https://www.itmedia.co.jp/news/articles/2006/05/news065.html)
最近ですと、AWS Chatbotで簡単にAWSサービスの内容をSlackへ連携しやすくなりましたが、まだ提供できるサービスが少ないためCloudWatch Eventでも対応できるようになりましたら検証してみたいと思います。