AWS CLIでRDSをこまめに止めてコスト削減

2018年12月4日Amazon RDS,Amazon Web Services,AWS CLI,PowerShell,プログラミング,開発・導入事例,開発環境・ツール

プラットフォーム技術部の小林正彦です。

Amazon Web Service。何と無しにマネジメントコンソールをさわっていたら、先月まで実現できなかったことがいつのまにかできるようになっている。つい最近話題に上ったことがすでに実装されている。という驚きに、AWSの最新情報には常にアンテナを張っていなければと改めて思いました。

さて、本記事ではRDSインスタンスの停止操作に関する情報の整理と、AWS CLIコマンドによる簡単な起動停止スクリプトのご紹介をしていきます。記事をご覧になった方の日々の運用を改善するヒントになれば幸いです。最後までお付き合いよろしくお願いします。

RDSインスタンスの停止について

RDSインスタンスを停止させる機能は2017年1月から利用可能となりました。

この機能が提供されるまではインスタンスを単純に停止することができず、「インスタンスのスナップショットを作成してからインスタンスを削除する」という作業をしなければなりませんでした。

そんな折、2017年1月以降にRDSインスタンスの停止が正式にサポートされたことによりRDSインスタンスを一時的な停止状態に置くことが簡単にできるようになりました。

また、機能を提供しはじめたタイミングでは停止可能なエンジンは

「MySQL、MariaDB、PopstgreSQL、Oracle、SQL Server」

の5つのみとなっていてAmazon Auroraは対象外とされていたのですが、そのAuroraも他のエンジンに遅れること20ヶ月、ついに停止機能が実装されました。これによりRDSが提供するすべてのDBエンジンでインスタンスを一時的に停止させておくことができる。ということになります。

下表に、2018年11月時点での各DBエンジンにおける停止操作の可否についてまとめました。

DBエンジン、シングル/マルチ構成、リードレプリカの有無等々で停止できるか否かが変わってくるので、(”すべての”と上では書いていますが)何でも停止できるわけではないということを覚えておきましょう。

エンジンSingle-AZMulti-AZ
MariaDB〇※1
Microsoft SQL Server×
MySQL〇※1
Oracle〇※1
PostgreSQL〇※1
Aurora MySQL
[Provisioned]
〇※2〇※2
Aurora MySQL
[Provisioned with Aurora parallel query enabled]
××
Aurora MySQL
[Serverless]
×
Aurora PostgreSQL〇※2〇※2

※1 リードレプリカが存在する場合にインスタンスを停止しようとすると下記のエラーが表示され、停止させることができません。

Cannot stop or start a Read-Replica instance (Service: AmazonRDS; Status Code: 400; Error Code: InvalidDBInstanceState; Request ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)

※2 クラスターの停止のみ可能。クラスター内の各インスタンス単位での停止はできない。

説明の中で「一時的な停止状態」と記載していますが、そのとおり、RDSインスタンスの停止状態とは一時的なものでしかありません。EC2インスタンスのように停止したらしっぱなしというわけにはいきません。

RDSインスタンスの停止期間は最大7日間となっています。7日をすぎるとRDSインスタンスは自動的に起動してくるということです。停止したからもう安心、ではありません。

停止における細かな制約についてはこちらも参照してください。

停止する目的

なぜRDSインスタンスを停めたいのか。それはずばりコスト削減です。使った分だけ課金されるクラウドシステムにおいて、無駄となってしまうような利用時間は極力減らしたいところです。

本番環境においてはRDSの可用性と耐久性のおかげで常に稼動し続けてくれるというのは非常にありがたいものですが、こと開発環境で24/365で常時稼動させておく必要性があるかというと、おそらくないでしょう(このテスト期間中は停めないで、という要望はでてきます)。

なので、開発環境でのRDSインスタンスの利用最適化がコスト最適化に直結するのです。

そのためにRDSインスタンスの停止について検討していきます。

停止方法

RDSインスタンス停止方法としては例えば下記のようなものがあげられます。

  • マネジメントコンソール
  • AWS APIリクエスト
    • AWS CLI
    • SDK
  • Lambda + CloudWatch Event
  • AWS Instance Scheduler※
    ※ EC2,RDS起動停止システムをデプロイできるソリューション。Amazon CloudWatch、AWS Lambda、および Amazon DynamoDB をまとめて起動し、各処理をコーディングすることなくスケジューリング可能なサービス。

