Auto Scalingイベントの発生をLambdaでChatWorkに通知する

こんにちは、虎塚です。

Auto Scalingを使ってシステムを運用していると、インスタンスが増えたり減ったりした時に、チャットに通知したい場合があります。

今回は、Auto Scalingのイベントが発生したら、S3上にあらかじめ用意しておいた設定を読み込んで、ChatWorkの特定の部屋に通知を送るLambdaスクリプトを紹介します。

Auto ScalingイベントをChatWorkにLambdaで通知する

この仕組みの使い方

この仕組みの入力と出力を説明します。

この仕組みを利用するにあたって、あらかじめS3バケットに次のような設定情報を保存しておきます。

具体的には、次のようなJSONファイルを用意します。

{
  "actions": [
    {
      "autoScalingGroupName": "test-asg",
      "notificationType": "autoscaling:EC2_INSTANCE_TERMINATE",
      "actionDetail": {
        "roomId": "12345678",
        "message": "an instance was terminated in an Auto Scaling group."
      }
    },
    {
      "autoScalingGroupName": "test-asg",
      "notificationType": "autoscaling:EC2_INSTANCE_LAUNCH",
      "actionDetail": {
        "roomId": "12345678",
        "message": "an instance was launched in an Auto Scaling group."
      }
    }
  ]
}

上のファイルには、Auto Scaling group (test-asg) でEC2のterminateまたはlaunchイベントが起きたら、ChatWorkのルーム (12345678) にメッセージを投稿するための設定を記述しています。

設定ファイルを保存するS3バケットは、test170313-bucket、設定ファイルのオブジェクトキーは、{AWS_ACCOUNT_ID}/sns.jsonとします。(S3バケット名は、全アカウントで一意でなければならないため、取得できる名前に読み替えてください)

実際にAuto Scalingのイベントが起きると、ChatWorkに次のようなメッセージが投稿されます。

ChatWorkへの通知例

環境構築と動作確認の手順を、以降で説明します。

仕組みの構築手順

1. S3バケットの作成

設定ファイルを保存するS3バケット (test170313-bucket) を作成します。

aws s3api create-bucket \
  --bucket test170313-bucket \
  --create-bucket-configuration LocationConstraint=ap-northeast-1

2. Lambda関数の作成

2.1. Pythonスクリプトの作成

ローカルで次のPythonスクリプトを作成します。S3バケット名とChatWorkのAPIキーは、環境に合わせて読み替えてください。

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
from __future__ import print_function

import boto3
import json
import logging
import requests

S3_BUCKET = 'test170313-bucket'
CHAT_APIKEY = '12345678901234567890123456789012'
CHAT_ENDPOINT = 'https://api.chatwork.com/v2'

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def create_message(data):
    message = '\nAccount: {}, AutoScaling group: {}, instance: {}'
    return message.format(
        data['AccountId'],
        data['AutoScalingGroupName'],
        data['EC2InstanceId'])

def get_setting(account):
    key = account + '/sns.json'
    obj = boto3.resource('s3').Object(S3_BUCKET, key)
    resp = obj.get()
    body = resp['Body'].read()
    return json.loads(body.decode('utf-8'))

def select_action(setting, data):
    for action in setting['actions']:
        if action['notificationType'] == data['Event'] and action['autoScalingGroupName'] == data['AutoScalingGroupName']:
            return action['actionDetail']

def post_chat(action, data):
    body = '[info]' + action['message'] + create_message(data) + '[/info]'
    post_message_url = '{}/rooms/{}/messages'.format(CHAT_ENDPOINT, action['roomId'])
    headers = { 'X-ChatWorkToken': CHAT_APIKEY }
    params = { 'body': body }
    resp = requests.post(post_message_url, headers=headers, params=params)
    logger.info(resp.content)

def lambda_handler(event, context):
    for record in event['Records']:
        data = json.loads(record['Sns']['Message'])
        logger.info(data)

        setting = get_setting(data['AccountId'])
        action = select_action(setting, data)
        post_chat(action, data)

2.2. デプロイパッケージの作成

デプロイパッケージ (post_chat.zip) を作成します。作業用にAmazonLinuxインスタンスを1つ起動し、ステップ2.1で作成したpost_chat.pyをscpで$HOMEにコピーします。

scp -i ~/.ssh/key.pem ./post_chat.py ec2-user@XXX.XXX.XXX.XXX:

インスタンスにSSHログインして、virtualenvで作業します。

cd /tmp/work/
. venv/bin/activate

スクリプトに必要なライブラリをインストールします。

pip install boto3
pip install requests

関連ファイルをzipにパッケージします。

cd $VIRTUAL_ENV/lib/python2.7/site-packages
zip -r9 ~/post_chat.zip *
cd ~
zip -g post_chat.zip post_chat.py

インスタンスからログアウトし、作成したzipファイルをローカルにコピーします。

scp -i ~/.ssh/key.pem ec2-user@XXX.XXX.XXX.XXX:post_chat.zip .

2.3. Lambda実行ロールの作成

まず、Lambdaの実行ロール (chat-notify-role) を作成します。ローカルにテキストファイルで次の信頼ポリシーを作成します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

上の信頼ポリシーを指定して、IAMロール (chat-notify-role) を作成します。

aws iam create-role \
  --role-name chat-notify-role \
  --assume-role-policy-document file://AllowLambdaAssumeRole.json

次に、Lambdaの実行ロールにアクセスポリシーをつけます。

# Lambdaの実行ログの出力を許可します。一般的なLambdaの実行ロールの権限です。
aws iam attach-role-policy \
  --role-name chat-notify-role \
  --policy-arn arn:aws:iam::aws:policy/AWSLambdaExecute

