技術(tech)

GitHub ActionsでAWS EC2 Spot Instanceを自動制御する開発環境の構築方法

メタディスクリプション: GitHub ActionsとAWS EC2 Spot Instanceを活用した自動制御開発環境の構築方法。stop/start/status機能の実装詳細と実用的な運用手順を解説。

はじめに

GitHub ActionsAWS EC2 Spot Instanceを組み合わせた自動制御開発環境を構築し、月額3-4ドルでClaude Code実行環境を運用する方法を解説します。

  • 技術スタック:GitHub ActionsAWS EC2Spot Instance
  • コスト削減効果:従来の80%減(月額$15-20 → $3-4)

対象読者

  • AWS EC2GitHub Actionsの基本知識がある方
  • クラウド開発環境のコスト最適化に取り組みたい方

技術背景と解決アプローチ

従来の開発環境では24時間EC2を稼働させるため、実使用時間に対して過剰なコストが発生します。AWS EC2 Spot Instanceは最大90%のコスト削減が可能ですが、予告なく終了される可能性があります。この特性を逆手に取り、GitHub Actionsによる完全自動化で一時的な開発環境として活用します。

運用課題

  • 固定費問題: t3.microでも月額15-20ドル
  • 手動運用: AWSコンソールでの煩雑な操作
  • 環境復旧: Spot Instance中断後の再構築

システム構成と主要機能

.github/workflows/ec2-control.yml  # メインワークフロー
user-data.sh                       # EC2初期化スクリプト

AWS Infrastructure
├── EC2 Spot Instance (t3.micro)
├── EBS Volume (暗号化済み)
└── Security Group

主要機能

  1. ワンクリック操作: GitHub Actions UIからEC2制御
  2. データ永続化: EBSボリュームでデータ保持
  3. 完全自動化: 環境構築からVPN設定まで自動

AWS EC2 Spot Instance自動制御の実装手順

実際のGitHub Actions ワークフローファイルと必要な設定について、順を追って説明します。

事前準備:AWSリソースとGitHub Actions設定

1. VPCとネットワークの準備

ワークフローではVPCとサブネットを自動検出するため、適切なタグを設定したリソースが必要です:

# VPCの作成(既存VPCがない場合)
VPC_ID=$(aws ec2 create-vpc \
  --cidr-block 10.0.0.0/16 \
  --query 'Vpc.VpcId' \
  --output text)

# VPCに「main」タグを追加(重要!ワークフローがこのタグで検索)
aws ec2 create-tags \
  --resources $VPC_ID \
  --tags Key=Name,Value=main

# インターネットゲートウェイの作成
IGW_ID=$(aws ec2 create-internet-gateway \
  --query 'InternetGateway.InternetGatewayId' \
  --output text)

# VPCにインターネットゲートウェイをアタッチ
aws ec2 attach-internet-gateway \
  --vpc-id $VPC_ID \
  --internet-gateway-id $IGW_ID

# パブリックサブネットの作成
SUBNET_ID=$(aws ec2 create-subnet \
  --vpc-id $VPC_ID \
  --cidr-block 10.0.1.0/24 \
  --availability-zone ap-northeast-1a \
  --query 'Subnet.SubnetId' \
  --output text)

# サブネットに「Public」タグを追加(重要!ワークフローがこのタグで検索)
aws ec2 create-tags \
  --resources $SUBNET_ID \
  --tags Key=Name,Value=Public-Subnet-1a

# パブリックIPの自動割り当てを有効化
aws ec2 modify-subnet-attribute \
  --subnet-id $SUBNET_ID \
  --map-public-ip-on-launch

# ルートテーブルの設定(インターネットアクセス用)
ROUTE_TABLE_ID=$(aws ec2 describe-route-tables \
  --filters "Name=vpc-id,Values=$VPC_ID" \
  --query 'RouteTables[0].RouteTableId' \
  --output text)

aws ec2 create-route \
  --route-table-id $ROUTE_TABLE_ID \
  --destination-cidr-block 0.0.0.0/0 \
  --gateway-id $IGW_ID

2. セキュリティグループとIAMリソースの準備

# 1. セキュリティグループの作成
SECURITY_GROUP_ID=$(aws ec2 create-security-group \
  --group-name claude-code-smartphone-sg \
  --description "Security group for development environment" \
  --vpc-id $VPC_ID \
  --query 'GroupId' \
  --output text)

# 2. セキュリティグループにタグを追加(重要!ワークフローがこのタグで検索)
aws ec2 create-tags \
  --resources $SECURITY_GROUP_ID \
  --tags Key=Name,Value=claude-code-smartphone-sg

