BLOGサブスレッドの日常

2025.12.19

コストをケチりながらEC2を使う - EC2自動停止

tama

tama です。

Ubuntuマシンでちょっぴり重めのファームウェアビルドをする機会がありました。
しかし、、手元には Ubuntu を動かせるPCがありません…!
(転がっているのは Apple Silicon の macbook や経理業務等で使っている Surface たち)
ぱそこんがどんどん高くなる昨今、数ヶ月しか使うアテがない機材を買うのは、、Mottainai ですよね。

そこで Ubuntu な EC2 を立てたのですが、ビルドするためには t3.nano や t3.micro ではダメで、
それなりのインスタンスタイプが必要になります。
ここでもコストがかかります。
Mottainai から EC2 にしてるのに、高額課金されては目も当てられません(x_x)

使ってるときだけEC2を起こせばいいのでは ⇒ 使っていないときは自動停止

この EC2 Ubuntu はセッションマネージャーを使ってアクセスして中で作業をするものなので、
サーバーのようにバックグラウンドで常時起こしておく必要はありません。

だったら!

接続が無くなったらインスタンスを停止してしまえばよいのです。
Instance Scheduler を使えば決まった時間にインスタンスを起こしたり停止したりはできますが、
もっとシンプルに、シェルスクリプトひとつで使ってないときは停止することにします。

#!/bin/bash

# 接続しているユーザー数を数える
ACTIVE_USERS=$(who | wc -l)

if [ "$ACTIVE_USERS" -eq "0" ]; then
    # EC2自身のインスタンスIDを取得して stop させる
    TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
      -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
    INSTANCE_ID=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
      http://169.254.169.254/latest/meta-data/instance-id)
    aws ec2 stop-instances --instance-ids "$INSTANCE_ID" --region ap-northeast-1
fi

EC2上にこんなシェルスクリプトを作って、sudo crontab -e で 10分ごとに起動することにします。
*/10 * * * * /home/ubuntu/watchdog.sh

who の結果を見て誰もいなくなったら
AWS CLI aws ec2 stop-instances を使ってインスタンスを停止するだけなのですが、
EC2 が自分自身のインスタンスIDを知る必要があります。ワタシハダレ
これはメタデータにアクセスすればよくて、そのためにはトークンが必要で…というのが TOKEN=INSTANCE_ID= の2行です。
簡単ですね!

ちょっとめんどくさい ⇒ 使うときだけEC2を自動起動

これで最長10分のアイドルタイムはあれど、使っていないときは自動的に停止してくれます。
めでたしめでたし…

ではあるのですが、EC2 Ubuntu を使うたびに AWSコンソールにログインして EC2 を起こすのはめんどくさいです。
これも自動化しちゃいましょう(スクリプトは末尾に掲載)

こちらは EC2 に接続するための処理なので、手元の mac や WSL などで動かすスクリプトです。
AWS CLI を使って EC2 の状態を取得 → stopped だったら起動処理をかけてしばらく待つ → SSM で接続
という一連の処理をします。

EC2 インスタンスを起こして使えるようになるまで少し待たないといけないのがもどかしくはありますが、
AWSコンソールから EC2起動しても同じだけ時間はかかるし、AWSコンソールにログインする手間を考えれば
いまはこれでいっか、と思ってます。

おしまい

ケチケチ大作戦でした!
もっといい方法をご存知の方、ぜひコッソリ教えてください(o'ヮ'o)

#!/usr/bin/env bash

# AWS CLI を使うとき使う profile と、対象EC2のインスタンスID
export AWS_PROFILE=any-profile
export INSTANCE_ID=i-XXXXXXXXXXXXXXXXX
# ※実運用ではハードコードせず、環境変数などに入れておくとよいかも

get_status() {
  # インスタンスの状態を取得
  aws ec2 describe-instances \
    --profile "${AWS_PROFILE}" \
    --instance-ids "${INSTANCE_ID}" \
    --query "Reservations[].Instances[].State.Name" \
    --output text
}

start_instance() {
  aws ec2 start-instances \
    --profile "${AWS_PROFILE}" \
    --instance-ids "${INSTANCE_ID}" \
    --query "StartingInstances[].CurrentState.Name" \
    --output text
}

wait_instance() {
  local status="$(get_status)"

  # stoppedになるまで待つ
  until [[ "$status" == "stopped" ]]; do
    case "$status" in
      running)
        # 起動中ならそもそも関数を抜けて良い
        return
        ;;
      shutting-down|terminated)
        # インスタンスが終了していたら処理できない
        echo "インスタンスは終了しています"
        exit 1
        ;;
      *)
        # pending、stopping などその他の状態なら変化が終わるのを待つ
        echo $status
        sleep 2
        status="$(get_status)"
        ;;
    esac
  done
  # stopped になった

  echo "EC2 を起動します"
  start_instance

  # running になるのを待つ
  until [[ "$(get_status)" == "running" ]]; do
      sleep 2
  done
  echo "EC2 が起動しました"
  sleep 8 # running になってから start-session できるようになるまで少し時間がかかる
}

if [ "$1" != "--quick" ]; then
  # EC2 が起動状態になるのを担保する
  wait_instance
fi

# Session Manager で接続する
aws ssm start-session \
  --profile "${AWS_PROFILE}" \
  --target "${INSTANCE_ID}"

この記事を書いた人

tama