はじめに
EC2は起動している時間単位で課金されるため、使う・使わないに応じてコマメに起動・停止するのが理想。
AWSコンソールで操作するのは手軽さに欠けるため、誰でも自由に操作できる環境を整備したい。
本ページではdiscordのメンバーが自由にEC2を起動・停止できるようにする他、
インスタンスタイプ(サーバー性能)を変更できるようにするbotを設定する。
サンプルコード↓
構成図
Herokuで動いているdiscord botから、APIGWを介してLambdaをキックして、EC2を起動・停止する。
やること
- EC2を起動・停止するLambdaを設置
- EC2起動時に、インスタンスタイプを変更する処理を追加
- discordのメンバーが送信した特定のemojiを検知してLambdaを起動するbotを設置
前提
- 初期設定済のEC2
- discord botを実行する環境(本ページではHeroku)
もくじ
- Lambdaを設定する
- discord botを設定する
- 動作を確認する
1. Lambdaを設定する
1-1. APIGWとLambdaの設定方法
下記ページを参照。
1-2. Lambdaの中身(ec2_up)
EC2のインスタンスタイプを上書きした後、起動する。
INSTANCE_ID
とALLOWED_INSTANCE_TYPES
は各自の環境に合わせて書き換える必要がある。
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
は各自の環境に合わせて書き換える必要がある。
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するように紐付けておく。
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.large
とm5n.large
で切り替えている。
(高負荷の休日と、低負荷の平日でサーバー性能を使い分けるような想定。)
TOKEN
, ID_CHANNEL_BOT
, LAMBDA_INVOKE_PATH
は各自の環境に合わせて書き換える必要がある。
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を追加
わかりやすい画像を用意して、よしなに追加する。
3. 動作を確認する
使うemojiによってインスタンスの性能を変更しつつ、EC2を起動・停止できた。
discordに参加しているメンバーなら、ワンボタンでEC2を操作できるようになった。