EC2インスタンスを別リージョンにコピーするスクリプト

# はじめに
AWSのEC2インスタンスを別リージョンに移設する必要があったため、対象EC2インスタンスID.ソースリージョン、ターゲットリージョンを指定して、AMIを作成し、別リージョンにコピーするスクリプトを作成しました。

# 前提
- AWS CLIがインストールされていること
- AWS ConfigureでIAM認証情報が設定されていること

# スクリプトの概要

このスクリプトは以下の機能を提供します：

1. 指定されたEC2インスタンスからAMIを作成
2. AMI作成を待って、作成したAMIを別リージョンにコピー

# スクリプト

<details open><summary>スクリプトファイル内容</summary>

```bash
#!/bin/bash

set -e

# ヘルプメッセージの表示
show_help() {
    cat << EOF
EC2インスタンスを別リージョンにコピーするスクリプト

使用方法:
    $(basename $0) [オプション]

オプション:
    --instance-id     コピー元のEC2インスタンスID（必須）
    --source-region   コピー元のAWSリージョン（必須）
    --target-region   コピー先のAWSリージョン（必須）
    --ami-name        AMIの名前（オプション）
    --help           このヘルプメッセージを表示

例:
    # AMI名を指定する場合
    $(basename $0) --instance-id i-1234567890abcdef0 --source-region ap-northeast-1 --target-region us-west-2 --ami-name "custom-ami-name"
    
    # AMI名を指定しない場合（EC2のNameタグから自動生成）
    $(basename $0) --instance-id i-1234567890abcdef0 --source-region ap-northeast-1 --target-region us-west-2
EOF
    exit 1
}

# エラーメッセージを表示して終了
error_exit() {
    echo "エラー: $1" >&2
    exit 1
}

# 引数のパース
while [[ $# -gt 0 ]]; do
    case $1 in
        --help)
            show_help
            ;;
        --instance-id)
            INSTANCE_ID="$2"
            shift 2
            ;;
        --source-region)
            SOURCE_REGION="$2"
            shift 2
            ;;
        --target-region)
            TARGET_REGION="$2"
            shift 2
            ;;
        --ami-name)
            CUSTOM_AMI_NAME="$2"
            shift 2
            ;;
        *)
            error_exit "不明な引数: $1"
            ;;
    esac
done

# 必須パラメータのチェック
[[ -z "$INSTANCE_ID" ]] && error_exit "instance-idは必須です"
[[ -z "$SOURCE_REGION" ]] && error_exit "source-regionは必須です"
[[ -z "$TARGET_REGION" ]] && error_exit "target-regionは必須です"

# インスタンスIDの形式チェック
[[ ! "$INSTANCE_ID" =~ ^i-[a-f0-9]{8,}$ ]] && error_exit "無効なインスタンスID形式です: $INSTANCE_ID"

# AWS CLIの存在確認
command -v aws >/dev/null 2>&1 || error_exit "AWS CLIがインストールされていません"

echo "パラメータの確認:"
echo "インスタンスID: $INSTANCE_ID"
echo "ソースリージョン: $SOURCE_REGION"
echo "ターゲットリージョン: $TARGET_REGION"

# EC2インスタンスの存在確認とNameタグの取得
echo "EC2インスタンスの情報を取得中..."
INSTANCE_INFO=$(aws ec2 describe-instances \
    --instance-ids "$INSTANCE_ID" \
    --region "$SOURCE_REGION" \
    --query 'Reservations[0].Instances[0]' \
    --output json 2>/dev/null) || error_exit "インスタンスの取得に失敗しました"

# AMI名の設定
if [[ -n "$CUSTOM_AMI_NAME" ]]; then
    AMI_NAME="$CUSTOM_AMI_NAME"
else
    # Nameタグの取得
    INSTANCE_NAME=$(echo "$INSTANCE_INFO" | jq -r '.Tags[] | select(.Key=="Name") | .Value' 2>/dev/null)
    if [[ -z "$INSTANCE_NAME" ]]; then
        error_exit "指定されたEC2インスタンス($INSTANCE_ID)にNameタグが設定されていません。--ami-nameオプションでAMI名を指定してください。"
    fi
    AMI_NAME="${INSTANCE_NAME}_AMI"
fi
echo "AMI名: $AMI_NAME"

# AMIの作成
echo "AMIを作成中..."
AMI_ID=$(aws ec2 create-image \
    --instance-id "$INSTANCE_ID" \
    --name "$AMI_NAME" \
    --description "Created from $INSTANCE_ID" \
    --region "$SOURCE_REGION" \
    --no-reboot \
    --output text) || error_exit "AMIの作成に失敗しました"

echo "AMI ID: $AMI_ID"

# AMI状態の待機処理
wait_for_image() {
    local image_id=$1
    local region=$2
    local retries=6
    local attempt=1

    while [ $attempt -le $retries ]; do
        echo "AMI状態確認: 試行 $attempt/$retries"
        if aws ec2 wait image-available --image-ids "$image_id" --region "$region"; then
            return 0
        fi
        attempt=$((attempt + 1))
        if [ $attempt -le $retries ]; then
            echo "リトライします..."
        fi
    done
    return 1
}

# AMI作成完了を待機
echo "AMIの作成完了を待機中..."
if ! wait_for_image "$AMI_ID" "$SOURCE_REGION"; then
    error_exit "AMIの作成待機中にエラーが発生しました（6回試行後）"
fi

echo "AMIの作成が完了しました"

# AMIを別リージョンにコピー
echo "AMIを $TARGET_REGION にコピー中..."
COPIED_AMI_ID=$(aws ec2 copy-image \
    --source-image-id "$AMI_ID" \
    --source-region "$SOURCE_REGION" \
    --region "$TARGET_REGION" \
    --name "$AMI_NAME" \
    --output text) || error_exit "AMIのコピーに失敗しました"

echo "コピーされたAMI ID: $COPIED_AMI_ID"

# AMIコピーの完了を待機
echo "AMIのコピー完了を待機中..."
if ! wait_for_image "$COPIED_AMI_ID" "$TARGET_REGION"; then
    error_exit "AMIのコピー待機中にエラーが発生しました（6回試行後）"
fi

echo "処理が正常に完了しました"
echo "ソースAMI ID (${SOURCE_REGION}): ${AMI_ID}"
echo "コピー先AMI ID (${TARGET_REGION}): ${COPIED_AMI_ID}"
```

