【大阪 / 横浜 / 徳島】インフラ / サーバーサイドエンジニア募集中!

【大阪 / 横浜 / 徳島】インフラ / サーバーサイドエンジニア募集中!

【導入実績 500社以上】AWS 構築・運用保守・監視サービス

【導入実績 500社以上】AWS 構築・運用保守・監視サービス

【CentOS 後継】AlmaLinux OS サーバー構築・移行サービス

【CentOS 後継】AlmaLinux OS サーバー構築・移行サービス

【WordPress 専用】クラウドサーバー『ウェブスピード』

【WordPress 専用】クラウドサーバー『ウェブスピード』

【格安】Webサイト セキュリティ自動診断「クイックスキャナー」

【格安】Webサイト セキュリティ自動診断「クイックスキャナー」

【予約システム開発】EDISONE カスタマイズ開発サービス

【予約システム開発】EDISONE カスタマイズ開発サービス

【100URLの登録が0円】Webサイト監視サービス『Appmill』

【100URLの登録が0円】Webサイト監視サービス『Appmill』

【200ヶ国以上に対応】グローバル eSIM「ビヨンドSIM」

【200ヶ国以上に対応】グローバル eSIM「ビヨンドSIM」

【中国への旅行・出張・駐在なら】中国SIMサービス「チョコSIM」

【中国への旅行・出張・駐在なら】中国SIMサービス「チョコSIM」

【グローバル専用サービス】北米・中国でも、ビヨンドのMSP

【グローバル専用サービス】北米・中国でも、ビヨンドのMSP

【YouTube】ビヨンド公式チャンネル「びよまるチャンネル」

【YouTube】ビヨンド公式チャンネル「びよまるチャンネル」

定期的にEC2インスタンスを削除するLambda関数をデプロイした話

インフラエンジニアの寺岡です。
今回は社内向けにLambda関数をデプロイした話をまとめてみたいと思います。

弊社では運用でお預かりしているお客様のサーバが稼働しているAWSアカウント以外にも
社内のエンジニアのメンバーが技術的な検証をするためのアカウントも管理しています。

このアカウント、ログイン周りのセキュリティ設定(IAMユーザー・ロール/MFA認証)はルールを定めて設定していますが
アカウント内での操作は権限を持っているメンバーであれば特に制約なく自由に扱えるようにしています。
検証用なのにあれこれルールが多すぎると扱いにくくなってしまいますからね。

検証の過程で最も多く作成されるリソースは弊社の場合はEC2インスタンスですが
自由に作成できる反面、停止忘れや削除忘れが割と多発している状況でした。

当然多発するとその分無駄なコストがかかってしまいます。
使い終わったら必ず停止か削除するという前提はありますが
忘れてしまった場合の予防線としてLambdaによって定期的にEC2インスタンスを自動削除する仕組みを導入しました。

ということで何をやったのかを下記にまとめます。

Lambda関数の作成

Pythonで書きました。
個人的にGolangの方が詳しいのでそっちで書けばよかった感。

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] 退役除外されたインスタンスがあるよ(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)

コードを見ていただければわかるかなと思いますが

  • 除外タグが付いていないインスタンスを削除する
  • 削除対象から除外されたインスタンスをチャットワークに通知する

という処理をやってくれます。
問答無用で消されてしまうと困る場合があるので
その場合は除外タグ「EXCLUDE_TERMINATE:true」を付与してね
というルールで社内周知しています。

Serverless Frameworkの設定

書いたコードをどのようにLambdaにデプロイするのかというお話。

Serverless Frameworkというツールを利用しています。
https://serverless.com

これはLambdaなどのサーバレス系のサービスに対して
所定の設定ファイルに従って関数をデプロイしてくれるCLIツールになっていて
Lambdaの場合はCloudFormationと連携してデプロイを実行してくれます。

設定ファイルはserverless.ymlという名前で用意します。

# 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

environmentで環境変数として除外タグとChatWorkの認証情報の設定をしています。
functionsの部分で文字通り関数の設定をしています。
eventsの部分でスケジュールの設定をしているのですが
この設定だとCloudWatchEventsと連携して毎日15:00(UTC)に定期実行してくれます。
JSTだと深夜0:00になりますね。

この設定ファイルがあるディレクトリにデプロイするコードも一緒に保存して

$ sis deploy --profile [AWS Profile Name]

とすればコマンドラインからServerless Frameworkが自動作成した
CloudFormationのスタックを実行してデプロイすることができます。

最後に

ちゃっかりブログのネタにしていますが
社内で一番消し忘れが多いのは実は僕ですごめんなさい。。。

この記事がお役に立てば【 いいね 】のご協力をお願いいたします!
1
読み込み中...
1 票, 平均: 1.00 / 11
2,353
X facebook はてなブックマーク pocket
【2025.6.30 Amazon Linux 2 サポート終了】Amazon Linux サーバー移行ソリューション

【2025.6.30 Amazon Linux 2 サポート終了】Amazon Linux サーバー移行ソリューション

この記事をかいた人

About the author

寺岡佑樹

2016年ビヨンド入社、現在6年目のインフラエンジニア
MSPの中の人として障害対応時のトラブルシューティングを行いながら
AWSなどのパブリッククラウドを用いたインフラの設計/構築も行っている。
最近はDockerやKubernetesなどのコンテナ基盤の構築や
運用自動化の一環としてTerraformやPackerなどのHashicorpツールを扱うことが多く
外部の勉強会やセミナーで登壇するEvangelistの役割も担っている。

・GitHub
https://github.com/nezumisannn

・登壇経歴
https://github.com/nezumisannn/my-profile

・発表資料(SpeakerDeck)
https://speakerdeck.com/nezumisannn

・所有資格
AWS Certified Solutions Architect - Associate
Google Cloud Professional Cloud Architect