3
4

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 3 years have passed since last update.

【AWS】EC2を起動&停止&性能変更するdiscord bot

Last updated at Posted at 2021-07-28

はじめに

EC2は起動している時間単位で課金されるため、使う・使わないに応じてコマメに起動・停止するのが理想。
AWSコンソールで操作するのは手軽さに欠けるため、誰でも自由に操作できる環境を整備したい。
本ページではdiscordのメンバーが自由にEC2を起動・停止できるようにする他、
インスタンスタイプ(サーバー性能)を変更できるようにするbotを設定する。

サンプルコード↓

構成図

Herokuで動いているdiscord botから、APIGWを介してLambdaをキックして、EC2を起動・停止する。
Untitled presentation.png

やること

  • EC2を起動・停止するLambdaを設置
  • EC2起動時に、インスタンスタイプを変更する処理を追加
  • discordのメンバーが送信した特定のemojiを検知してLambdaを起動するbotを設置

前提

  • 初期設定済のEC2
  • discord botを実行する環境(本ページではHeroku)

もくじ

  1. Lambdaを設定する
  2. discord botを設定する
  3. 動作を確認する

1. Lambdaを設定する

1-1. APIGWとLambdaの設定方法

下記ページを参照。

1-2. Lambdaの中身(ec2_up)

EC2のインスタンスタイプを上書きした後、起動する。
INSTANCE_IDALLOWED_INSTANCE_TYPESは各自の環境に合わせて書き換える必要がある。

index.py
import boto3

# YOUR OWN INSTANCE ID
INSTANCE_ID = 'i-00000000000000000'
ALLOWED_INSTANCE_TYPES = ['t3a.large', 'm5n.large']


def lambda_handler(event, context):
    new_instance_type = event['instance_type']
    if not new_instance_type in ALLOWED_INSTANCE_TYPES:
        return response_maker(400, "[ERROR!] invalid instance_type")

    ec2 = boto3.client("ec2")

    # only running instance will return
    description = ec2.describe_instance_status(
        InstanceIds=[INSTANCE_ID],
        IncludeAllInstances=True
    )
    instance_state = description['InstanceStatuses'][0]['InstanceState']['Name']

    if instance_state != 'stopped':
        return response_maker(400, "[ERROR!] ec2 instance is not in 'stopped' state")

    ec2.modify_instance_attribute(
        InstanceId=INSTANCE_ID,
        Attribute='instanceType',
        Value=new_instance_type
    )
    ec2.start_instances(InstanceIds=[INSTANCE_ID])

    return response_maker(200, "minecraft up up up ...")


def response_maker(status, message):
    return {
        "isBase64Encoded": True,
        "statusCode": status,
        "headers": {
            "Content-Type": "application/json",
        },
        "body": {
            "message": message
        }
    }

1-3. Lambdaの中身(ec2_down)

EC2を停止する。
instancesは各自の環境に合わせて書き換える必要がある。

index.py
import boto3

# YOUR OWN INSTANCE ID
instances = ['i-00000000000000000']


def lambda_handler(event, context):
    ec2 = boto3.client("ec2")
    ec2.stop_instances(InstanceIds=instances)
    return {
        "statusCode": 200,
        "headers": {
            "Content-Type": "application/json"
        },
        "body": {
            "message": "minecraft down down down ..."
        }
    }

1-4. APIGWの設定

/up/downで異なるLambdaをinvokeするように紐付けておく。

Screen Shot 2021-07-28 at 23.25.35.png

2. discord botを設定する

2-1. discord botの運用環境

GCPでは最小インスタンスを1つまで無料で運用できる。
また、1ヶ月あたりの起動時間に制限こそあるが、用途によってはHerokuでも十分に無料運用できる。

Herokuにdiscord botをディプロイする方法は既にすばらしい記事が存在するため、深堀りしない↓

2-2. botの中身

discordサーバーの特定チャンネル内で、特定のemoji:ec2_down:, :ec2_up_common:, :ec2_up_boost:が送信されたとき、Lambdaをキックする。
emojiはメッセージ本文に含んだとき、リアクションとして押したとき、両方とも反応する。