# 3. セキュリティグループルールの設定
# SSHアクセス(必要に応じてIPを制限してください)
aws ec2 authorize-security-group-ingress \
  --group-id $SECURITY_GROUP_ID \
  --protocol tcp \
  --port 22 \
  --cidr 0.0.0.0/0

# HTTPSアクセス(Webサービスを実行する場合)
aws ec2 authorize-security-group-ingress \
  --group-id $SECURITY_GROUP_ID \
  --protocol tcp \
  --port 443 \
  --cidr 0.0.0.0/0

# HTTPアクセス(開発用)
aws ec2 authorize-security-group-ingress \
  --group-id $SECURITY_GROUP_ID \
  --protocol tcp \
  --port 80 \
  --cidr 0.0.0.0/0

# 4. OIDCプロバイダーの作成(初回のみ)
aws iam create-open-id-connect-provider \
  --url https://token.actions.githubusercontent.com \
  --client-id-list sts.amazonaws.com \
  --thumbprint-list YOUR_THUMBPRINT

# 5. IAMロールの作成(GitHub Actions用)
# まず信頼ポリシーファイルを作成
# YOUR_ACCOUNT_ID, YOUR_GITHUB_USERNAME, YOUR_REPO_NAMEを実際の値に置き換えてください
cat > trust-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::YOUR_ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
          "token.actions.githubusercontent.com:sub": "repo:YOUR_GITHUB_USERNAME/YOUR_REPO_NAME:ref:refs/heads/main"
        }
      }
    }
  ]
}
EOF

# IAMロールの作成
aws iam create-role \
  --role-name GitHubActionsEC2Role \
  --assume-role-policy-document file://trust-policy.json

# 6. 必要なポリシーをアタッチ
aws iam attach-role-policy \
  --role-name GitHubActionsEC2Role \
  --policy-arn arn:aws:iam::aws:policy/AmazonEC2FullAccess

# 7. インスタンスプロファイルの作成(必要に応じて)
aws iam create-instance-profile \
  --instance-profile-name GitHubActionsEC2InstanceProfile

aws iam add-role-to-instance-profile \
  --instance-profile-name GitHubActionsEC2InstanceProfile \
  --role-name GitHubActionsEC2Role

重要な設定ポイント

タグ設定の重要性

  • VPCには必ず Name=main タグを設定
  • パブリックサブネットには Name=Public* パターンのタグを設定
  • セキュリティグループには Name={PROJECT_NAME}-sg パターンのタグを設定

これらのタグがないと、ワークフローの自動検出機能が動作しません。

3. GitHub Actions Secrets設定

リポジトリの Settings > Secrets and variables > Actions で以下を設定:

# 必須のSecrets
AWS_ROLE_ARN: arn:aws:iam::YOUR_ACCOUNT_ID:role/GitHubActionsEC2Role
GIT_USERNAME: your-github-username
GIT_EMAIL: your-email@example.com
GIT_TOKEN: ghp_YOUR_GITHUB_TOKEN
TAILSCALE_AUTH_KEY: tskey-auth-YOUR_KEY

# オプション(Secretsで設定しない場合はVariablesで設定可能)
# Settings > Secrets and variables > Actions > Variables
GIT_USERNAME: your-github-username  # Secretsで設定していない場合
GIT_EMAIL: your-email@example.com   # Secretsで設定していない場合

1. EC2自動制御のGitHub Actionsワークフロー設定

name: EC2 Instance Control

on:
  workflow_dispatch:
    inputs:
      action:
        description: 'EC2 Action'
        required: true
        default: 'status'
        type: choice
        options:
        - status
        - start
        - stop

permissions:
  id-token: write
  contents: read

env:
  AWS_REGION: ap-northeast-1
  INSTANCE_NAME: claude-code-smartphone-server
  INSTANCE_TYPE: t3.micro
  SPOT_PRICE_MAX: "0.005"
  VOLUME_SIZE: "20"
  PROJECT_NAME: claude-code-smartphone

jobs:
  ec2-control:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v4
      with:
        role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
        aws-region: ${{ env.AWS_REGION }}

    - name: Set GitHub user variables
      run: |
        echo "GH_USER=${{ secrets.GIT_USERNAME || vars.GIT_USERNAME }}" >> $GITHUB_ENV
        echo "GH_MAIL=${{ secrets.GIT_EMAIL || vars.GIT_EMAIL }}" >> $GITHUB_ENV

    - name: Execute EC2 Control
      run: |
        # 関数定義を行い、メイン処理を実行
        # ここで以下の関数を定義します

        case "${{ github.event.inputs.action }}" in
          "status")
            echo "EC2インスタンスの状態確認中..."
            check_instance_status
            ;;
          "start")
            echo "EC2インスタンスを起動中..."
            start_or_create_instance
            ;;
          "stop")
            echo "EC2インスタンスを停止中..."
            stop_instance
            ;;
        esac