これらに限らず多様なツールがありますので工夫次第で様々な仕組みを構築することができると思いますが、本記事では網羅的な停止方法の検討・検証は実施しないので、後日機会がありましたら書かせていただきます。

マネジメントコンソール

単純に停止するだけであればマネジメントコンソールで事足ります。

運用担当者が手動でRDSを管理していけばコスト最適化も実現できるでしょう。ただし手作業であるが故の面倒さは解消できません。起動/停止する時間帯に担当者がいなければならない。等々。

開発ツール

各種開発ツールでAPIを利用するアプリケーションを開発するというのも1つの方法です。

AWS CLIコマンドを実行する簡単なスクリプトを作成し、そのスクリプトを定期的に実行する仕組み(タスクスケジューラ、cron、ジョブスケジュール製品など)と環境を用意すれば、それだけで自動化されたシステムの完成です。

実行環境であるサーバー(EC2)の準備・管理や、アプリケーションの実行制御方式については検討が必要ですが、開発環境のRDS管理用であると割り切れば、それほど大掛かりなシステムを構築しなくても良いでしょうし比較的導入はしやすいと思います。

また、社内にAWSを推進する部署等があり、AWS API利用のための基盤が用意されているといった環境であれば難しいことを考えずに導入できるでしょう。

サーバーレス

Lambda + Cloudwatch Eventを組み合わせてサーバーレス環境を用意するのもいいでしょう。

実行環境サーバ(EC2)の準備・管理、処理制御の仕組みを考える必要も無く、AWSの自前のサービスを組み合わせるだけで実現できます。

例えば、インターネットに接続されていないクローズドな環境でLambdaを利用してRDSのAPIを実行する、ということもできます。

VPC内にLambdaを配置することの注意点についてこちらにまとめていますのでご参考に。

さてここまで以下のような流れで停止方法について書いてきました。

手作業 → アプリケーションによる自動化 → サーバーレス化

ですが、最終的にはサーバーレスな仕組みを実現しよう!ということをお伝えしたいわけではありません。

要するに、どの方法が優れている、劣っているという問題ではなく、今の現場の運用にどの方法ならフィットするか、ベターであるかを検討する必要があるということです。

自社のAWS利用環境が充実していて、サーバーレス構成を実現するためのナレッジがたまっている、共通化された仕組みがあるといった場合はサーバーレスを採用することになるでしょうし、場合によっては「コスト削減」という命題に対して「手運用」というのが最適解となる可能性もあります。

AWSを利用するのだからサーバーレスな構成がベストである。と決め付けてしまうのではなく、今の運用業務の延長線上にはまりそうか、という点をしっかり検討していきましょう。

自動化する意義

RDSインスタンスは停止後7日を過ぎると自動的に起動してきます。

起動タイミングにあわせて週次で停止作業をしていけばよいのですが、仮に手運用で日々インスタンスの状態を確認していたとしても、タイミング悪く休日に起動し始めて翌営業日に担当者が確認するまで休日の間ずっと起動しっぱなしだった。。。

なんてことが起こってしまうかもしれません。

そんなもったいない事態を避けるためにもRDSの起動停止を自動化しよう!というわけです。

コスト比較

「コスト削減が目的である」とはいったものの、実際にどれくらいコスト最適化が見込めるのでしょうか。下表は同じインスタンスタイプでのEC2とRDSのコスト比較表です。

t2.largeEC2RDS Single-AZRDS Multi-AZ
USD/時間0.09280.1360.27
24時間*30日
(ドル110円)
7,349円10,771円21,384円
8時間*20日
(77% off)
1,633円2,393円4,752円

表から分かるように、RDS Multi-AZ構成は同じインスタンスタイプのEC2と比べて実に3倍近い料金が課金されます。

RDSインスタンスの方が時間単価が高いのですから、RDSインスタンスの稼動時間を適切にコントロールする方がコスト最適化にインパクトがあるのはお分かりいただけると思います。

