EC2-Classic環境またはVPC環境のAWS EC2インスタンスをElastic IPごと新しいVPCに移行するAWS CLIを用いたシェルスクリプト(バッチ処理プログラム)

6月 22, 2017AWS,AWS CLI,EC2,Programming,Shell Script,VPC

AWS EC2はクラウド上にサーバを素早く用意し柔軟に操作できることが特徴のクラウドサービスです。
現在のAWS EC2はVPCとあわせて使用することがほとんどですが、従来のEC2-Classicを未だに使用している場合やネットワーク変更などでVPCの移行が必要になる場合があります。

そのような場合もEC2インスタンスが提供するサービスのダウンタイムを最小限にして素早く、柔軟に移行処理を行うことが、ほとんどのリソースをプログラマブルに利用できるAWSなら可能です。
今回は様々な移行要件のなかでもEC2-Classic環境またはVPC環境から新しいVPC環境にEC2インスタンスを移行するAWS CLIを用いたシェルスクリプト(バッチ処理プログラム)を備忘録として掲載したいと思います。

EC2-Classic環境またはVPC環境のAWS EC2インスタンスをElastic IPごと新しいVPCに移行するAWS CLIを用いたシェルスクリプト(バッチ処理プログラム)

VPC移行シェルスクリプト(バッチ処理プログラム)の処理の流れ

Elastic IPが関連付けられたEC2インスタンスが起動していることを前提とします。

  • 移行元のEC2-Classic環境またはVPC環境のインスタンスのAMIを作成する
  • 移行元がEC2-Classic環境の場合はElastic IPをVPCに移行する
  • AMIの作成、Elastic IPのVPC移行が完了するのを待つ
  • 取得したAMIから移行先のVPCにインスタンスを作成する
  • インスタンスの状態がrunning、システムステータスとインスタンスステータスのチェックがpassedになるのを待つ
  • Elastic IPを関連付けし、NameタグにAMI名を入力する。

移行の時間の大半はAMI作成の時間とAMIからEC2インスタンスが起動するまでの待ち時間です。
AMIの作成時間はAMIイメージに関連付けられるスナップショットの元となるEBSのサイズが大きいほど、また前回のAMI作成からの変更差分が大きいほど時間がかかります。

ですので、一概に移行時間については言及することはできませんが、Tokyoリージョンでの個人的な施行では10GBのEBSをアタッチしているオリジナルのAMIから起動しただけのAmazon Linuxであれば5分から10分程度で移行が完了します。

VPC移行シェルスクリプト(バッチ処理プログラム)のソースコード例

#!/bin/bash

#EC2インスタンスをEC2-Classic環境またはVPC環境から新しいVPCに環境に移行するスクリプト

#移行元のインスタンスID
FROM_INS_ID=${1}
#移行先のプライベートIP
TO_PIP=${2}
#移行先のセキュリティーグループID
TO_SG_ID=${3}
#移行先のサブネットID
TO_SUBNET_ID=${4}
#移行先のインスタンスタイプ
TO_TYPE=${5}
#移行先のキーペア
TO_KEY_NAME=${6}
#AMI名
AMI_NAME=${7}
#移行元および移行後のElastic IP
EIP=${8}

#ステータスの確認間隔
DELAY=10

DATE_STR=`date +"%Y/%m/%d %H:%M:%S"`
echo "Start. ${DATE_STR}"

#移行元インスタンスのEIPを取得
echo "aws ec2 describe-instances --output text --instance-ids ${FROM_INS_ID} --query 'Reservations[].Instances[].PublicIpAddress'"
ASSOCIATED_EIP=`aws ec2 describe-instances --output text --instance-ids ${FROM_INS_ID} --query 'Reservations[].Instances[].PublicIpAddress'`

echo "ASSOCIATED_EIP=${ASSOCIATED_EIP}"

#移行元インスタンスにEIPの関連付けがない場合でスクリプト引数にもEIPがない場合は処理を終了
if [ "${ASSOCIATED_EIP}" = "" -a "${EIP}" = "" ] ; then
  echo "EIP is not associated. Please enter EIP parameter by manual."
  exit 1
fi