</details>

# 使用方法

```bash
./copy-ec2.sh [オプション]

オプション:
    --instance-id     コピー元のEC2インスタンスID（必須）
    --source-region   コピー元のAWSリージョン（必須）
    --target-region   コピー先のAWSリージョン（必須）
    --ami-name        AMIの名前（オプション）
    --help           このヘルプメッセージを表示
```

## 実行例

```bash
# AMI名を指定する場合
./copy-ec2.sh --instance-id i-1234567890abcdef0 --source-region ap-northeast-1 --target-region us-west-2 --ami-name "custom-ami-name"

# AMI名を指定しない場合（EC2のNameタグから自動生成）
./copy-ec2.sh --instance-id i-1234567890abcdef0 --source-region ap-northeast-1 --target-region us-west-2
```

# スクリプトの主要な機能

## 1. パラメータの検証

```bash
# 必須パラメータのチェック
[[ -z "$INSTANCE_ID" ]] && error_exit "instance-idは必須です"
[[ -z "$SOURCE_REGION" ]] && error_exit "source-regionは必須です"
[[ -z "$TARGET_REGION" ]] && error_exit "target-regionは必須です"

# インスタンスIDの形式チェック
[[ ! "$INSTANCE_ID" =~ ^i-[a-f0-9]{8,}$ ]] && error_exit "無効なインスタンスID形式です: $INSTANCE_ID"
```

## 2. AMI名の設定

AMI名は以下の優先順位で決定されます：

1. `--ami-name`オプションで指定された値
2. EC2インスタンスのNameタグの値 + "_AMI"

```bash
if [[ -n "$CUSTOM_AMI_NAME" ]]; then
    AMI_NAME="$CUSTOM_AMI_NAME"
else
    INSTANCE_NAME=$(echo "$INSTANCE_INFO" | jq -r '.Tags[] | select(.Key=="Name") | .Value' 2>/dev/null)
    if [[ -z "$INSTANCE_NAME" ]]; then
        error_exit "指定されたEC2インスタンス($INSTANCE_ID)にNameタグが設定されていません。--ami-nameオプションでAMI名を指定してください。"
    fi
    AMI_NAME="${INSTANCE_NAME}_AMI"
fi
```

## 3. AMIの作成とコピー

AMIの作成とコピーは以下の手順で行われます：

1. ソースリージョンでAMIを作成
2. AMIの作成完了を待機
3. ターゲットリージョンにAMIをコピー
4. コピーの完了を待機

```bash
# AMIの作成
AMI_ID=$(aws ec2 create-image \
    --instance-id "$INSTANCE_ID" \
    --name "$AMI_NAME" \
    --description "Created from $INSTANCE_ID" \
    --region "$SOURCE_REGION" \
    --no-reboot \
    --output text)

# AMIを別リージョンにコピー
COPIED_AMI_ID=$(aws ec2 copy-image \
    --source-image-id "$AMI_ID" \
    --source-region "$SOURCE_REGION" \
    --region "$TARGET_REGION" \
    --name "$AMI_NAME" \
    --output text)
```

# まとめ

AMI作成やコピーに時間がかかるため、GUIで作業すると待機時間がかかるため、スクリプトで自動化することで、放置している間に作業が完了するため、便利かなと思い、作成しました。

# 参考文献
- [AWS CLI リファレンス](https://docs.aws.amazon.com/cli/latest/reference/ec2/create-image.html)