AWS の RDS でスナップショットをCLI や SDK を使ってクロスリージョンでコピーする方法

RDS には取得したスナップショットをクロスリージョンでコピーする機能があります。今年の3月に追加された機能のようで、割と新しめの機能のようですがすごく便利で、災害時を想定した非機能要件を簡単に満たせるようになるので使っていきたいところです。

マネージメントコンソールでの使用方法は、Sunny Cloud さんのブログ記事に詳細に書かれているので御覧ください。https://www.sunnycloud.jp/column/20210308-01/

弊社でもこの機能を使っていこうと思い、自動化のためにAPIを調べて実装していたのですが、幾つかハマりポイントがあったので共有致します。説明の前に、まずはうまくいったケースの AWS CLI と PHP の SDK のサンプルを載せておきます。CLIの例です。

aws rds copy-db-cluster-snapshot \
--source-db-cluster-snapshot-identifier arn:aws:rds:ap-northeast-1:************:cluster-snapshot:snapshot-1 \
--target-db-cluster-snapshot-identifier snapshot-copy-1 \
--kms-key-id ********-****-****-****-************ \
--source-region ap-northeast-1 \
--region us-east-1

成功すると以下のようなJSONが返ってきます。

{
    "DBClusterSnapshot": {
        "AvailabilityZones": [],
        "DBClusterSnapshotIdentifier": "snapshot-copy-1",
        "DBClusterIdentifier": "database-1",
        "SnapshotCreateTime": "2021-10-18T10:52:08.803000+00:00",
        "Engine": "aurora-mysql",
        "AllocatedStorage": 0,
        "Status": "copying",
        "Port": 0,
        "ClusterCreateTime": "2021-10-11T09:35:31.780000+00:00",
        "MasterUsername": "admin",
        "EngineVersion": "5.7.mysql_aurora.2.07.2",
        "LicenseModel": "aurora-mysql",
        "SnapshotType": "manual",
        "PercentProgress": 0,
        "StorageEncrypted": true,
        "KmsKeyId": "arn:aws:kms:us-east-1:************:key/********-****-****-****-************",
        "DBClusterSnapshotArn": "arn:aws:rds:us-east-1:************:cluster-snapshot:snapshot-copy-1",
        "SourceDBClusterSnapshotArn": "arn:aws:rds:ap-northeast-1:************:cluster-snapshot:snapshot-1",
        "IAMDatabaseAuthenticationEnabled": false,
        "TagList": []
    }
}

次はPHPの例です。

<?php

require 'vendor/autoload.php';

use \Aws\Rds\RdsClient;

$rds = new RdsClient([
    'region' => 'us-east-1',
    'version' => '2014-10-31',
]);

$result = $rds->copyDBClusterSnapshot([
    'SourceDBClusterSnapshotIdentifier' => 'arn:aws:rds:ap-northeast-1:************:cluster-snapshot:snapshot-1',
    'TargetDBClusterSnapshotIdentifier' => 'snapshot-copy-1',
    'SourceRegion' => 'ap-northeast-1',
    'KmsKeyId' => '********-****-****-****-************',
]);

var_dump($result);

成功したら以下のようなデータが返却されます。

class Aws\Result#214 (2) {
  private $data =>
  array(2) {
    'DBClusterSnapshot' =>
    array(21) {
      'AvailabilityZones' =>
      array(0) {
        ...
      }
      'DBClusterSnapshotIdentifier' =>
      string(15) "snapshot-copy-1"
      'DBClusterIdentifier' =>
      string(10) "database-1"
      'SnapshotCreateTime' =>
      class Aws\Api\DateTimeResult#230 (3) {
        ...
      }
      'Engine' =>
      string(12) "aurora-mysql"
      'EngineMode' =>
      string(11) "provisioned"
      'AllocatedStorage' =>
      int(0)
      'Status' =>
      string(7) "copying"
      'Port' =>
      int(0)
      'ClusterCreateTime' =>
      class Aws\Api\DateTimeResult#231 (3) {
        ...
      }
      'MasterUsername' =>
      string(5) "admin"
      'EngineVersion' =>
      string(23) "5.7.mysql_aurora.2.07.2"
      'LicenseModel' =>
      string(12) "aurora-mysql"
      'SnapshotType' =>
      string(6) "manual"
      'PercentProgress' =>
      int(0)
      'StorageEncrypted' =>
      bool(true)
      'KmsKeyId' =>
      string(75) "arn:aws:kms:us-east-1:************:key/********-****-****-****-************"
      'DBClusterSnapshotArn' =>
      string(67) "arn:aws:rds:us-east-1:************:cluster-snapshot:snapshot-copy-1"
      'SourceDBClusterSnapshotArn' =>
      string(67) "arn:aws:rds:ap-northeast-1:************:cluster-snapshot:snapshot-1"
      'IAMDatabaseAuthenticationEnabled' =>
      bool(false)
      'TagList' =>
      array(0) {
        ...
      }
    }
    '@metadata' =>
    array(4) {
      'statusCode' =>
      int(200)
      'effectiveUri' =>
      string(35) "https://rds.us-east-1.amazonaws.com"
      'headers' =>
      array(4) {
        ...
      }
      'transferStats' =>
      array(1) {
        ...
      }
    }
  }
  private $monitoringEvents =>
  array(0) {
  }
}

ポイント

1. コピー先のリージョンにカスタマー管理型のKMSキーを作成しておく

クロスリージョンのスナップショットコピーでは、コピー先のスナップショットの暗号化にカスタマー管理型のキー(CMK)が必要なようです。KMSで予め作成しておきましょう。作れたらKMSの画面で以下のように表示されるはずです。

画像内の赤枠で囲っている部分が `–kms-key-id` で必要なパラメータになります。

2. –source-db-cluster-snapshot-identifier にはIDではなくARNの値を入力する

パラメーターのキー名に identfier とあるのでスナップショット名のことかと思ってしまいますし、実際同じリージョン内でコピーする時はスナップショット名をここに入力するのですが、クロスリージョンの場合はARNとなります。ここは結構罠だと思います。おそらくスナップショット名はリージョン内でしか一意でないので、ARNを入力する必要があるのでしょう。

3. コピー先リージョンからCLIやSDKの操作を実行する

これもハマりどころでした。例えば東京リージョンからバージニア北部にコピーしたい場合、普通の感覚だと東京リージョンで操作を実行するものだと考えると思います。実際、マネージメントコンソールだとコピー元リージョンのスナップショットからコピー先リージョンを選んでコピーします。

しかし、CLIやSDKからスナップショットをクロスリージョンコピーする場合はコピー先のリージョンでコマンドを実行する必要があります。この場合はバージニア北部から操作を実行するのが正解となります。最初に掲載したサンプルでも、いずれもコピー先のリージョン(us-east-1)からコマンドを実行しているのが見て取れるかと思います。

コツが分かれば大したことない話なのですが、こんなつまらないことで数時間も費やしてしまったので、同じ問題で他の人が時間を浪費しないように記録させていただいた次第です。

APIやSDKのリファレンスを読むと PreSignedUrl というパラメーターにコピー用のWeb API呼び出し用のURLを渡す必要があるようなことが書いてありますが、よく読むとそのすぐ後に SourceRegion を渡せばAPI or SDK側でよしなにやってくれるという記述があります。

殆どの場合では PreSignedUrl を作るよりは SourceRegion を設定した方が簡単なのではないかと思います。