EC2の自動停止スクリプト(Lambda+CloudWatch Events)

うはww また消し忘れて寝てるww 請求書((((;゚Д゚))))
となるのを避けるのを防ぐためのスクリプトのお話。 今回は、下記で実施
- Lambda
- bash
- awscli
- CloudWatch Events

前提

  • 開発環境でaws cliが設定済み
  • Lambdaで利用するロールは事前に作成済みとする
    今回の場合だと、AWSLambdaBasicExecutionRole以外にも、
    EC2を操作する権限や、パラメータストアから値を取得する権限が必要

シェルの用意

除外する方法もいろいろ。今回はAWS Systems Manager のパラメータストアを利用
組織とかで使うなら、tagやファイルの読み込みのほうが使いやすそう

#!/bin/bash

#起動中インスタンスの一覧を取得
RUNNING_INSTANCE=$(aws ec2 describe-instances | jq -r '[.Reservations[].Instances[] | select(.State.Name == "running") | .InstanceId] | join(" ")')
#除外対象とするインスタンスの一覧を取得
EXCLUDE_LIST=$(aws ssm get-parameter --name <設定するパラメータキー名> | jq -r '.Parameter.Value' | sed -e 's/,/ /g' )

#除外対象以外のインスタンスを停止
for e in ${RUNNING_INSTANCE[@]}; do
    if ! `echo ${EXCLUDE_LIST[@]} | grep -q "${e[@]}"` ; then
    aws ec2 stop-instances --instance-ids ${e[@]}
else
    :
fi
done

パラメータストアの設定

valueに除外対象のインスタンスIdをカンマ区切りで記載

aws ssm put-parameter \
    --name "パラメータキー名" \
    --value "除外対象のインスタンスID_1,除外対象のインスタンスID_2" \
    --type StringList

Lamda関数の作成

bashの場合カスタムランタイムで起動する。すぐに終わるかと思ったが、 awsチュートリアルで進めると、awscliやjqコマンドをたたく際に、そんなコマンドねーぞと怒られるので少し準備が必要。 Bashを動かす環境としてbash-lambda-layerなる便利なものが公開されていたのでこちらをlayerとして使わせていただきます<(_ _)>

アップロードするスクリプトファイルの作成

bash-lambda-layer example-basic.shを参考に用意したシェルを修正し、index.shとして保存

function handler () {
    set -e

    #起動中インスタンスの一覧を取得
    RUNNING_INSTANCE=$(aws ec2 describe-instances | jq -r '[.Reservations[].Instances[] | select(.State.Name == "running") | .InstanceId] | join(" ")')
    #除外対象とするインスタンスの一覧を取得
    EXCLUDE_LIST=$(aws ssm get-parameter --name exclude.ec2instance | jq -r '.Parameter.Value' | sed -e 's/,/ /g' )
    
    #除外対象以外のインスタンスを停止
    for e in ${RUNNING_INSTANCE[@]}; do
        if ! `echo ${EXCLUDE_LIST[@]} | grep -q "${e[@]}"` ; then
        aws ec2 stop-instances --instance-ids ${e[@]}
    else
        :
    fi
    done

    echo "{\"success\": true}" >&2
}

アップロードのためindex.shをzip化

 zip function.zip index.sh

awscliでlambda関数の作成

$ aws lambda create-function \
    --function-name terminate-ec2 \
    --role <事前準備済みのroleを指定> \
    --handler index.handler \
    --runtime provided \
    --timeout --> defaultの3秒は厳しいので適当な値を設定 \
    --memory-size --> memoryもデフォルト128 MBだと結構時間がかかるのでry \
    --layers arn:aws:lambda:ap-northeast-1:744348701589:layer:bash:8 \
    --zip-file fileb://function.zip

起動確認

$ aws lambda invoke --function-name terminate-ec2 response.txt
{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}
$ cat response.txt
{"success": true}

Cloudwatch Eventsの作成

時刻はUTCなので日本時間では+9 (夜中の3時に実行)

$ aws events put-rule --name "Ec2TerminateAtMidnight" --schedule-expression "cron(0 18 * * ? *)" --state ENABLED

CloudWatch Eventsから作成したLamba実行できるための権限付与

$ aws lambda add-permission \
  --function-name "terminate-ec2" \
  --statement-id "cwevents" \
  --action 'lambda:InvokeFunction' \
  --principal events.amazonaws.com \
  --source-arn arn:aws:events:ap-northeast-1:<accountId>:rule/Ec2TerminateAtMidnight

targetの設定 (eventとlambda関数の紐づけ)

aws events put-targets --rule "Ec2TerminateAtMidnight" --targets Arn=arn:aws:lambda:ap-northeast-1:<accountId>:function:terminate-ec2,Id=1

こんな感じでできれば完成

f:id:genepon:20200613232226p:plain
完成図

参考

チュートリアル – カスタムランタイムの公開
gkrizek/bash-lambda-layer
AWS CLIを使ってEC2インスタンスの情報を取得する