#移行元インスタンスにEIPの関連付けがある場合はEIPの関連付けを外す
if [ "${ASSOCIATED_EIP}" != "" ] ; then
  EIP=${ASSOCIATED_EIP}
  DOMAIN=`aws ec2 describe-addresses --output text --public-ips ${EIP} --query 'Addresses[].Domain'`
  #移行元がEC2-Classicの場合はEIPを引数に関連付けを外し、VPCの場合は関連付けID(AssociationId)を引数に関連付けをはずす
  if  [ "${DOMAIN}" != "vpc" ] ; then
    echo "aws ec2 disassociate-address --public-ip ${EIP}"
    aws ec2 disassociate-address --output text --public-ip ${EIP}
  else
    ASSOC_ID=`aws ec2 describe-addresses --output text --public-ips ${EIP} --query 'Addresses[].AssociationId'`
    echo "aws ec2 disassociate-address --output text --association-id ${ASSOC_ID}"
    aws ec2 disassociate-address --output text --association-id ${ASSOC_ID}
  fi
fi

#移行元インスタンスを停止する
echo "aws ec2 stop-instances --output text --instance-ids ${FROM_INS_ID}"
aws ec2 stop-instances --output text --instance-ids ${FROM_INS_ID}

#移行元インスタンスの停止処理中のステータスを取得する
echo "aws ec2 describe-instances --output text --instance-ids ${FROM_INS_ID} --query 'Reservations[].Instances[].State.Name'"
STATE=`aws ec2 describe-instances --output text --instance-ids ${FROM_INS_ID} --query 'Reservations[].Instances[].State.Name'`

echo "STATE=${STATE}"

#移行元インスタンスが停止するまで処理を待つ
while [ "${STATE}" != "stopped" ] 
do
  sleep ${DELAY}
  STATE=`aws ec2 describe-instances --output text --instance-ids ${FROM_INS_ID} --query 'Reservations[].Instances[].State.Name'`
  echo "STATE=${STATE}"
done

#AMI_NAMEの引数が指定されていない場合はインスタンスIDと日付でAMI名を指定する
if  [ "${AMI_NAME}" = "" ] ; then
  DATE_STR=`date +"%Y%m%d-%H%M%S"`
  echo "DATE_STR=${DATE_STR}"
  AMI_NAME="${FROM_INS_ID}-${DATE_STR}"
fi

#移行元インスタンスのAMIを作成する
echo "aws ec2 create-image --output text --instance-id ${FROM_INS_ID} --name ${AMI_NAME}"
AMI_ID=`aws ec2 create-image --output text --instance-id ${FROM_INS_ID} --name ${AMI_NAME}`

echo "AMI_ID=${AMI_ID}"


#AMI作成、Elastic IPのVPCへの移行はともに時間がかかる処理のため並列実行して完了を待つ。
#Elastic IPのドメイン情報を取得する
echo "aws ec2 describe-addresses --output text --public-ips ${EIP} --query 'Addresses[].Domain'"
DOMAIN=`aws ec2 describe-addresses --output text --public-ips ${EIP} --query 'Addresses[].Domain'`

echo "DOMAIN=${DOMAIN}"

#Elastic IPのドメインがVPCでなければElastic IPの移行処理を行う。
if [ "${DOMAIN}" != "vpc" ] ; then
  #Elastic IPをVPCに移行する
  aws ec2 move-address-to-vpc --output text --public-ip ${EIP}
  echo "EIP=${EIP}"
fi


#AMI作成処理中のステータスを取得する
echo "aws ec2 describe-images --output text --image-ids ${AMI_ID} --query 'Images[].State'"
STATE=`aws ec2 describe-images --output text --image-ids ${AMI_ID} --query 'Images[].State'`

echo "STATE=${STATE}"

#AMI作成処理が終了するまで処理を待つ
while [ "${STATE}" != "available" ]
do
  sleep ${DELAY}
  STATE=`aws ec2 describe-images --output text --image-ids ${AMI_ID} --query 'Images[].State'`
  echo "STATE=${STATE}"
done

#Elastic IPのVPCへの移行処理が終了するまで処理を待つ
while [ "${DOMAIN}" != "vpc" ]
do
  sleep ${DELAY}
  DOMAIN=`aws ec2 describe-addresses --output text --public-ips ${EIP} --query 'Addresses[].Domain'`
  echo "DOMAIN=${DOMAIN}"
done

#VPCへ移行後のElastic IPのAllocationIdを取得する
echo "aws ec2 describe-addresses --output text --public-ips ${EIP} --query 'Addresses[].AllocationId'"
ALLOC_ID=`aws ec2 describe-addresses --output text --public-ips ${EIP} --query 'Addresses[].AllocationId'`

