AWS CLIを利用したRDSの起動停止スクリプト(検証環境用2017年1月版)

AWS CLI

西澤です。検証環境のRDSの利用料金を節約する為に、みんな大好きAWS CLIを利用した停止/再開相当のスクリプトを用意した話を以前に紹介しました。

こちらを最新化する必要があったので、2017年1月時点での更新版を記録しておこうと思います。ちなみに今回はRedshiftについては触れません。

前提

執筆時点のRDSにおいて、検証環境等の常時起動が不要なRDS環境を、少しでも安く利用する為に用意したスクリプトをご紹介し、同じような仕組みを準備したいと考えている方の参考にしていただければと公開しているものになります。また、スケジュール設定により自動実行とするものではなく、手動でのスクリプト実行を想定したものですので、停止(削除)時には、確認プロンプトが返されるようにしました。当然ですが、本番環境での動作を保証するような類のものではありませんのでご了承ください。

RDS Oracleの起動停止スクリプト

前回の記事を執筆した時点では、RDSスナップショットからのリストア時にSecurity Groupの指定ができていたのですが、botocore1.2.7になったタイミングから指定不可となっていました。下記記事でもご指摘をいただいていましたが、記載の修正が遅れましたことをお詫びいたします(前回の記事内に注釈を加えました)。

今回はこちらを踏まえて修正版となります。下記はRDS Oracleを利用して試したものとなる為、他のDBエンジンではオプションの修正が必要となるかもしれません。

#!/bin/bash
set -e

### 環境に合わせて変数を指定
export AWS_DEFAULT_REGION=ap-northeast-1
RDS_IDENTIFIER=myrdsinstance
RDS_INSTANCE_CLASS=db.t2.micro
AZ=ap-northeast-1
DB_PARAMTER_GROUP=mydbparamgroup
DB_SUBNET_GROUP=mydbsubnetgroup
DB_OPTION_GROUP_NAME=mydboptiongroup
STORAGE_TYPE=gp2
VPC_SECURITY_GROUP="sg-xxxxxxxx"

### RDSインスタンスのステータスを表示
function rds_status() {
  aws rds describe-db-instances \
    --db-instance-identifier ${RDS_IDENTIFIER} \
    --query "DBInstances[].{DBInstanceIdentifier:DBInstanceIdentifier,DBInstanceStatus:DBInstanceStatus}" \
    --output table
}

### RDSインスタンスがavailableになるまでひらすら待つ(タイムアウト指定はないので要注意)
function rds_wait_available() {
  while :
  do
    RDS_STATUS=$(aws rds describe-db-instances \
      --db-instance-identifier ${RDS_IDENTIFIER} \
      --query "DBInstances[].DBInstanceStatus" \
      --output text)
    if [ "${RDS_STATUS}" != "available" ]; then
      echo "$(date +'[%Y/%m/%d %H:%M:%S]') Waiting for RDS Instance=${RDS_IDENTIFIER} is to be in available state..."
      sleep 10
    else
      break
    fi
  done
}

### 引数指定で分岐
case $1 in
  start)

    ### 最新のRDS手動スナップショットを取得
    LAST_RDS_SNAPSHOT=$(aws rds describe-db-snapshots \
      --snapshot-type manual \
      --query "reverse(sort_by(DBSnapshots,&SnapshotCreateTime))[?DBInstanceIdentifier==\`${RDS_IDENTIFIER}\`]|[0].[DBSnapshotIdentifier]" \
      --output text)

    ### 取得したRDSスナップショットからリストア、--no-multi-az、--no-publicly-accessibleは静的に指定
    aws rds restore-db-instance-from-db-snapshot \
      --db-instance-identifier ${RDS_IDENTIFIER} \
      --db-snapshot-identifier ${LAST_RDS_SNAPSHOT} \
      --db-instance-class ${RDS_INSTANCE_CLASS} \
      --availability-zone ${AZ} \
      --db-subnet-group-name ${DB_SUBNET_GROUP} \
      --no-multi-az \
      --no-publicly-accessible \
      --option-group-name ${DB_OPTION_GROUP_NAME} \
      --storage-type ${STORAGE_TYPE}

    ### リストア完了まで待つ、10分程度かかることがある
    rds_wait_available

    ### DBパラメータグループ、VPC Security Groupを修正
    aws rds modify-db-instance \
      --db-instance-identifier ${RDS_IDENTIFIER} \
      --db-parameter-group-name ${DB_PARAMTER_GROUP} \
      --vpc-security-group-ids ${VPC_SECURITY_GROUP} \
      --apply-immediately

    ### 設定変更反映の為、RDSインスタンスをリブート
    aws rds reboot-db-instance \
      --db-instance-identifier ${RDS_IDENTIFIER}

    ### RDSインスタンスのステータスを表示
    rds_status

    ;;
  stop)

    ### 停止(削除)確認プロンプトを返す
    echo -n "Are you sure you are going to stop RDS Instance: ${RDS_IDENTIFIER}? [y/n]: "
    read ANS
    case $ANS in
      "Y" | "y" | "yes" | "Yes" | "YES" )
        echo "RDS Instnace: ${RDS_IDENTIFIER} is going to stop."
        ;;
      * )
        echo "Operation is cancelled."
        exit 1
        ;;
    esac

    ### 取得するRDSスナップショット名を指定
    RDS_SNAPSHOT_NAME=${RDS_IDENTIFIER}-$(date +'%Y%m%d-%H%M%S')

    ### 最終スナップショットを取得しつつ、RDSインスタンスを停止(削除)
    aws rds delete-db-instance \
      --db-instance-identifier ${RDS_IDENTIFIER} \
      --no-skip-final-snapshot \
      --final-db-snapshot-identifier ${RDS_SNAPSHOT_NAME}

    ### RDSインスタンスのステータスを表示
    rds_status

    ;;
  status)
    
    ### RDSインスタンスのステータスを表示
    rds_status

    ;;
  *)

    ### 引数誤り
    echo "Usage: $0 {start|stop|status}"

    ;;
