Open7

LambdaでEC2のステータス変化をSlack通知

antyuntyunantyuntyun

EC2のステータス(起動,停止)をSlack通知したくなったなり実装したのでその際のメモ.
EC2のステータス変化を取得するトリガーはCloudWatch Eventsと記憶していたが, 今はAmazon Event Bridgeというものの一部になっていた.

AmazonEvent Bridge

antyuntyunantyuntyun

lambdaでpythonランタイムで実装する際, 標準パッケージでないものをzip化してレイヤーとしてアップロードするのは調べればすぐ出てくるものだが, それでもなぜかimportできなくて少々ハマった.
原因としては, zip化したローカル環境ではpython3.7に対してlambdaのラインタイムがpython3.8でバージョン相違によるimportできないというところだった. 3系なら読み込めるだろうと言う謎の思い込みのせいでハマった.

antyuntyunantyuntyun
python -V
mkdir -p tmp && cd ./tmp
pip install requests boto3 -t .
chmod -R 755 ./*
zip -r upload.zip *
antyuntyunantyuntyun

lambda関数コード

下記記事を参考にさせて頂きながら実装.
https://techblog.forgevision.com/entry/2018/10/15/101011

antyuntyunantyuntyun

起動と停止の二つの変化をトリガーにしている.
インスタンス状態をマネコンでみるためのボタンも追加.

import json
import boto3
import requests

def post_slack_h(data):
    # SlackでIncoming Webhooksを有効にして取得したWebhook URLを記載
    post_url = 'https://hooks.slack.com/services/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'
    requests.post(post_url, data=json.dumps(data))
    
def lambda_handler(event, context):
    print('Event')
    print(json.dumps(event, indent=2))
    instance_id = event['detail']['instance-id']
    client = boto3.client('ec2')
    response = client.describe_instances(
        InstanceIds=[
            instance_id,
        ],
    )
    instance = response['Reservations'][0]['Instances'][0]
    fields = []
    # Name
    for tag in instance['Tags']:
        if tag['Key'] != 'Name':
            continue
        instance_name = tag['Value']
        fields.append({
            'title': 'Name',
            'value': instance_name,
            'short': True,
        })
        break
    # InstanceId
    fields.append({
        'title': 'インスタンス ID',
        'value': instance_id,
        'short': True,
    })
    # InstanceType
    fields.append({
        'title': 'インスタンスタイプ',
        'value': instance['InstanceType'],
        'short': True,
    })
    # AvailabilityZone
    fields.append({
        'title': 'アベイラビリティーゾーン',
        'value': instance['Placement']['AvailabilityZone'],
        'short': True,
    })
    # NetworkInterfaces
    for interfaces in instance['NetworkInterfaces']:
        idx = interfaces['Attachment']['DeviceIndex']
        for ip_addresses in interfaces['PrivateIpAddresses']:
            fields.append({
                'title': 'プライベート IP (eth{})'.format(idx),
                'value': ip_addresses.get('PrivateIpAddress'),
                'short': True,
            })
            fields.append({
                'title': 'パブリック IP (eth{})'.format(idx),
                'value': ip_addresses.get('Association', {}).get('PublicIp'),
                'short': True,
            })
    
    instance_state = event['detail']['state']
    stopped_or_runnnig_message = "起動" if instance_state == "running" else "停止"
    ec2_instances_url = "https://ap-northeast-1.console.aws.amazon.com/ec2/v2/home?region=ap-northeast-1#Instances:"
    ec2_instance_detail_url = "https://ap-northeast-1.console.aws.amazon.com/ec2/v2/home?region=ap-northeast-1#InstanceDetails:instanceId="+instance_id
    data = {
        'attachments': [{
            'pretext': '<!here> EC2インスタンスが *'+ stopped_or_runnnig_message +'* しました',
            'color': 'good',
            'fields': fields,
            "actions": [
                {
                  "type": "button",
                  "text": "Open Instance List",
                  "url": ec2_instances_url
                },
                {
                  "type": "button",
                  "text": "Open Instance Detail",
                  "url": ec2_instance_detail_url
                }
            ]
        }]
    }

    # Slack通知
    post_slack_h(data)