押すスタンプの種類によって、インスタンスタイプの種類をt3a.largem5n.largeで切り替えている。
(高負荷の休日と、低負荷の平日でサーバー性能を使い分けるような想定。)

TOKEN, ID_CHANNEL_BOT, LAMBDA_INVOKE_PATHは各自の環境に合わせて書き換える必要がある。

EC2ManagerBot.py
import discord
import requests

# YOUR OWN DISCORD BOT TOKEN
TOKEN = 'AAAAAAAAAAAAAAAAAAAAAAAA.AAAAAA.AAAA_AAAAAAAAAAAAAAAAAAAAA'

# YOUR OWN DISCORD CHANNEL ID
ID_CHANNEL_BOT = 000000000000000000

EMOJI_EC2_DOWN = 'ec2_down'
EMOJI_EC2_UP_COMMON = 'ec2_up_common'
EMOJI_EC2_UP_BOOST = 'ec2_up_boost'

EC2_INSTANCE_TYPE_POOR = 't3a.large'
EC2_INSTANCE_TYPE_GOOD = 'm5n.large'

LAMBDA_INVOKE_PATH = 'https://${YOUR_OWN_APIGW_PATH}/dev/ec2'

STATUS_OK = 200

client = discord.Client()


async def down_ec2(user_name):
    channel = client.get_channel(ID_CHANNEL_BOT)
    r = requests.get(f'{LAMBDA_INVOKE_PATH}/down').json()

    status = r["statusCode"]

    if status == STATUS_OK:
        await channel.send(f'ec2 down down down ... by `{user_name}`')
    else:
        await channel.send(f'[ERROR!] lambda invocation error --> `{r["body"]}`')


async def up_ec2(user_name, ec2_instance_type):
    channel = client.get_channel(ID_CHANNEL_BOT)
    r = requests.get(
        f'{LAMBDA_INVOKE_PATH}/up?instance_type={ec2_instance_type}'
    ).json()

    status = r["statusCode"]

    if status == STATUS_OK:
        await channel.send(f'ec2 up up up ... by `{user_name}`\ninstance type is `{ec2_instance_type}`')
    else:
        await channel.send(f'[ERROR!] lambda invocation error --> `{r["body"]}`')


async def validate_message(message):
    if message.author.bot:
        return

    channel_id = message.channel.id
    if channel_id != ID_CHANNEL_BOT:
        return

    content = message.content
    author = message.author.name

    if EMOJI_EC2_DOWN in content:
        await down_ec2(author)
        return

    if EMOJI_EC2_UP_COMMON in content:
        await up_ec2(author, EC2_INSTANCE_TYPE_POOR)
        return

    if EMOJI_EC2_UP_BOOST in content:
        await up_ec2(author, EC2_INSTANCE_TYPE_GOOD)
        return


async def validate_reaction(payload):
    channel_id = payload.channel_id
    if channel_id != ID_CHANNEL_BOT:
        return

    emoji = payload.emoji.name
    author = payload.member

    if emoji == EMOJI_EC2_DOWN:
        await down_ec2(author)
        return

    if emoji == EMOJI_EC2_UP_COMMON:
        await up_ec2(author, EC2_INSTANCE_TYPE_POOR)
        return

    if emoji == EMOJI_EC2_UP_BOOST:
        await up_ec2(author, EC2_INSTANCE_TYPE_GOOD)
        return


@client.event
async def on_ready():
    channel = client.get_channel(ID_CHANNEL_BOT)
    await channel.send('hello!')


@client.event
async def on_message(message):
    await validate_message(message)


@client.event
async def on_raw_reaction_add(payload):
    await validate_reaction(payload)


client.run(TOKEN)

2-3. discordサーバーにemojiを追加

わかりやすい画像を用意して、よしなに追加する。

Screen Shot 2021-07-28 at 23.10.40.png

3. 動作を確認する

使うemojiによってインスタンスの性能を変更しつつ、EC2を起動・停止できた。
discordに参加しているメンバーなら、ワンボタンでEC2を操作できるようになった。

Screen Shot 2021-07-28 at 23.49.25.png

3
4
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
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?