esac

静的に指定しているオプションやスキップしているオプションがありますので、ご利用の際には必ず公式ドキュメントをご確認ください。

RDS Auroraの起動停止スクリプト

Auroraの場合には、インスタンス1台の構成であってもDBクラスタとしての操作が必要となる為、必要な処理がかなり複雑になりました。RDSインスタンスはクラスタのコンピューティングリソースとしてを用意した上で、そのクラスタ内で操作するインスタンスを操作するということがよく理解でき、個人的には結果的にとても勉強になりました。PostgreSQL互換のAuroraが正式に利用できるようになると、EngineやEngineVersion指定が必要となる可能性があります。

#!/bin/bash
set -e

### 環境に合わせて変数を指定
export AWS_DEFAULT_REGION=ap-northeast-1
RDS_IDENTIFIER=myaurora
DB_CLUSTER_IDENTIFIER=myauroracluster
RDS_INSTANCE_CLASS=db.t2.medium
AZ=ap-northeast-1a
DB_PARAMTER_GROUP=mydbparamgroup
DB_CLUSTER_PARAMETER_GROUP=mydbclusterparamgroup
DB_SUBNET_GROUP=mydbsubnetgroup
DB_OPTION_GROUP_NAME=mydboptiongroup
VPC_SECURITY_GROUP="sg-xxxxxxxx"

### Auroraクラスタのステータスを表示
function aurora_status() {
  aws rds describe-db-clusters \
    --db-cluster-identifier ${DB_CLUSTER_IDENTIFIER} \
    --query "DBClusters[].{DBClusterIdentifier:DBClusterIdentifier,Status:Status}" \
    --output table
  aws rds describe-db-instances \
    --db-instance-identifier ${RDS_IDENTIFIER} \
    --query "DBInstances[].{DBInstanceIdentifier:DBInstanceIdentifier,DBInstanceStatus:DBInstanceStatus}" \
    --output table
}

### Auroraクラスタがavailableになるまでひたすら待つ(タイムアウト指定はないので要注意)
function aurora_cluster_wait_available() {
  while :
  do
    AURORA_CLUSTER_STATUS=$(aws rds describe-db-clusters \
      --db-cluster-identifier ${DB_CLUSTER_IDENTIFIER} \
      --query "DBClusters[].Status" \
      --output text)
    if [ "${AURORA_CLUSTER_STATUS}" != "available" ]; then
      echo "$(date +'[%Y/%m/%d %H:%M:%S]') Waiting for Aurora Cluster=${DB_CLUSTER_IDENTIFIER} to be in available state..."
      sleep 10
    else
      break
    fi
  done
}

### Auroraクラスタメンバーとして起動したRDSインスタンスがavailableになるまでひたすら待つ(タイムアウト指定はないので要注意)
function rds_wait_available() {
  while :
  do
    RDS_STATUS=$(aws rds describe-db-instances \
      --db-instance-identifier ${RDS_IDENTIFIER} \
      --query "DBInstances[].DBInstanceStatus" \
      --output text)
    if [ "${RDS_STATUS}" != "available" ]; then
      echo "$(date +'[%Y/%m/%d %H:%M:%S]') Waiting for RDS Instance=${RDS_IDENTIFIER} to be in available state..."
      sleep 10
    else
      break
    fi
  done
}

### Auroraクラスタ内のメンバーが0になるまでひたすら待つ(タイムアウト指定はないので要注意)
function aurora_cluster_members_null() {
  while :
  do
    AURORA_CLUSTER_MEMBERS=$(aws rds describe-db-clusters \
      --db-cluster-identifier ${DB_CLUSTER_IDENTIFIER} \
      --query "Clusters[].DBClusterMembers[]")
    if [ "${AURORA_CLUSTER_MEMBERS}" != "null" ]; then
      echo "$(date +'[%Y/%m/%d %H:%M:%S]') Waiting for Members fo Aurora Cluster=${DB_CLUSTER_IDENTIFIER} to be null..."
      sleep 10
    else
      break
    fi
  done
}

