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

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

【サーバー管理不要】WordPress専用クラウド『WebSpeed』

【サーバー管理不要】WordPress専用クラウド『WebSpeed』

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

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

【コミュニケーションアプリ開発】LINE アプリ開発サービス

【コミュニケーションアプリ開発】LINE アプリ開発サービス

【ECサイト構築】Shopify カスタムアプリ開発サービス

【ECサイト構築】Shopify カスタムアプリ開発サービス

【音声アプリ開発】Twilio アプリ開発サービス

【音声アプリ開発】Twilio アプリ開発サービス

【グローバル対応】北米リージョン・クラウド / サーバー サポート

【グローバル対応】北米リージョン・クラウド / サーバー サポート

【CPU】AMD EPYC 技術検証(PoC)サービス

【CPU】AMD EPYC 技術検証(PoC)サービス

【Webシステム / サービス開発】SAKARAKU Lab(セカラクラボ)

【Webシステム / サービス開発】SAKARAKU Lab(セカラクラボ)

【取材記事】サーバー系企業ビヨンドが サーバーサイドエンジニアを募集中

【取材記事】サーバー系企業ビヨンドが サーバーサイドエンジニアを募集中

【対談記事】「やっぱクラウド移設っていいですよね」マイネット × ビヨンド エンジニア対談

【対談記事】「やっぱクラウド移設っていいですよね」マイネット × ビヨンド エンジニア対談

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

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

【15周年記念 特設サイト】ビヨンドは「2022.4.4」で15周年を迎えました!

【15周年記念 特設サイト】ビヨンドは「2022.4.4」で15周年を迎えました!

定期的に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のスタックを実行してデプロイすることができます。

最後に

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

この記事がお役に立てば【 いいね 】のご協力をお願いいたします!
0
読み込み中...
0 票, 平均: 0.00 / 10
1,210
facebook twitter はてなブックマーク

この記事をかいた人

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