echo "ALLOC_ID=${ALLOC_ID}"

#取得したAMIから移行先のVPCにインスタンスを起動する
echo "aws ec2 run-instances --output text --image-id ${AMI_ID} --count 1 --instance-type ${TO_TYPE} --key-name ${TO_KEY_NAME} --security-group-ids ${TO_SG_ID} --subnet-id ${TO_SUBNET_ID} --private-ip-address ${TO_PIP} --disable-api-termination --associate-public-ip-address --query 'Instances[].InstanceId'"
TO_INS_ID=`aws ec2 run-instances --output text --image-id ${AMI_ID} --count 1 --instance-type ${TO_TYPE} --key-name ${TO_KEY_NAME} --security-group-ids ${TO_SG_ID} --subnet-id ${TO_SUBNET_ID} --private-ip-address ${TO_PIP} --disable-api-termination --associate-public-ip-address --query 'Instances[].InstanceId'`

echo "TO_INS_ID=${TO_INS_ID}"

#インスタンスの起動ステータスを取得する
echo "aws ec2 describe-instances --output text --instance-ids ${TO_INS_ID} --query 'Reservations[].Instances[].State.Name'"
STATE=`aws ec2 describe-instances --output text --instance-ids ${TO_INS_ID} --query 'Reservations[].Instances[].State.Name'`

echo "STATE=${STATE}"

#インスタンスが移行先VPCで起動するまで待つ
while [ "${STATE}" != "running" ] 
do
  sleep ${DELAY}
  STATE=`aws ec2 describe-instances --output text --instance-ids ${TO_INS_ID} --query 'Reservations[].Instances[].State.Name'`
  echo "STATE=${STATE}"
done

#インスタンスのシステムステータスを取得する
echo "aws ec2 describe-instance-status --output text --instance-ids ${TO_INS_ID} --query 'InstanceStatuses[].SystemStatus.Details[].Status'"
SYSTEM_STATUS=`aws ec2 describe-instance-status --output text --instance-ids ${TO_INS_ID} --query 'InstanceStatuses[].SystemStatus.Details[].Status'`

echo "SYSTEM_STATUS=${SYSTEM_STATUS}"

#インスタンスのインスタンスステータスを取得する
echo "aws ec2 describe-instance-status --output text --instance-ids ${TO_INS_ID} --query 'InstanceStatuses[].InstanceStatus.Details[].Status'"
INSTANCE_STATUS=`aws ec2 describe-instance-status --output text --instance-ids ${TO_INS_ID} --query 'InstanceStatuses[].InstanceStatus.Details[].Status'`

echo "INSTANCE_STATUS=${INSTANCE_STATUS}"

#インスタンスのシステムステータスとインスタンスステータスのチェックが完了するまで待つ
while [ "${SYSTEM_STATUS}" != "passed" -o ${INSTANCE_STATUS} != "passed" ] 
do
  sleep ${DELAY}
  SYSTEM_STATUS=`aws ec2 describe-instance-status --output text --instance-ids ${TO_INS_ID} --query 'InstanceStatuses[].SystemStatus.Details[].Status'`
  INSTANCE_STATUS=`aws ec2 describe-instance-status --output text --instance-ids ${TO_INS_ID} --query 'InstanceStatuses[].InstanceStatus.Details[].Status'`

  echo "SYSTEM_STATUS=${SYSTEM_STATUS}, INSTANCE_STATUS=${INSTANCE_STATUS}"
done

#起動完了しステータスチェックを終えたVPC移行後のインスタンスにVPCに移行したElastic IPを関連付ける
echo "aws ec2 associate-address --output text --instance-id ${TO_INS_ID} --allocation-id ${ALLOC_ID}"
ASSOC_ID=`aws ec2 associate-address --output text --instance-id ${TO_INS_ID} --allocation-id ${ALLOC_ID}`

echo "ASSOC_ID=${ASSOC_ID}"

#移行先VPCに起動後のインスタンスを判別するためにとりあえずタグに元となったAMI_NAMEを登録する
echo "aws ec2 create-tags --output text --resources ${TO_INS_ID} --tags Key=Name,Value=${AMI_NAME}"
aws ec2 create-tags --output text --resources ${TO_INS_ID} --tags Key=Name,Value=${AMI_NAME}

DATE_STR=`date +"%Y/%m/%d %H:%M:%S"`
echo "End. ${DATE_STR}"
Reference: Tech Blog citing related sources