### 引数指定で分岐
case $1 in
  start)

    ### 最新のAuroraクラスタ手動スナップショットを取得
    LAST_AURORA_CLUSTER_SNAPSHOT=$(aws rds describe-db-cluster-snapshots \
      --snapshot-type manual \
      --query "reverse(sort_by(DBClusterSnapshots,&SnapshotCreateTime))[?DBClusterIdentifier==\`${DB_CLUSTER_IDENTIFIER}\`]|[0].[DBClusterSnapshotIdentifier]" \
      --output text)

    ### 取得したAuroraクラスタスナップショットからリストア、--engineは静的に指定
    aws rds restore-db-cluster-from-snapshot \
      --db-cluster-identifier ${DB_CLUSTER_IDENTIFIER} \
      --snapshot-identifier ${LAST_AURORA_CLUSTER_SNAPSHOT} \
      --engine "aurora" \
      --db-subnet-group-name ${DB_SUBNET_GROUP} \
      --vpc-security-group-ids "${VPC_SECURITY_GROUP}"

    ### リストア完了まで待つ、30分以上かかるケースもありました
    aurora_cluster_wait_available

    ### DBクラスタパラメータグループを修正
    aws rds modify-db-cluster \
      --db-cluster-identifier ${DB_CLUSTER_IDENTIFIER} \
      --db-cluster-parameter-group-name ${DB_CLUSTER_PARAMETER_GROUP}

    ### クラスタメンバーとしてRDSインスタンスを起動、--engine、--no-multi-az、--no-publicly-accessibleは静的に指定
    aws rds create-db-instance \
      --db-instance-identifier ${RDS_IDENTIFIER} \
      --db-instance-class ${RDS_INSTANCE_CLASS} \
      --engine aurora \
      --availability-zone ${AZ} \
      --db-parameter-group-name ${DB_PARAMTER_GROUP} \
      --no-multi-az \
      --no-publicly-accessible \
      --option-group-name ${DB_OPTION_GROUP_NAME} \
      --db-cluster-identifier ${DB_CLUSTER_IDENTIFIER}

    ### メンバーサーバがavailableになるまで待つ
    rds_wait_available

    ### Auroraクラスタのステータスを表示
    aurora_status

    ;;
  stop)

    ### 停止(削除)確認プロンプトを返す
    echo -n "Are you sure you are going to stop Aurora Cluster: ${DB_CLUSTER_IDENTIFIER}? [y/n]: "
    read ANS
    case $ANS in
      "Y" | "y" | "yes" | "Yes" | "YES" )
        echo "Aurora Cluster: ${DB_CLUSTER_IDENTIFIER} is going to stop."
        ;;
      * )
        echo "Operation is cancelled."
        exit 1
        ;;
    esac

    ### クラスタメンバーであるRDSインスタンスを削除
    aws rds delete-db-instance \
      --db-instance-identifier ${RDS_IDENTIFIER} \
      --skip-final-snapshot

    ### クラスタメンバーが0になるまで待つ
    aurora_cluster_members_null

    ### 取得するクラスタスナップショット名を指定
    AURORA_CLUSTER_SNAPSHOT_NAME=${DB_CLUSTER_IDENTIFIER}-$(date +'%Y%m%d-%H%M%S')

    ### 最終スナップショットを取得しつつ、Auroraクラスタを停止(削除)
    aws rds delete-db-cluster \
      --db-cluster-identifier ${DB_CLUSTER_IDENTIFIER} \
      --no-skip-final-snapshot \
      --final-db-snapshot-identifier ${AURORA_CLUSTER_SNAPSHOT_NAME}

    ### Auroraクラスタのステータスを表示
    aurora_status

    ;;
  status)

    ### Auroraクラスタのステータスを表示
    aurora_status

    ;;
  *)

    ### 引数誤り
    echo "Usage: $0 {start|stop|status}"

    ;;
esac

同様に、静的に指定しているオプションやスキップしているオプションがありますので、ご利用の際には必ず公式ドキュメントをご確認ください。

まとめ

これまでAuroraの操作をAPIから行ったことが無かったのですが、クラスタメンバーの削除は--skip-final-snapshotで行って、クラスタ削除のところで--no-skip-final-snapshotを指定するところから、データの管理はクラスタメンバーで行われていないことが納得できました。特にAuroraの操作が少々複雑になってしまったのですが、AWS CLIでAWSを操作することで、実体の理解につながる場合がよくありますので、色々と触って試してみていただければと思います。

どこかの誰かのお役に立てば嬉しいです。