また、RDSインスタンスの課金体系は1時間単位となります。1時間未満の稼動であっても1時間分の料金として課金されてしまうので、適切に利用時間をコントロールすることで、無駄な稼働時間を減らすことができます。(ちなみにEC2インスタンスは秒単位で料金計算。)

開発環境上にEC2インスタンスとRDSインスタンスが1セットのみしか存在しないなんてことはないでしょう。開発の段階や用途に応じてセット数は増え、稼働時間の調整もばらばらだったりすると、それだけ管理が煩雑になっていきます。

例えば、開発Aインスタンスは短時間の性能テスト用なので数時間だけ起動させる。開発Bインスタンスは9-18時の業務時間中は起動する。等々。利用用途に応じて柔軟に起動停止時間を管理できることが望ましいです。

例えば「1日の稼働時間を業務時間と同じ8時間に縮小し、土日は終日停止」とするだけで、

  • 24時間 × 30日 = 720時間
  • 8時間 × 20日 = 160時間

→77%の稼働時間削減!!

ということになります。起動しっぱなしはもったいないですよ!

AWS CLIを使ったRDS管理

前置きがだいぶ長くなりましたが、ここからは具体的なユースケースを紹介して、AWS CLIを使ったRDS管理方法について書いていきます。

環境

EC2、RDSインスタンス内のOS,MWなどはこちら。

EC2
OSWindows Server 2012R2
APPowershell + AWS CLI
MW(スケジューラ)JP1
RDS
DBエンジンOracle 12c
構成Single-AZ

構成図はこちら。このように、クローズドな環境に配置されたEC2とRDS、インターネットへ接続するためのプロキシサーバが存在しています。

構成図:AWS CLIを使ったRDS管理方法

AWS CLI では、AWS サービスのパブリック API に直接アクセスして各サービスとやりとりします。そのため、インターネット経由でAWSのパブリックAPIにアクセスできること、というのがAWS CLIを利用するための大前提となります。

AWS CLIを利用する端末がインターネットに接続している端末であれば、難しいことを考えずにパブリックAPIを利用することができます。一方、クローズドな環境に配置されたインスタンスでAWS CLIによるAPI利用を検討する場合、下記のような構成が考えられます。

  • プロキシを介してインターネットへ接続する(今回の構成)
  • PrivateLinkを利用する
  • VPC Lambdaを利用する

PrivateLinkの利用する場合の注意点などについてはこちらに詳細をまとめていますのでご参考までに。

IAMロール

APIリクエストを作成する際の認証情報は、IAMロールによって生成されたものを使用します。AWS CLI環境設定にアクセスキー、シークレットアクセスキーの設定はしません。

AWS認証情報についてはスクリプト解説欄に詳細を記載したのでそちらを参照ください。

スクリプト実行環境となるEC2に、以下の3つのアクションを許可するロールをアタッチします。

RDS操作アクション

操作操作のために必要なアクションAWS CLIコマンド
起動StartDBInstancerds start-db-instance
停止StopDBInstancerds stop-db-instance
状態確認DescribeDBInstancesrds describe-db-instance

停止するだけであれば停止アクションのみを、起動だけであれば起動アクションのみをアタッチすればオッケーです。

起動停止だけであればDescribeDBInstancesのアクションは不要です。今回は1つの実行環境で3つのアクションを実行するのですべてのアクション許可ロールをアタッチしています。

今回作成したポリシーのjson出力は下記のとおり。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "rds:DescribePendingMaintenanceActions",
                "rds:StopDBInstance",
                "rds:StartDBInstance"
            ],
            "Resource": "arn:aws:rds:ap-northeast-1:<account_id>:db:<instancename>"
        }
    ]
}

スクリプト

作成の方針

コスト最適化のための自動化用スクリプトであり、基本的に開発環境のみの利用を前提にしている運用スクリプトですので、細かい障害を想定したエラーハンドリングなどは実装していません。

また、スクリプトの実行と制御はJP1で実現するため、「起動/停止/状態確認」といった各処理を1つのスクリプト上に実装するのではなく、1処理1スクリプトで分割して作成します。それらスクリプトをJP1の先行後続関係で順番に実行させていきます。

今回作成したスクリプトは下記の4つです。

  • 起動スクリプト
  • 停止スクリプト
  • ステータスチェックスクリプト
  • ステータスチェックスクリプト(指定時間継続して状態監視)