ワークフローの主要特徴:

  • workflow_dispatch: GitHub Actions UIからの手動実行と実行時アクション選択機能
  • permissions: GitHub OIDCを使用したAWSへのセキュア接続(長期的な認証情報が不要)
  • 環境変数: インスタンス設定の一元管理

2. EC2制御機能の実装

基本的な制御ロジック:

# Status check
get_instance_status() {
  aws ec2 describe-instances \
    --filters "Name=tag:Name,Values=$INSTANCE_NAME" \
    --query 'Reservations[0].Instances[0].State.Name' \
    --output text
}

# Start/Create instance
start_or_create_instance() {
  EXISTING_INSTANCE=$(get_instance_id)
  if [ -n "$EXISTING_INSTANCE" ]; then
    aws ec2 start-instances --instance-ids "$EXISTING_INSTANCE"
  else
    create_spot_instance
  fi
}

# Stop instance
stop_instance() {
  INSTANCE_ID=$(get_instance_id)
  aws ec2 stop-instances --instance-ids "$INSTANCE_ID"
}

3. Spot Instance作成

create_spot_instance() {
  # Auto-detect VPC/Subnet
  VPC_ID=$(aws ec2 describe-vpcs --filters "Name=tag:Name,Values=main" --query 'Vpcs[0].VpcId' --output text)
  SUBNET_ID=$(aws ec2 describe-subnets --filters "Name=tag:Name,Values=Public*" --query 'Subnets[0].SubnetId' --output text)

  # Get latest Ubuntu AMI
  AMI_ID=$(aws ec2 describe-images --owners 099720109477 \
    --filters "Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*" \
    --query 'Images | sort_by(@, &CreationDate) | [-1].ImageId' --output text)

  # Request Spot Instance
  aws ec2 request-spot-instances \
    --spot-price "$SPOT_PRICE_MAX" \
    --instance-count 1 \
    --type "one-time" \
    --launch-specification "{
      \"ImageId\": \"$AMI_ID\",
      \"InstanceType\": \"$INSTANCE_TYPE\",
      \"UserData\": \"$(base64 -i user-data.sh)\"}"
}

4. データ永続化設定

EBSボリューム設定でデータ保持:

"BlockDeviceMappings": [{
  "DeviceName": "/dev/sda1",
  "Ebs": {
    "VolumeSize": 20,
    "VolumeType": "gp3",
    "DeleteOnTermination": false,  // 重要: インスタンス終了時も削除しない
    "Encrypted": true
  }
}]

5. 自動環境セットアップ (user-data.sh)

#!/bin/bash
set -euo pipefail

# System update
sudo apt-get update && sudo apt-get upgrade -y

# Git configuration
git config --global user.name "$git_username"
git config --global user.email "$git_email"
echo "https://$git_username:$github_token@github.com" > ~/.git-credentials
chmod 600 ~/.git-credentials
git config --global credential.helper store

# Tailscale VPN
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --authkey="$tailscale_auth_key" --ssh

# Development tools
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker ubuntu
curl -fsSL https://claude.ai/install.sh | sh

運用結果とコスト削減効果

3ヶ月の運用後:

コスト比較

# 従来 (オンデマンド24時間稼働)
t3.micro: $15/月 + EBS: $2/月 = ~$17-20/月

# 改善後 (Spot Instance + 4時間/日)
Spot: $0.4/月 + EBS: $2/月 + 転送: $1/月 = ~$3-4/月

削減率: 80%のコスト削減を達成

運用効率の改善

  • 起動時間: 5-10分 → 2-3分
  • 操作: 10回以上のクリック → 1クリック
  • VPN設定: 手動 → 自動

まとめ

GitHub ActionsとEC2 Spot Instanceを組み合わせることで、従来の1/5のコストで柔軟な開発環境を実現できました。

成功のポイント

  • 完全自動化: 環境構築からVPN設定まで自動化
  • コスト最適化: Spot Instanceの特性を開発環境のニーズにマッチ
  • データ保護: EBSボリュームでインスタンス終了後もデータ保持

クラウド開発環境のコストで悩んでいる方は、このアプローチを検討してみてください。

建てたEC2をどう扱うのかは、以下の記事に詳しく記載しています。

スマートフォンでちょこっと開発!Claude Code + AWS EC2で月額300円の気軽なAI開発環境 この記事で分かること Claude Code CLIを活用したAI駆動開発環境の構築 スマートフォンからAWS EC2への安全な接続...

参考資料