# S3の読み取りを許可します。
aws iam attach-role-policy \
  --role-name chat-notify-role \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess

2.4. デプロイパッケージのアップロード

ステップ2.2のデプロイパッケージとステップ2.3のIAMロールを指定して、Lambda関数 (post_chat) を作成します。

aws lambda create-function \
  --function-name post_chat \
  --runtime python2.7 \
  --role arn:aws:iam::123456789012:role/chat-notify-role \
  --handler post_chat.lambda_handler \
  --zip-file fileb://post_chat.zip

動作確認

構築した仕組みを実際に使ってみましょう。

1. テスト用のAuto Scaling環境構築

Launch Configuration (test-lc) とAuto Scaling group (test-asg) を作成します。

aws autoscaling create-launch-configuration \
  --launch-configuration-name test-lc \
  --image-id ami-56d4ad31 \ # latest AmazonLinux AMI
  --key-name KEY_PAIR_NAME \
  --security-groups default \
  --instance-type t2.micro \
  --iam-instance-profile INSTANCE_ROLE_NAME

aws autoscaling create-auto-scaling-group \
  --auto-scaling-group-name test-asg \
  --availability-zones ap-northeast-1c \
  --launch-configuration-name test-lc \
  --min-size 1 \
  --max-size 1

ここでは、現時点で最新のAmazonLinuxインスタンスが1つだけ起動するAuto Scaling groupを作成しました。

Auto Scaling groupのイベント通知を配信するためのSNSトピック (test-topic) を作成します。

aws sns create-topic --name test-topic

実行結果の戻り値のTopicArn (仮にarn:aws:sns:ap-northeast-1:123456789012:test-topic) を後で使うため控えておきます。

Auto Scaling group (test-asg) 配下で、インスタンスが起動または終了した場合に、SNSトピック (test-topic) に通知を配信するように設定します。

aws autoscaling put-notification-configuration \
  --auto-scaling-group-name test-asg \
  --topic-arn arn:aws:sns:ap-northeast-1:123456789012:test-topic \
  --notification-types "autoscaling:EC2_INSTANCE_TERMINATE" "autoscaling:EC2_INSTANCE_LAUNCH"

2. SNSトピックとLambda関数の連携

ステップ1のSNSトピックを、仕組み構築で作成したLambda関数 (post_chat) にサブスクライブさせます。

事前にLambda関数 (post_chat) のARNを確認します。

aws lambda get-function \
  --function-name post_chat \
  | jq '.Configuration.FunctionArn'

"arn:aws:lambda:ap-northeast-1:123456789012:function:post_chat"

SNSトピック (test-topic) をLambda関数 (post_chat) でサブスクライブします。

aws sns subscribe \
  --topic-arn arn:aws:sns:ap-northeast-1:123456789012:test-topic \
  --protocol lambda \
  --notification-endpoint arn:aws:lambda:ap-northeast-1:123456789012:function:post_chat

Lambda関数 (post_chat) に権限を追加して、SNS通知を受け取れるようにします。

aws lambda add-permission \
    --function-name post_chat \
    --statement-id AllowSnsSubscribe \
    --action "lambda:InvokeFunction" \
    --principal sns.amazonaws.com \
    --source-arn arn:aws:sns:ap-northeast-1:123456789012:test-topic

3. 設定ファイルの作成とS3への設置

前半に載せた設定ファイルを再掲します。

{
  "actions": [
    {
      "autoScalingGroupName": "test-asg",
      "notificationType": "autoscaling:EC2_INSTANCE_TERMINATE",
      "actionDetail": {
        "roomId": "12345678",
        "message": "an instance was terminated in an Auto Scaling group."
      }
    },
    {
      "autoScalingGroupName": "test-asg",
      "notificationType": "autoscaling:EC2_INSTANCE_LAUNCH",
      "actionDetail": {
        "roomId": "12345678",
        "message": "an instance was launched in an Auto Scaling group."
      }
    }
  ]
}

上の設定ファイルをS3バケット (test170313-bucket) に保存します。

aws s3api put-object \
  --body sns.json \
  --bucket test170313-bucket \
  --key 123456789012/sns.json

キーの一部の123456789012は、AWSアカウントIDです。

4. 通知の確認

Auto Scaling group配下のインスタンスを手動で削除して、ChatWorkに通知がくることを確認しましょう。

Auto Scaling group (test-asg) 内で起動しているインスタンスのIDを確認します。出力内容は一例です。

aws autoscaling describe-auto-scaling-groups \
  --auto-scaling-group-names test-asg \
  | jq '.AutoScalingGroups[] \
  | .AutoScalingGroupName, .Instances[].InstanceId'

"test-asg"
"i-0a9e3c4b44d996d56"

仮に上のような値が取れた場合、次のようなコマンドでインスタンスをterminateします。

aws ec2 terminate-instances --instance-ids i-0a9e3c4b44d996d56

インスタンスが削除されると、Auto Scaling groupはヘルスチェックの失敗を検知して、新しいインスタンスを起動します。数分待つと、チャットにインスタンスの削除と起動の通知が送られてきます。通知例を再掲します。

通知例

発展

次のクロスアカウント設定と組み合わせると、Auto Scaling groupが他のAWSアカウントにある場合も、SNS通知をLambda関数で受け取ることができます。

また、今回は設定ファイルをS3に直接置きましたが、API Gateway経由で設定をPOSTできるように拡張すれば、さらに便利に使えるかもしれません。

おわりに

運用自動化の一例として、Auto Scalingイベントの発生をChatWorkに通知するLambdaスクリプトをご紹介しました。

それでは、また。