各スクリプトの内容を掲載していますのでご参考までに。見ていただければ分かると思いますが非常に簡単です。

最後にスクリプトの簡単な解説をのせています。

起動スクリプト

Set-Item env:HTTPS_PROXY -value http://<ip>:<port>/
Set-Item env:NO_PROXY -value 169.254.169.254
Set-Item env:tz -Value jst

$OPT = $Args[0]
$INSTANCENAME = $Args[1]
if( $Args[0] -ne "-i"){
    exit 9 
}elseif ( $Args[1] -eq $null ){
    exit 9
}

aws --region ap-northeast-1 rds start-db-instance `
      --db-instance-identifier $INSTANCENAME

停止スクリプト

起動スクリプトと全く同じ構造です 。AWS CLIコマンドが異なるだけ。

Set-Item env:HTTPS_PROXY -value http://<ip>:<port>/
Set-Item env:NO_PROXY -value 169.254.169.254
Set-Item env:tz -Value jst

$OPT = $Args[0]
$INSTANCENAME = $Args[1]
if( $Args[0] -ne "-i"){
    exit 9 
}elseif ( $Args[1] -eq $null ){
    exit 9
}

aws --region ap-northeast-1 rds stop-db-instance `
      --db-instance-identifier $INSTANCENAME

ステータスチェックスクリプト

インスタンスの状態確認用のスクリプトです。

rds describe-db-instanceコマンドで出力した結果から、DBInstancesStatusの情報を抽出・整形して出力しています。

Set-Item env:HTTPS_PROXY -value http://<ip>:<port>/
Set-Item env:NO_PROXY -value 169.254.169.254
Set-Item env:tz -Value jst

$OPT = $Args[0]
$INSTANCENAME = $Args[1]
if( $Args[0] -ne "-i"){
    exit 9 
}elseif ( $Args[1] -eq $null ){
    exit 9
}

$CHECK_TEXT = aws --region ap-northeast-1 rds describe-db-instance --db-instance-identifier $INSTANCENAME | findstr.exe "DBInstancesStatus"
$STATUS = $CHECK_TEXT -split '"'
Write-Host $INSTANCENAME status : $STATUS[3]

ステータスチェックスクリプト(指定時間継続して状態確認)

デフォルトで60秒間隔 × 15回 = 15分の間、インスタンスのステータスを確認します。

このスクリプトを先ほどの起動スクリプト、停止スクリプトの後続に配置して、インスタンスの状態が確実に[available/stopped]になるのを確認します。

RDSインスタンスの起動・停止処理の開始から状態が落ち着くまでに5~10分ほどの時間がかかるので、余裕を持って、確認時間のデフォルトを15分間としています。

Set-Item env:HTTPS_PROXY -value http://<ip>:<port>/
Set-Item env:NO_PROXY -value 169.254.169.254
Set-Item env:tz -Value jst

$OPT = $Args[0]
$INSTANCENAME = $Args[1]
$WAITTIME = $Args[3]

if( $Args[0] -ne "-i"){
    exit 9 
}elseif ( $Args[1] -eq $null ){
    exit 9
}elseif ( ( $Args[2] -ne $null ) -and ( $Args[2] -ne "-w" ) ){
    exit 9
}elseif ( $Args[3] -eq $null ){
    $WAITTIME = 15
}

Write-Host [start] check instance status
Write-Host target : $INSTANCENAME
Write-Host interval : 60[s]
Write-Host wait-count : $WAITTIME

for ( $i  = 0; $i -lt $WAITTIME; $i++ ){
    $CHECK_TEXT = aws --region ap-northeast-1 rds describe-db-instance --db-instance-identifier $INSTANCENAME | findstr.exe "DBInstanceStatus"
    $STATUS = $CHECK_TEXT -split '"'
    Write-Host $INSTANCENAME status : $dtatus[3]
    if ( $STATUS -eq "stopped" ) -or ( $STATUS[3] -eq "available")) {
        Write-Host [end] check instance status is success
        exit 0
    }
    sleep 60
}

Write-Host [end] check instance status is failed
exit 9

スクリプト解説

プロキシ利用設定

Set-Item env:HTTPS_PROXY -value http://<ip>:<port>/

