top of page

Posts

How to Deploy a Lambda Function to Delete EC2 Instance Periodically



When you have many EC2 instances, you may forget to stop instances that you are not using.

If you keep running the instances that aren't necessary, it keeps adding to your costs of running the instances.

To avoid this, you can use Lambda to delete EC2 instances automatically and periodically.


Create Lambda functions

I wrote in Python.


import json
import boto3
import os
import requests
 
def terminate(event, context):
     
    EXCLUDE_TERMINATE_TAG = os.environ['EXCLUDE_TERMINATE_TAG']
    CHATWORK_API_KEY = os.environ['CHATWORK_API_KEY']
    CHATWORK_ROOM_ID = os.environ['CHATWORK_ROOM_ID']
    CHATWORK_ENDPOINT = os.environ['CHATWORK_ENDPOINT']
 
    ec2 = boto3.client('ec2')
 
    without_tag = ec2.describe_instances()
    with_tag = ec2.describe_instances(
        Filters=[{ 'Name': 'tag:' + EXCLUDE_TERMINATE_TAG, 'Values': ['true'] }]
    )
     
    without_tag_set = set(ec2['InstanceId'] for resId in without_tag['Reservations'] for ec2 in resId['Instances'])
    with_tag_set = set(ec2['InstanceId'] for resId in with_tag['Reservations'] for ec2 in resId['Instances'])
  
    target_instances = without_tag_set - with_tag_set
    list_target_instances = list(target_instances)
     
    if len(list_target_instances) != 0:
        terminateInstances = ec2.terminate_instances(
            InstanceIds=list_target_instances
        )
     
    notify_instances = ''
     
    if len(with_tag['Reservations']) != 0:
        for reservation in with_tag['Reservations']:
            for instance in reservation['Instances']:
                if len(instance['Tags']) != 0:
                    instance_name = ''
                    for tag in instance['Tags']:
                        if tag['Key'] == 'Name':
                            instance_name = tag['Value']
                 
                instance_state_enc = json.dumps(instance['State'])
                instance_state_dec = json.loads(instance_state_enc)
                 
                if instance_name != '':
                    notify_instances += instance['InstanceId'] + ' -> ' + instance_name + '(' + instance_state_dec['Name'] + ')'+ '\n'
                else:
                    notify_instances += instance['InstanceId'] + ' -> ' + 'None' + '(' + instance_state_dec['Name'] + ')'+ '\n'
     
        message = '[To:350118][To:1786285][info][title][Beyond POC] There are instance that have been removed (devil)[/title]' + notify_instances + '[/info]'
        PostChatwork(CHATWORK_ENDPOINT,CHATWORK_API_KEY,CHATWORK_ROOM_ID,message)
 
    response = {
        "TerminateInstances": list_target_instances
    }
     
    print (response)
    return response
 
def PostChatwork(ENDPOINT,API_KEY,ROOM_ID,MESSAGE):
    post_message_url = '{}/rooms/{}/messages'.format(ENDPOINT, ROOM_ID)
    headers = { 'X-ChatWorkToken': API_KEY }
    params = { 'body': MESSAGE }
     
    resp = requests.post(post_message_url,
                     headers=headers,
                     params=params)

You might have an idea if you read the code, but it says

  • Delete the instances that have no exclude tag.

  • Notify in Chatwork, a business chat tool we use, of the instances that have been excluded from the deletion target.


Sometimes there are some instances that I still want to keep, in that case, I add a “EXCLUDE_TERMINATE:true” tag.


Serverless Framework setting

How to deploy this code to Lambda.

I used a tool that’s called Serverless Framework.


This is a CLI tool that deploys functions according to the configuration file. In the case of Lambda, it works with CloudFront to deploy.


I named serverless.yml for the configuration file.


# Welcome to Serverless!
#
# This file is the main config file for your service.
# It's very minimal at this point and uses default values.
# You can always add more config options for more control.
# We've included some commented out config examples here.
# Just uncomment any of them to get that config option.
#
# For full config options, check the docs:
#    docs.serverless.com
#
# Happy Coding!
 
service: beyond-poc
 
plugins:
  - serverless-prune-plugin
 
provider:
  name: aws
  runtime: python3.8
  profile: ${opt:profile, ''}
  region: ap-northeast-1
  role: [IAM Role Name]
  environment:
    EXCLUDE_TERMINATE_TAG: EXCLUDE_TERMINATE
    CHATWORK_API_KEY: [ChatWark API KEY]
    CHATWORK_ROOM_ID: [ChatWark ROOM ID]
    CHATWORK_ENDPOINT: https://api.chatwork.com/v2
    timeout: 10
 
# my custom env
custom:
  prune:
    automatic: true 
    number: 5
 
# you can overwrite defaults here
#  stage: dev
#  region: us-east-1
 
# you can add packaging information here
#package:
#  include:
#    - include-me.py
#    - include-me-dir/**
#  exclude:
#    - exclude-me.py
#    - exclude-me-dir/**
 
functions:
  terminate-instance:
    handler: handler.terminate
    events:
      - schedule: cron(0 15 ? * * *)
    timeout: 120
    memorySize: 128
    reservedConcurrency: 1

The environment section sets confirmation information of the Chatwork and exclusion tag as an environment variable.

The event section sets a schedule, but with this setting, it works with CloudWatchEvents and executes at 15:00(UTC) every day.


Save the code to deploy to the directory where this configuration file is located.


$ sis deploy --profile [AWS Profile Name]

Now you can deploy the CloudFormation stack that has been automatically created by Serverless Framework from the command line.



This blog post is translated from a blog post written by Yuki Teraoka on our Japanese website Beyond Co..

bottom of page