6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

EC2、RDS、ElastiCache、Elasticsearch のリザーブドインスタンスの有効期限をまとめてチェックしてSlackへ通知する Lambda (Python3.8版)

Last updated at Posted at 2019-04-25

概要

設定

Lambda に付けるロールを作成する

ロールにアタッチするポリシー

  • AWSLambdaBasicExecutionRole

  • AWS 管理ポリシー

  • DescribeReservedInstances

  • 下記の内容で作成する

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "DescribeReservedInstances",
            "Effect": "Allow",
            "Action": [
                "es:DescribeReservedElasticsearchInstances",
                "rds:DescribeReservedDBInstances",
                "elasticache:DescribeReservedCacheNodes",
                "ec2:DescribeReservedInstances"
            ],
            "Resource": "*"
        }
    ]
}

ロールを作成

  • reserved_instances_check
  • このロールを使用するサービス: Lambda
  • 上記2つのポリシーをアタッチする

Lambda 関数を作成する

  • 関数名

    • reserved_instances_check
  • ランタイム

    • Python 3.7
  • ロール

    • 上で作成した reserved_instances_check ロール
  • コード

import os
import boto3
import json
import logging
from pprint import pprint
from datetime import datetime, timedelta, timezone
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError

THRESHOLD10 = int(os.environ['THRESHOLD10'])
THRESHOLD1 = int(os.environ['THRESHOLD1'])

SLACK_WEBHOOK_URL = os.environ['SLACK_WEBHOOK_URL']
AWS_ACCOUNT_ALIAS = os.environ['AWS_ACCOUNT_ALIAS']

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

JST = timezone(timedelta(hours=+9), 'JST')
now = datetime.now(JST).date()

def lambda_handler(event, context):
    logger.info("Event: " + str(event))

    ec2_reserved_instance_expiration_date_check()
    rds_reserved_instance_expiration_date_check()
    elasticache_reserved_instance_expiration_date_check()
    es_reserved_instance_expiration_date_check()

def validcheck(RI, RIID, InstanceType, InstanceCount, End, ValidPeriod):
    if THRESHOLD10 == ValidPeriod:
        slack(RI, RIID, InstanceType, str(InstanceCount), End, str(ValidPeriod))
    elif THRESHOLD1 == ValidPeriod:
        slack(RI, RIID, InstanceType, str(InstanceCount), End, str(ValidPeriod))

def slack(RI, RIID, InstanceType, InstanceCount, End, ValidPeriod):
    color = "warning"
    icon = ":bulb:"
    SlackText=RI + " Reserved Instance Check"
    SlackTextAttachments = "AWS: " + AWS_ACCOUNT_ALIAS + "\n" + "RI ID: " + RIID + "\n" + "Instance Type: " + InstanceType + "\n" + "Instance Count: " + InstanceCount + "\n" + "End: " + End + " (only " + ValidPeriod + " days left)"
    slack_message = {
        'username': "reserved-instance-expiration-data-checker",
        'text': SlackText,
        'icon_emoji': icon,
        'attachments': [
            {
                "color": color,
                "text": SlackTextAttachments
            }
        ]
    }
    req = Request(SLACK_WEBHOOK_URL, json.dumps(slack_message).encode('utf-8'))
    try:
        response = urlopen(req)
        response.read()
        logger.info("Message posted to %s", SLACK_WEBHOOK_URL)
    except HTTPError as e:
        logger.error("Request failed: %d %s", e.code, e.reason)
    except URLError as e:
        logger.error("Server connection failed: %s", e.reason)

def ec2_reserved_instance_expiration_date_check():
    RI="EC2"
    client = boto3.client('ec2')
    responce = client.describe_reserved_instances()
    for ReservedInstance in responce['ReservedInstances']:
        if ReservedInstance['State'] == "active":
            ValidPeriod = (((ReservedInstance['End'].astimezone(JST).date()) - now).days)
            END = str((ReservedInstance['End']).astimezone(JST).date())
            validcheck(RI, ReservedInstance['ReservedInstancesId'], ReservedInstance['InstanceType'], str(ReservedInstance['InstanceCount']), str(END), ValidPeriod)

def rds_reserved_instance_expiration_date_check():
    RI="RDS"
    client = boto3.client('rds')
    response = client.describe_reserved_db_instances()
    for ReservedDBInstance in response['ReservedDBInstances']:
        if ReservedDBInstance['State'] == "active":
            END = ((ReservedDBInstance['StartTime'].astimezone(JST) + timedelta(seconds=ReservedDBInstance['Duration'])).astimezone(JST).date())
            ValidPeriod = (END - now).days
            validcheck(RI, ReservedDBInstance['ReservedDBInstanceId'], ReservedDBInstance['DBInstanceClass'], str(ReservedDBInstance['DBInstanceCount']), str(END), ValidPeriod)

def elasticache_reserved_instance_expiration_date_check():
    RI="ElastiCache"
    client = boto3.client('elasticache')
    response = client.describe_reserved_cache_nodes()
    for ReservedCacheNode in response['ReservedCacheNodes']:
        if ReservedCacheNode['State'] == "active":
            END = ((ReservedCacheNode['StartTime'].astimezone(JST) + timedelta(seconds=ReservedCacheNode['Duration'])).astimezone(JST).date())
            ValidPeriod = (END - now).days
            validcheck(RI, ReservedCacheNode['ReservedCacheNodeId'], ReservedCacheNode['CacheNodeType'], "", str(END), ValidPeriod)

def es_reserved_instance_expiration_date_check():
    RI="Elasticsearch"
    client = boto3.client('es')
    response = client.describe_reserved_elasticsearch_instances()
    for ReservedElasticsearchInstance in response['ReservedElasticsearchInstances']:
        if ReservedElasticsearchInstance['State'] == "active":
            END = ((ReservedElasticsearchInstance['StartTime'].astimezone(JST) + timedelta(seconds=ReservedElasticsearchInstance['Duration'])).astimezone(JST).date())
            ValidPeriod = (END - now).days
            validcheck(RI, ReservedElasticsearchInstance['ReservedElasticsearchInstanceId'], ReservedElasticsearchInstance['ElasticsearchInstanceType'], "", str(END), ValidPeriod)             

  • 環境変数

    • キー:AWS_ACCOUNT_ALIAS
      値:AWSアカウント名
    • キー:SLACK_WEBHOOK_URL
      値:SlackのWebhook
    • キー:THRESHOLD10
      値:10
    • キー:THRESHOLD1
      値:1
  • タイムアウト

    • 1分

CloudWatch イベント ルール

  • スケジュール(注意:設定はGMT)

    • Cron式:0 0 * * ? *
  • ターゲット

    • Lambda関数:reserved_instances_check
  • ルール名

    • reserved_instances_check

Slack通知例

スクリーンショット 2019-04-25 19.34.36.png

更新時の注意点(AWSから届いたメールの内容)

現在お使いのリザーブドインスタンスを引き続きご利用いただく場合

  • 現在お使いのリザーブドインスタンスと同じスペックで、来月以降もご使用する場合は、 同じリザーブドインスタンスを新規でご購入いただく形となります。
  • 一度ご購入いただいたリザーブドインスタンスはキャンセルできませんのでご注意ください。
  • 新規購入分も購入日より1年または3年の有効期限となります。
  • 現在の有効期限が切れる直前に新規購入いただくことでリザーブドインスタンスの重複期間を最小限に抑えていただけます。
  • 軽度・中度のリザーブドインスタンスをお使いの方は、現在提供しているタイプの中に同じ課金体系のものはありませんので、新規購入はできません。ご注意ください。
6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?