今回のケースでは、プロキシサーバを使用して AWS にアクセスする必要があるため、HTTP_PROXY 環境変数をプロキシサーバーの IP アドレスを使用して設定しています。

プロキシ利用しない設定

さきほどのプロキシ利用設定の次の行で、「169.254.169.254」のアドレス向けのHTTPアクセスはプロキシを利用しないよ、という設定をしています。

Set-Item env:NO_PROXY -value 169.254.169.254

今回の操作に限らずにAPIリクエストを作成する処理を実装する場合は、その処理を実行するアプリケーション(というと大げさですが、今回のような非常に簡単なスクリプトもその一つ)がAWS認証情報(アクセスキー/シークレットアクセスキー)でAPIリクエストに署名をする必要があります。

例えば、AWS CLIの環境設定に「AWS Access Key ID / AWS Secret Access Key」を設定したり、環境設定には組み込まずにAWS CLIコマンドのオプションとしてkeyを引き渡す。などです。

要するにAWS認証情報を実行環境となるインスタンス内部で保持し、適切に管理していかなければならないということになります。場合によっては非常に煩雑な運用をしていかないといけなくなる可能性もあります。

そのような運用をしないために、AWS認証情報の配布を自身で管理せず、アプリケーションが使用するアクションとリソースを定義したロールを、実行環境となるEC2インスタンスにアタッチする構成をとっています。

IAMロールによって提供される認証情報を取得するには、該当のインスタンスのインスタンスメタデータを参照できなければなりません。インスタンスメタデータとは下記のパスに格納されている情報で、その時点で有効なIAMロールが作成したセキュリティ情報が格納されています。

http://169.254.169.254/latest/meta-data/iam/security-credentials/

AWS CLIは、APIリクエストを作成する際に自動的にこのメタデータを参照することになっているので、さきほどのプロキシ除外設定をいれていないと下記のようなエラーが出力され、APIリクエストを作成することができません。

Get-RDSDBInstance : No credentials specified or obrtained from persisted/shell defaults.

タイムゾーン設定

Windows環境で AWS CLI を利用時にUnicodeWarning が表示される場合は、ワーニングの出力を回避するために下記のタイムゾーン設定をします。

Set-Item env:tz -Value jst

引数判定

この辺は作成者の好みですが、引数に対象のRDSインスタンス名を指定できるようにしたので、引数判定処理を入れています。スクリプトの実行ログを出力するというような処理も実装していないので非常にシンプルです。

$OPT = $Args[0] 
$INSTANCENAME = $Args[1] 
if( $Args[0] -ne "-i"){ 
    exit 9 
}elseif ( $Args[1] -eq $null ){
    exit 9 
}

スクリプトの組み合わせ

起動/停止コマンドを実行したあとに、想定どおりにインスタンスの状態が変わったかを確認するのにマネージドコンソールを開いていたのでは自動化する意味が全くありません。

そこで、起動停止後の一定時間の状態変化を確認し、想定どおりの状態の落ち着いたところで処理が終了するという一連の流れを実装する必要があります。

上記の流れを1つのスクリプトに実装することももちろんできますが、今回はジョブ実行環境としてJP1を利用したので、各スクリプトの処理は必要最低限にし、実行タイミングの制御や、先行後続関係の設定、処理のハンドリング等はJP1の機能を利用して実現しています。

実際のジョブ構成はこのような感じです。処理が処理だけに非常にシンプルでおもしろみは無いですね。

実際のジョブ構成

真ん中のCheckInstanceInterval処理で指定時間(15分)経過してもインスタンスの状態が[aveirable|stopped]になっていない場合は異常終了し、後続のリカバリジョブでもう一度起動処理を実施させています。

まとめ

RDS起動しっぱなしになっていませんか?下記事項を確認して、RDS停止運用を導入し適切に時間コントロールしましょう!

  • 開発環境のRDS、無駄な起動時間はないか
  • 今の環境で適用可能な停止方法は何か
  • AWS認証情報の扱い インスタンスごとに管理 or IAMロールで管理
  • 開発ツールの選定
  • アプリケーションの実行制御方法

  • 株式会社アークシステムの予約・来訪管理システム BRoomHubs
  • 低コスト・短納期で提供するまるごとおまかせZabbix